diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift
index 186cd4c4..b4187938 100644
--- a/packages/apple/Sources/Models/Types.swift
+++ b/packages/apple/Sources/Models/Types.swift
@@ -20,8 +20,8 @@ public enum AlternativeBillingModeAndroid: String, Codable, CaseIterable {
case alternativeOnly = "alternative-only"
}
-/// Billing program types for external content links and external offers (Android)
-/// Available in Google Play Billing Library 8.2.0+
+/// Billing program types for external content links, external offers, and external payments (Android)
+/// Available in Google Play Billing Library 8.2.0+, EXTERNAL_PAYMENTS added in 8.3.0
public enum BillingProgramAndroid: String, Codable, CaseIterable {
/// Unspecified billing program. Do not use.
case unspecified = "unspecified"
@@ -31,6 +31,25 @@ public enum BillingProgramAndroid: String, Codable, CaseIterable {
/// External Offers program.
/// Allows offering digital content purchases outside the app.
case externalOffer = "external-offer"
+ /// External Payments program (Japan only).
+ /// Allows presenting a side-by-side choice between Google Play Billing and developer's external payment option.
+ /// Users can choose to complete the purchase on the developer's website.
+ /// Available in Google Play Billing Library 8.3.0+
+ case externalPayments = "external-payments"
+}
+
+/// Launch mode for developer billing option (Android)
+/// Determines how the external payment URL is launched
+/// Available in Google Play Billing Library 8.3.0+
+public enum DeveloperBillingLaunchModeAndroid: String, Codable, CaseIterable {
+ /// Unspecified launch mode. Do not use.
+ case unspecified = "unspecified"
+ /// Google Play will launch the link in an external browser or eligible app.
+ /// Use this when you want Play to handle launching the external payment URL.
+ case launchInExternalBrowserOrApp = "launch-in-external-browser-or-app"
+ /// The caller app will launch the link after Play returns control.
+ /// Use this when you want to handle launching the external payment URL yourself.
+ case callerWillLaunchLink = "caller-will-launch-link"
}
public enum ErrorCode: String, Codable, CaseIterable {
@@ -194,6 +213,9 @@ public enum IapEvent: String, Codable, CaseIterable {
case purchaseError = "purchase-error"
case promotedProductIos = "promoted-product-ios"
case userChoiceBillingAndroid = "user-choice-billing-android"
+ /// Fired when user selects developer-provided billing option in external payments flow.
+ /// Available on Android with Google Play Billing Library 8.3.0+
+ case developerProvidedBillingAndroid = "developer-provided-billing-android"
}
/// Unified purchase states from IAPKit verification response.
@@ -401,6 +423,16 @@ public struct BillingProgramReportingDetailsAndroid: Codable {
public var externalTransactionToken: String
}
+/// Details provided when user selects developer billing option (Android)
+/// Received via DeveloperProvidedBillingListener callback
+/// Available in Google Play Billing Library 8.3.0+
+public struct DeveloperProvidedBillingDetailsAndroid: Codable {
+ /// External transaction token used to report transactions made through developer billing.
+ /// This token must be used when reporting the external transaction to Google Play.
+ /// Must be reported within 24 hours of the transaction.
+ public var externalTransactionToken: String
+}
+
/// Discount amount details for one-time purchase offers (Android)
/// Available in Google Play Billing Library 7.0+
public struct DiscountAmountAndroid: Codable {
@@ -896,6 +928,20 @@ public struct AndroidSubscriptionOfferInput: Codable {
}
}
+/// Parameters for creating billing program reporting details (Android)
+/// Used with createBillingProgramReportingDetailsAsync
+/// Available in Google Play Billing Library 8.3.0+
+public struct BillingProgramReportingDetailsParamsAndroid: Codable {
+ /// The billing program to create reporting details for
+ public var billingProgram: BillingProgramAndroid
+
+ public init(
+ billingProgram: BillingProgramAndroid
+ ) {
+ self.billingProgram = billingProgram
+ }
+}
+
public struct DeepLinkOptions: Codable {
/// Android package name to target (required on Android)
public var packageNameAndroid: String?
@@ -911,6 +957,28 @@ public struct DeepLinkOptions: Codable {
}
}
+/// Parameters for developer billing option in purchase flow (Android)
+/// Used with BillingFlowParams to enable external payments flow
+/// Available in Google Play Billing Library 8.3.0+
+public struct DeveloperBillingOptionParamsAndroid: Codable {
+ /// The billing program (should be EXTERNAL_PAYMENTS for external payments flow)
+ public var billingProgram: BillingProgramAndroid
+ /// The launch mode for the external payment link
+ public var launchMode: DeveloperBillingLaunchModeAndroid
+ /// The URI where the external payment will be processed
+ public var linkUri: String
+
+ public init(
+ billingProgram: BillingProgramAndroid,
+ launchMode: DeveloperBillingLaunchModeAndroid,
+ linkUri: String
+ ) {
+ self.billingProgram = billingProgram
+ self.launchMode = launchMode
+ self.linkUri = linkUri
+ }
+}
+
public struct DiscountOfferInputIOS: Codable {
public var identifier: String
public var keyIdentifier: String
@@ -1032,6 +1100,10 @@ public struct PurchaseOptions: Codable {
}
public struct RequestPurchaseAndroidProps: Codable {
+ /// Developer billing option parameters for external payments flow (8.3.0+).
+ /// When provided, the purchase flow will show a side-by-side choice between
+ /// Google Play Billing and the developer's external payment option.
+ public var developerBillingOption: DeveloperBillingOptionParamsAndroid?
/// Personalized offer flag
public var isOfferPersonalized: Bool?
/// Obfuscated account ID
@@ -1042,11 +1114,13 @@ public struct RequestPurchaseAndroidProps: Codable {
public var skus: [String]
public init(
+ developerBillingOption: DeveloperBillingOptionParamsAndroid? = nil,
isOfferPersonalized: Bool? = nil,
obfuscatedAccountIdAndroid: String? = nil,
obfuscatedProfileIdAndroid: String? = nil,
skus: [String]
) {
+ self.developerBillingOption = developerBillingOption
self.isOfferPersonalized = isOfferPersonalized
self.obfuscatedAccountIdAndroid = obfuscatedAccountIdAndroid
self.obfuscatedProfileIdAndroid = obfuscatedProfileIdAndroid
@@ -1188,6 +1262,10 @@ public struct RequestPurchasePropsByPlatforms: Codable {
}
public struct RequestSubscriptionAndroidProps: Codable {
+ /// Developer billing option parameters for external payments flow (8.3.0+).
+ /// When provided, the purchase flow will show a side-by-side choice between
+ /// Google Play Billing and the developer's external payment option.
+ public var developerBillingOption: DeveloperBillingOptionParamsAndroid?
/// Personalized offer flag
public var isOfferPersonalized: Bool?
/// Obfuscated account ID
@@ -1208,6 +1286,7 @@ public struct RequestSubscriptionAndroidProps: Codable {
public var subscriptionProductReplacementParams: SubscriptionProductReplacementParamsAndroid?
public init(
+ developerBillingOption: DeveloperBillingOptionParamsAndroid? = nil,
isOfferPersonalized: Bool? = nil,
obfuscatedAccountIdAndroid: String? = nil,
obfuscatedProfileIdAndroid: String? = nil,
@@ -1217,6 +1296,7 @@ public struct RequestSubscriptionAndroidProps: Codable {
subscriptionOffers: [AndroidSubscriptionOfferInput]? = nil,
subscriptionProductReplacementParams: SubscriptionProductReplacementParamsAndroid? = nil
) {
+ self.developerBillingOption = developerBillingOption
self.isOfferPersonalized = isOfferPersonalized
self.obfuscatedAccountIdAndroid = obfuscatedAccountIdAndroid
self.obfuscatedProfileIdAndroid = obfuscatedProfileIdAndroid
diff --git a/packages/docs/src/components/MenuDropdown.tsx b/packages/docs/src/components/MenuDropdown.tsx
index 45061362..d55adf66 100644
--- a/packages/docs/src/components/MenuDropdown.tsx
+++ b/packages/docs/src/components/MenuDropdown.tsx
@@ -63,6 +63,7 @@ export function MenuDropdown({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
+ padding: '0.4rem 0',
}}
>
+ Fired when a user selects developer-provided billing in the External
+ Payments flow on Android. This is different from User Choice Billing -
+ it presents a side-by-side choice dialog in the purchase flow itself.
+
+ Registers a listener for Developer Provided Billing events. This
+ listener is only triggered when the user selects the developer's
+ payment option (instead of Google Play) in the External Payments flow.
+
+
+
+ {{
+ typescript: (
+ {`import { developerProvidedBillingListener } from 'expo-iap';
+
+const subscription = developerProvidedBillingListener(async (details) => {
+ console.log('User selected developer billing');
+ console.log('Token:', details.externalTransactionToken);
+
+ // Process payment with your payment system
+ const paymentResult = await processPaymentWithYourGateway({
+ token: details.externalTransactionToken,
+ // Your payment details
+ });
+
+ if (paymentResult.success) {
+ // IMPORTANT: Report the token to Google Play within 24 hours
+ await reportExternalTransactionToGoogle(details.externalTransactionToken);
+ grantUserAccess();
+ }
+});
+
+// Cleanup when done
+subscription.remove();`}
+ ),
+ kotlin: (
+ {`import dev.hyo.openiap.DeveloperProvidedBillingDetailsAndroid
+
+// Using callback
+openIapStore.addDeveloperProvidedBillingListener { details ->
+ println("User selected developer billing")
+ println("Token: \${details.externalTransactionToken}")
+
+ lifecycleScope.launch {
+ // Process payment with your payment system
+ val paymentResult = processPaymentWithYourGateway(
+ token = details.externalTransactionToken
+ )
+
+ if (paymentResult.success) {
+ // IMPORTANT: Report the token to Google Play within 24 hours
+ reportExternalTransactionToGoogle(details.externalTransactionToken)
+ grantUserAccess()
+ }
+ }
+}`}
+ ),
+ dart: (
+ {`import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+
+// Android only (8.3.0+) - will not fire on iOS or older Android
+final subscription = FlutterInappPurchase.developerProvidedBillingStream
+ .listen((details) async {
+ print('User selected developer billing');
+ print('Token: \${details.externalTransactionToken}');
+
+ // Process payment with your payment system
+ final paymentResult = await processPaymentWithYourGateway(
+ token: details.externalTransactionToken,
+ );
+
+ if (paymentResult.success) {
+ // IMPORTANT: Report the token to Google Play within 24 hours
+ await reportExternalTransactionToGoogle(details.externalTransactionToken);
+ grantUserAccess();
+ }
+});
+
+// Cleanup when done
+subscription.cancel();`}
+ ),
+ }}
+
+
+
Event Payload
+
+ {{
+ typescript: (
+ {`interface DeveloperProvidedBillingDetails {
+ externalTransactionToken: string;
+}`}
+ ),
+ swift: (
+ {`// Android only - not available on iOS`}
+ ),
+ kotlin: (
+ {`data class DeveloperProvidedBillingDetailsAndroid(
+ val externalTransactionToken: String
+)`}
+ ),
+ dart: (
+ {`class DeveloperProvidedBillingDetails {
+ final String externalTransactionToken;
+}`}
+ ),
+ }}
+
+
+ externalTransactionToken - Token that must be
+ reported to Google Play within 24 hours after completing the payment
+
+
+
Comparison: User Choice vs Developer Provided Billing
+
+
+
+
Feature
+
User Choice Billing
+
Developer Provided Billing
+
+
+
+
+
Billing Library
+
7.0+
+
8.3.0+
+
+
+
Availability
+
Eligible regions
+
Japan only
+
+
+
When presented
+
After initConnection()
+
During requestPurchase()
+
+
+
UI
+
Separate dialog before purchase
+
Side-by-side choice in purchase dialog
+
+
+
Event
+
UserChoiceBillingAndroid
+
DeveloperProvidedBillingAndroid
+
+
+
Setup
+
AlternativeBillingModeAndroid.UserChoice
+
enableBillingProgram(EXTERNAL_PAYMENTS) + developerBillingOption in requestPurchase
+
+
+
+
+
+
+ ⚠️ Important: The external transaction token MUST
+ be reported to Google Play within 24 hours using the{' '}
+ externaltransactions.createexternaltransaction API.
+ Failure to report tokens may result in account suspension.
+
diff --git a/packages/docs/src/pages/docs/features/external-purchase.tsx b/packages/docs/src/pages/docs/features/external-purchase.tsx
index 343906dd..9f7e828f 100644
--- a/packages/docs/src/pages/docs/features/external-purchase.tsx
+++ b/packages/docs/src/pages/docs/features/external-purchase.tsx
@@ -59,7 +59,7 @@ function ExternalPurchase() {
Android
Alternative Billing / Billing Programs
Android 6.0+ (API 23)
-
Google Play Billing 6.2+ (legacy), 8.2.0+ (recommended)
+
Google Play Billing 6.2+ (legacy), 8.2.0+ (recommended), 8.3.0+ (External Payments)
@@ -781,7 +781,7 @@ Future handleAlternativeBillingPurchase(String productId) async {
initConnection,
requestPurchase,
purchaseUpdatedListener,
- userChoiceBillingListener,
+ userChoiceBillingListenerAndroid,
type UserChoiceBillingDetails,
type Purchase,
} from 'expo-iap';
@@ -792,7 +792,7 @@ await initConnection({
});
// Set user choice billing listener (for alternative billing selection)
-const userChoiceSubscription = userChoiceBillingListener(
+const userChoiceSubscription = userChoiceBillingListenerAndroid(
async (details: UserChoiceBillingDetails) => {
console.log('User selected alternative billing');
console.log('Token:', details.externalTransactionToken);
@@ -836,7 +836,7 @@ async function handleUserChoicePurchase(productId: string) {
});
// If user selects Google Play → purchaseUpdatedListener callback
- // If user selects alternative → userChoiceBillingListener callback
+ // If user selects alternative → userChoiceBillingListenerAndroid callback
} catch (error) {
console.error('Purchase error:', error);
}
@@ -1257,6 +1257,387 @@ Future handleExternalPurchaseWithBillingPrograms(String productId) async {
+
External Payments (8.3.0+ - Japan Only)
+
+ Google Play Billing Library 8.3.0 introduces the{' '}
+ External Payments program, currently available
+ only in Japan. This presents a side-by-side choice between Google
+ Play Billing and the developer's external payment option directly
+ in the purchase flow.
+
+
+
+
+ ℹ️ Availability: The External Payments program
+ is currently only available in Japan. Users in other regions
+ will not see the developer billing option. Check{' '}
+ isBillingProgramAvailable(EXTERNAL_PAYMENTS) to
+ verify availability.
+
+
+
+
Key Differences from Other Programs
+
+
+ Side-by-side choice: User sees both Google Play
+ and developer payment options in the same purchase dialog
+
+
+ DeveloperProvidedBillingListener: New callback
+ when user selects developer billing (different from UserChoiceBillingListener)
+
+
+ DeveloperBillingOptionParams: Configure the
+ developer billing option in BillingFlowParams
+
+
+ EnableBillingProgramParams: Required to enable
+ EXTERNAL_PAYMENTS with DeveloperProvidedBillingListener
+
+
+
+
+ {{
+ typescript: (
+ {`import {
+ initConnection,
+ enableBillingProgramAndroid,
+ isBillingProgramAvailableAndroid,
+ requestPurchase,
+ createBillingProgramReportingDetailsAndroid,
+ developerProvidedBillingListenerAndroid,
+ type DeveloperProvidedBillingDetails,
+} from 'expo-iap';
+
+// Step 0: Enable External Payments program BEFORE initConnection
+enableBillingProgramAndroid('EXTERNAL_PAYMENTS');
+
+await initConnection();
+
+// Step 1: Set up listener for when user selects developer billing
+const developerBillingSubscription = developerProvidedBillingListenerAndroid(
+ async (details: DeveloperProvidedBillingDetails) => {
+ console.log('User selected developer billing');
+ console.log('External transaction token:', details.externalTransactionToken);
+
+ try {
+ // Step 2: Process payment with your backend
+ const paymentResult = await yourBackend.createPayment({
+ productId: currentProductId,
+ userId,
+ amount: productPrice,
+ });
+
+ if (paymentResult.success) {
+ // Step 3: Report the external transaction token to Google
+ // This must be done within 24 hours
+ await yourBackend.reportExternalTransaction({
+ token: details.externalTransactionToken,
+ orderId: paymentResult.orderId,
+ productId: currentProductId,
+ });
+
+ console.log('External payment completed and reported!');
+ }
+ } catch (error) {
+ console.error('External payment error:', error);
+ }
+ }
+);
+
+// Purchase flow with External Payments
+async function handlePurchaseWithExternalPayments(productId: string) {
+ try {
+ // Check if External Payments is available (Japan only)
+ const result = await isBillingProgramAvailableAndroid('EXTERNAL_PAYMENTS');
+ if (!result.isAvailable) {
+ console.log('External Payments not available (not in Japan)');
+ // Fall back to standard Google Play purchase
+ return;
+ }
+
+ // Launch purchase with developer billing option
+ // User will see side-by-side choice dialog
+ await requestPurchase({
+ google: {
+ skus: [productId],
+ developerBillingOption: {
+ billingProgram: 'EXTERNAL_PAYMENTS',
+ linkUri: 'https://your-payment-site.com/checkout',
+ launchMode: 'LAUNCH_IN_EXTERNAL_BROWSER_OR_APP',
+ },
+ },
+ });
+
+ // If user selects Google Play → purchaseUpdatedListener callback
+ // If user selects developer billing → developerProvidedBillingListenerAndroid callback
+ } catch (error) {
+ console.error('Purchase error:', error);
+ }
+}
+
+// Clean up when done
+// developerBillingSubscription.remove();`}
+ ),
+ kotlin: (
+ {`import dev.hyo.openiap.store.OpenIapStore
+import dev.hyo.openiap.BillingProgramAndroid
+import dev.hyo.openiap.DeveloperBillingOptionParamsAndroid
+import dev.hyo.openiap.DeveloperBillingLaunchModeAndroid
+import dev.hyo.openiap.DeveloperProvidedBillingDetailsAndroid
+import dev.hyo.openiap.RequestPurchaseProps
+import dev.hyo.openiap.ProductQueryType
+
+// Initialize store
+val iapStore = OpenIapStore(context = applicationContext)
+
+// Step 0: Enable External Payments program BEFORE initConnection
+iapStore.enableBillingProgram(BillingProgramAndroid.ExternalPayments)
+
+iapStore.initConnection(null)
+
+// Step 1: Set up listener for when user selects developer billing
+iapStore.addDeveloperProvidedBillingListener { details ->
+ Log.d("IAP", "User selected developer billing")
+ Log.d("IAP", "External transaction token: \${details.externalTransactionToken}")
+
+ // Step 2: Process payment with your backend
+ lifecycleScope.launch {
+ try {
+ val paymentResult = yourBackend.createPayment(
+ productId = currentProductId,
+ userId = userId,
+ amount = productPrice
+ )
+
+ if (paymentResult.success) {
+ // Step 3: Report the external transaction token to Google
+ // This must be done within 24 hours
+ yourBackend.reportExternalTransaction(
+ token = details.externalTransactionToken,
+ orderId = paymentResult.orderId,
+ productId = currentProductId
+ )
+
+ Log.d("IAP", "External payment completed and reported!")
+ }
+ } catch (e: Exception) {
+ Log.e("IAP", "External payment error: \${e.message}")
+ }
+ }
+}
+
+// Purchase flow with External Payments
+suspend fun handlePurchaseWithExternalPayments(productId: String) {
+ try {
+ // Check if External Payments is available (Japan only)
+ val result = iapStore.isBillingProgramAvailable(
+ BillingProgramAndroid.ExternalPayments
+ )
+ if (!result.isAvailable) {
+ Log.w("IAP", "External Payments not available (not in Japan)")
+ // Fall back to standard Google Play purchase
+ return
+ }
+
+ iapStore.setActivity(activity)
+
+ // Launch purchase with developer billing option
+ // User will see side-by-side choice dialog
+ val props = RequestPurchaseProps(
+ request = RequestPurchaseProps.Request.Purchase(
+ RequestPurchasePropsByPlatforms(
+ google = RequestPurchaseAndroidProps(
+ skus = listOf(productId),
+ developerBillingOption = DeveloperBillingOptionParamsAndroid(
+ billingProgram = BillingProgramAndroid.ExternalPayments,
+ linkUri = "https://your-payment-site.com/checkout",
+ launchMode = DeveloperBillingLaunchModeAndroid.LaunchInExternalBrowserOrApp
+ )
+ )
+ )
+ ),
+ type = ProductQueryType.InApp
+ )
+
+ iapStore.requestPurchase(props)
+
+ // If user selects Google Play → onPurchaseSuccess callback
+ // If user selects developer billing → DeveloperProvidedBillingListener callback
+ } catch (e: Exception) {
+ Log.e("IAP", "Purchase error: \${e.message}")
+ }
+}`}
+ ),
+ dart: (
+ {`import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+
+// Step 0: Enable External Payments program BEFORE initConnection
+FlutterInappPurchase.instance.enableBillingProgramAndroid(
+ BillingProgramAndroid.externalPayments,
+);
+
+await FlutterInappPurchase.instance.initConnection();
+
+// Step 1: Set up listener for when user selects developer billing
+FlutterInappPurchase.developerProvidedBillingStream.listen((details) async {
+ print('User selected developer billing');
+ print('External transaction token: \${details.externalTransactionToken}');
+
+ try {
+ // Step 2: Process payment with your backend
+ final paymentResult = await yourBackend.createPayment(
+ productId: currentProductId,
+ userId: userId,
+ amount: productPrice,
+ );
+
+ if (paymentResult.success) {
+ // Step 3: Report the external transaction token to Google
+ // This must be done within 24 hours
+ await yourBackend.reportExternalTransaction(
+ token: details.externalTransactionToken,
+ orderId: paymentResult.orderId,
+ productId: currentProductId,
+ );
+
+ print('External payment completed and reported!');
+ }
+ } catch (e) {
+ print('External payment error: \$e');
+ }
+});
+
+// Purchase flow with External Payments
+Future handlePurchaseWithExternalPayments(String productId) async {
+ try {
+ // Check if External Payments is available (Japan only)
+ final result = await FlutterInappPurchase.instance
+ .isBillingProgramAvailableAndroid(BillingProgramAndroid.externalPayments);
+ if (!result.isAvailable) {
+ print('External Payments not available (not in Japan)');
+ // Fall back to standard Google Play purchase
+ return;
+ }
+
+ // Launch purchase with developer billing option
+ // User will see side-by-side choice dialog
+ await FlutterInappPurchase.instance.requestPurchase(
+ productId,
+ developerBillingOption: DeveloperBillingOptionParamsAndroid(
+ billingProgram: BillingProgramAndroid.externalPayments,
+ linkUri: 'https://your-payment-site.com/checkout',
+ launchMode: DeveloperBillingLaunchModeAndroid.launchInExternalBrowserOrApp,
+ ),
+ );
+
+ // If user selects Google Play → purchaseUpdatedStream callback
+ // If user selects developer billing → developerProvidedBillingStream callback
+ } catch (e) {
+ print('Purchase error: \$e');
+ }
+}`}
+ ),
+ }}
+
+
+
External Payments Flow Diagram
+
+
+
+
Step
+
API / Action
+
Description
+
+
+
+
+
0
+
enableBillingProgram(EXTERNAL_PAYMENTS)
+
Enable External Payments program BEFORE initConnection
+
+
+
1
+
addDeveloperProvidedBillingListener()
+
Register callback for when user selects developer billing
+
+
+
2
+
isBillingProgramAvailable(EXTERNAL_PAYMENTS)
+
Check if available (Japan only)
+
+
+
3
+
requestPurchase(developerBillingOption: ...)
+
Launch purchase with developer billing option configured
+
+
+
4
+
User Choice Dialog
+
User sees side-by-side choice: Google Play or Developer Billing
+
+
+
5a
+
If Google Play: onPurchaseSuccess
+
Normal Google Play purchase flow
+
+
+
5b
+
If Developer: DeveloperProvidedBillingListener
+
Callback receives externalTransactionToken
+
+
+
6
+
Process Payment
+
Process payment with your payment gateway
+
+
+
7
+
Report Token
+
Report externalTransactionToken to Google within 24 hours
+
+
+
+
+
+
+ ⚠️ Token Reporting Requirement: When a user
+ completes a purchase through developer billing, you{' '}
+ must report the externalTransactionToken to
+ Google Play within 24 hours. Failure to report may result in
+ account suspension. Use the Google Play Developer API's{' '}
+ externaltransactions.createexternaltransaction{' '}
+ endpoint to report the token.
+
+
+
+
DeveloperBillingLaunchMode Options
+
+
+
+
Mode
+
Behavior
+
+
+
+
+
LAUNCH_IN_EXTERNAL_BROWSER_OR_APP
+
+ Google Play will launch the linkUri in an external browser
+ or eligible app. Use this for web-based payment flows.
+
+
+
+
CALLER_WILL_LAUNCH_LINK
+
+ Google Play returns control to your app without launching.
+ Your app handles launching the payment flow. Use this for
+ in-app payment experiences.
+
InitConnectionConfig - Configuration for
initConnection()
@@ -123,7 +131,7 @@ function TypesAlternative() {
- Usage Example
+ Basic Usage
{{
@@ -183,6 +191,642 @@ await FlutterInappPurchase.instance.initConnection();`}
),
}}
+
+
+ User Choice Billing Complete Example
+
+
+ With User Choice Billing (7.0+), users see a dialog to choose between
+ Google Play or your alternative payment. Handle both paths:
+
+
+ {{
+ typescript: (
+ {`import {
+ initConnection,
+ userChoiceBillingListenerAndroid,
+ fetchProducts,
+ requestPurchase,
+ createAlternativeBillingToken,
+} from 'expo-iap';
+
+// Step 1: Set up listener for when user selects alternative billing
+const userChoiceSubscription = userChoiceBillingListenerAndroid(async (details) => {
+ console.log('User chose alternative billing');
+ console.log('Products:', details.products.map(p => p.productId));
+ console.log('External Transaction Token:', details.externalTransactionToken);
+
+ // Process payment with your backend using the token
+ const paymentResult = await yourBackend.processPayment({
+ products: details.products,
+ token: details.externalTransactionToken,
+ });
+
+ if (paymentResult.success) {
+ grantUserAccess();
+ }
+});
+
+// Step 2: Initialize with user choice billing
+await initConnection({
+ alternativeBillingModeAndroid: 'user-choice',
+});
+
+// Step 3: Fetch products and purchase as normal
+const products = await fetchProducts({
+ request: { skus: ['premium_subscription'] },
+ type: 'subs',
+});
+
+// Step 4: Request purchase - dialog will show both options
+await requestPurchase({
+ request: {
+ google: { skus: ['premium_subscription'] },
+ },
+ type: 'subs',
+});
+
+// If user selects Google Play → purchaseUpdatedListener fires
+// If user selects alternative → userChoiceBillingListenerAndroid fires
+
+// Cleanup
+userChoiceSubscription.remove();`}
+ ),
+ kotlin: (
+ {`import dev.hyo.openiap.store.OpenIapStore
+import dev.hyo.openiap.InitConnectionConfig
+import dev.hyo.openiap.AlternativeBillingModeAndroid
+import dev.hyo.openiap.listener.OpenIapUserChoiceBillingListener
+
+val iapStore = OpenIapStore(context)
+
+// Step 1: Set up listener for when user selects alternative billing
+iapStore.addUserChoiceBillingListener(object : OpenIapUserChoiceBillingListener {
+ override fun onUserChoiceBilling(details: UserChoiceBillingDetails) {
+ Log.d("IAP", "User chose alternative billing")
+ Log.d("IAP", "Products: \${details.products.map { it.productId }}")
+ Log.d("IAP", "Token: \${details.externalTransactionToken}")
+
+ // Process payment with your backend using the token
+ lifecycleScope.launch {
+ val paymentResult = yourBackend.processPayment(
+ products = details.products,
+ token = details.externalTransactionToken
+ )
+
+ if (paymentResult.success) {
+ grantUserAccess()
+ }
+ }
+ }
+})
+
+// Step 2: Initialize with user choice billing
+iapStore.initConnection(
+ InitConnectionConfig(
+ alternativeBillingModeAndroid = AlternativeBillingModeAndroid.UserChoice
+ )
+)
+
+// Step 3: Fetch products and purchase as normal
+val products = iapStore.fetchProducts(
+ skus = listOf("premium_subscription"),
+ type = ProductQueryType.Subs
+)
+
+// Step 4: Request purchase - dialog will show both options
+iapStore.setActivity(activity)
+iapStore.requestPurchase(
+ RequestPurchaseProps(
+ request = RequestPurchaseProps.Request.Subscription(
+ RequestSubscriptionPropsByPlatforms(
+ google = RequestSubscriptionAndroidProps(
+ skus = listOf("premium_subscription")
+ )
+ )
+ ),
+ type = ProductQueryType.Subs
+ )
+)
+
+// If user selects Google Play → onPurchaseSuccess fires
+// If user selects alternative → OpenIapUserChoiceBillingListener fires`}
+ ),
+ dart: (
+ {`import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+
+// Step 1: Set up listener for when user selects alternative billing
+final userChoiceSubscription = FlutterInappPurchase.userChoiceBillingStream
+ .listen((details) async {
+ print('User chose alternative billing');
+ print('Products: \${details.products.map((p) => p.productId).toList()}');
+ print('Token: \${details.externalTransactionToken}');
+
+ // Process payment with your backend using the token
+ final paymentResult = await yourBackend.processPayment(
+ products: details.products,
+ token: details.externalTransactionToken,
+ );
+
+ if (paymentResult.success) {
+ grantUserAccess();
+ }
+});
+
+// Step 2: Initialize with user choice billing
+await FlutterInappPurchase.instance.initConnection(
+ alternativeBillingModeAndroid: AlternativeBillingModeAndroid.UserChoice,
+);
+
+// Step 3: Fetch products and purchase as normal
+final products = await FlutterInappPurchase.instance.getSubscriptions(
+ ['premium_subscription'],
+);
+
+// Step 4: Request purchase - dialog will show both options
+await FlutterInappPurchase.instance.requestSubscription(
+ sku: 'premium_subscription',
+);
+
+// If user selects Google Play → purchaseUpdatedStream fires
+// If user selects alternative → userChoiceBillingStream fires
+
+// Cleanup
+userChoiceSubscription.cancel();`}
+ ),
+ }}
+
+
+
+ Alternative Billing Only Complete Example
+
+
+ With Alternative Only mode (6.2+), all purchases go through your
+ alternative payment system. Google Play is not shown:
+
+
+ {{
+ typescript: (
+ {`import {
+ initConnection,
+ fetchProducts,
+ checkAlternativeBillingAvailability,
+ showAlternativeBillingDialog,
+ createAlternativeBillingToken,
+} from 'expo-iap';
+
+// Step 1: Initialize with alternative billing only
+await initConnection({
+ alternativeBillingModeAndroid: 'alternative-only',
+});
+
+// Step 2: Check if alternative billing is available
+const availability = await checkAlternativeBillingAvailability();
+if (!availability.isAvailable) {
+ console.log('Alternative billing not available in this region');
+ // Fall back to standard Google Play billing
+ return;
+}
+
+// Step 3: Fetch products (still needed to show prices)
+const products = await fetchProducts({
+ request: { skus: ['premium_subscription'] },
+ type: 'subs',
+});
+
+// Step 4: Show required Google Play disclosure dialog
+const dialogResult = await showAlternativeBillingDialog();
+if (dialogResult.responseCode !== 0) {
+ console.log('User did not accept alternative billing');
+ return;
+}
+
+// Step 5: Create token for this transaction
+const token = await createAlternativeBillingToken(products[0].id);
+
+// Step 6: Process purchase with your backend
+const paymentResult = await yourBackend.processAlternativePurchase({
+ productId: products[0].id,
+ price: products[0].price,
+ token: token,
+ userId: currentUserId,
+});
+
+if (paymentResult.success) {
+ // Report transaction to Google (required)
+ await yourBackend.reportExternalTransaction(token, paymentResult.orderId);
+ grantUserAccess();
+}`}
+ ),
+ kotlin: (
+ {`import dev.hyo.openiap.store.OpenIapStore
+import dev.hyo.openiap.InitConnectionConfig
+import dev.hyo.openiap.AlternativeBillingModeAndroid
+
+val iapStore = OpenIapStore(context)
+
+// Step 1: Initialize with alternative billing only
+iapStore.initConnection(
+ InitConnectionConfig(
+ alternativeBillingModeAndroid = AlternativeBillingModeAndroid.AlternativeOnly
+ )
+)
+
+// Step 2: Check if alternative billing is available
+val availability = iapStore.checkAlternativeBillingAvailability()
+if (!availability.isAvailable) {
+ Log.w("IAP", "Alternative billing not available in this region")
+ // Fall back to standard Google Play billing
+ return
+}
+
+// Step 3: Fetch products (still needed to show prices)
+val products = iapStore.fetchProducts(
+ skus = listOf("premium_subscription"),
+ type = ProductQueryType.Subs
+)
+
+// Step 4: Show required Google Play disclosure dialog
+iapStore.setActivity(activity)
+val dialogResult = iapStore.showAlternativeBillingDialog()
+if (dialogResult.responseCode != 0) {
+ Log.d("IAP", "User did not accept alternative billing")
+ return
+}
+
+// Step 5: Create token for this transaction
+val token = iapStore.createAlternativeBillingToken(products.first().id)
+
+// Step 6: Process purchase with your backend
+lifecycleScope.launch {
+ val paymentResult = yourBackend.processAlternativePurchase(
+ productId = products.first().id,
+ price = products.first().price,
+ token = token,
+ userId = currentUserId
+ )
+
+ if (paymentResult.success) {
+ // Report transaction to Google (required)
+ yourBackend.reportExternalTransaction(token, paymentResult.orderId)
+ grantUserAccess()
+ }
+}`}
+ ),
+ dart: (
+ {`import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+
+final iap = FlutterInappPurchase.instance;
+
+// Step 1: Initialize with alternative billing only
+await iap.initConnection(
+ alternativeBillingModeAndroid: AlternativeBillingModeAndroid.AlternativeOnly,
+);
+
+// Step 2: Check if alternative billing is available
+final availability = await iap.checkAlternativeBillingAvailability();
+if (!availability.isAvailable) {
+ print('Alternative billing not available in this region');
+ // Fall back to standard Google Play billing
+ return;
+}
+
+// Step 3: Fetch products (still needed to show prices)
+final products = await iap.getSubscriptions(['premium_subscription']);
+
+// Step 4: Show required Google Play disclosure dialog
+final dialogResult = await iap.showAlternativeBillingDialog();
+if (dialogResult.responseCode != 0) {
+ print('User did not accept alternative billing');
+ return;
+}
+
+// Step 5: Create token for this transaction
+final token = await iap.createAlternativeBillingToken(products.first.productId);
+
+// Step 6: Process purchase with your backend
+final paymentResult = await yourBackend.processAlternativePurchase(
+ productId: products.first.productId,
+ price: products.first.price,
+ token: token,
+ userId: currentUserId,
+);
+
+if (paymentResult.success) {
+ // Report transaction to Google (required)
+ await yourBackend.reportExternalTransaction(token, paymentResult.orderId);
+ grantUserAccess();
+}`}
+ ),
+ }}
+
+
+
+ Reporting Requirement:
+
+ For both User Choice and Alternative Only modes, you must report
+ completed transactions to Google Play within 24 hours using the
+ Google Play Developer API. Failure to report may result in account
+ suspension.
+
+
+
+
+
+
+ Billing Programs (Android 8.2.0+)
+
+
+ Google Play Billing Library 8.2.0+ introduces the Billing Programs API,
+ which provides a more structured approach to external offers and
+ content links. Version 8.3.0 adds External Payments for Japan.
+
+
+
+ BillingProgramAndroid
+
+
+ Enum for different billing program types:
+
+
+
+
+
Name
+
Summary
+
Version
+
+
+
+
+
+ EXTERNAL_CONTENT_LINK
+
+
+ For apps that link to external content (reader apps, music streaming)
+
+
8.2.0+
+
+
+
+ EXTERNAL_OFFER
+
+
+ For apps offering alternative payment options
+
+
8.2.0+
+
+
+
+ EXTERNAL_PAYMENTS
+
+
+ Side-by-side choice between Google Play and developer billing (Japan only)
+
+
8.3.0+
+
+
+
+
+
+ DeveloperBillingOptionParamsAndroid
+
+
+ Parameters for configuring developer billing option in purchase flow (8.3.0+):
+
+
+
+
+
Name
+
Type
+
Summary
+
+
+
+
+
+ billingProgram
+
+
+ BillingProgramAndroid
+
+
+ The billing program (usually EXTERNAL_PAYMENTS)
+
+
+
+
+ linkUri
+
+
+ String
+
+
+ URL where the external payment will be processed
+
+
+
+
+ launchMode
+
+
+ DeveloperBillingLaunchModeAndroid
+
+
+ How to launch the external payment link
+
+
+
+
+
+
+ DeveloperBillingLaunchModeAndroid
+
+
+ How the external payment URL is launched:
+
+
+
+
+
Name
+
Summary
+
+
+
+
+
+ LAUNCH_IN_EXTERNAL_BROWSER_OR_APP
+
+
+ Google Play launches the link in a browser or eligible app
+
+
+
+
+ CALLER_WILL_LAUNCH_LINK
+
+
+ Your app handles launching the link after Play returns control
+
+
+
+
+
+
+ DeveloperProvidedBillingDetailsAndroid
+
+
+ Details received when user selects developer billing (8.3.0+):
+
+
+
+
+
Name
+
Type
+
Summary
+
+
+
+
+
+ externalTransactionToken
+
+
+ String
+
+
+ Token to report external transaction to Google (must report within 24 hours)
+
+ Token Reporting: When a user completes a purchase
+ through developer billing, you must report the{' '}
+ externalTransactionToken to Google Play within 24 hours.
+ See{' '}
+
+ External Payments documentation
+ {' '}
+ for complete implementation details.
+
+
diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx
index 886c41fc..e7605139 100644
--- a/packages/docs/src/pages/docs/updates/notes.tsx
+++ b/packages/docs/src/pages/docs/updates/notes.tsx
@@ -23,6 +23,145 @@ function Notes() {
useScrollToHash();
const allNotes: Note[] = [
+ // External Payments 8.3.0 Support - Dec 27, 2025
+ {
+ id: 'external-payments-830',
+ date: new Date('2025-12-27'),
+ element: (
+
+ Google Play Billing Library 8.3.0 introduces the External Payments
+ program, which presents a side-by-side choice between Google Play
+ Billing and the developer's external payment option directly in the
+ purchase flow.
+
+
+ New APIs:
+
+
+
+
+ BillingProgramAndroid.EXTERNAL_PAYMENTS
+ {' '}
+ - New billing program type for external payments
+