diff --git a/CHANGELOG.md b/CHANGELOG.md index 45286e42..53b7bb2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,201 @@ All notable changes to the OpenIAP monorepo will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [openiap-gql 1.3.2] - 2025-12-11 + +### Added + +#### Google Play Billing Library 8.1.0 Support + +- **`SubscriptionProductReplacementParamsAndroid`**: New type for per-product subscription replacement configuration + - `oldProductId`: The product ID being replaced + - `replacementMode`: The replacement mode enum value +- **`SubscriptionReplacementModeAndroid.KeepExisting`**: New replacement mode (8.1.0+) to keep the existing payment schedule unchanged + +#### Google Play Billing Library 8.2.0 Support (Billing Programs API) + +- **`BillingProgramAndroid`**: Enum for billing program types + - `ExternalContentLink`: For apps linking to external content (reader apps, music streaming) + - `ExternalOffer`: For apps offering alternative payment options + - `Unspecified`: Default/unspecified value +- **`BillingProgramAvailabilityResultAndroid`**: Result type for billing program availability checks + - `billingProgram`: The program that was checked + - `isAvailable`: Whether the program is available +- **`BillingProgramReportingDetailsAndroid`**: Reporting details for external transactions + - `billingProgram`: The billing program used + - `externalTransactionToken`: Token for reporting to Google Play +- **`LaunchExternalLinkParamsAndroid`**: Parameters for launching external links + - `billingProgram`: Which billing program to use + - `launchMode`: How to launch the link + - `linkType`: Type of external link + - `linkUri`: The URI to launch +- **`ExternalLinkLaunchModeAndroid`**: Enum for external link launch modes + - `LaunchInExternalBrowserOrApp`: Launch in external browser or app + - `CallerWillLaunchLink`: Caller handles the link launch + - `Unspecified`: Default value +- **`ExternalLinkTypeAndroid`**: Enum for external link types + - `LinkToDigitalContentOffer`: Link to digital content offer + - `LinkToAppDownload`: Link to app download + - `Unspecified`: Default value + +### Changed + +- Updated `RequestSubscriptionAndroidProps` to include optional `subscriptionProductReplacementParams` field + +--- + +## [openiap-google 1.3.12] - 2025-12-11 + +### Added + +#### Google Play Billing Library 8.1.0 APIs + +- **`applySubscriptionProductReplacementParams()`**: Apply per-product replacement params to subscription upgrades/downgrades + - Enables `KeepExisting` replacement mode (only available via this API) + - More granular control over subscription replacements at the product level + +#### Google Play Billing Library 8.2.0 APIs (Billing Programs) + +- **`enableBillingProgram(program: BillingProgramAndroid)`**: Enable a billing program before `initConnection()` + - Must be called before connecting to configure the BillingClient + - Available via both `OpenIapModule` and `OpenIapStore` +- **`isBillingProgramAvailable(program: BillingProgramAndroid)`**: Check if a billing program is available + - Replaces deprecated `checkAlternativeBillingAvailability()` for external offers + - Returns `BillingProgramAvailabilityResultAndroid` with availability status +- **`createBillingProgramReportingDetails(program: BillingProgramAndroid)`**: Create reporting details for external transactions + - Replaces deprecated `createAlternativeBillingReportingToken()` + - Returns `BillingProgramReportingDetailsAndroid` with `externalTransactionToken` +- **`launchExternalLink(activity: Activity, params: LaunchExternalLinkParamsAndroid)`**: Launch external link for external offers + - Replaces deprecated `showAlternativeBillingInformationDialog()` + - Supports configurable launch modes and link types + +### Deprecated + +The following APIs are deprecated in favor of the new Billing Programs API (8.2.0+): + +- `checkAlternativeBillingAvailability()` → Use `isBillingProgramAvailable(BillingProgramAndroid.ExternalOffer)` +- `showAlternativeBillingInformationDialog()` → Use `launchExternalLink(activity, params)` +- `createAlternativeBillingReportingToken()` → Use `createBillingProgramReportingDetails(BillingProgramAndroid.ExternalOffer)` + +### Changed + +- **Example App (`AlternativeBillingScreen`)**: Updated to demonstrate all three billing modes: + - Billing Programs (8.2.0+) - Recommended + - Alternative Billing Only (Legacy 6.2+) + - User Choice Billing (Legacy 7.0+) +- **Error Handling**: Improved exception propagation in Proxy handlers using `resumeWithException()` +- **Null Safety**: Added null-safe activity handling to prevent potential NPE + +### Fixed + +- Empty catch blocks now properly log errors and display status messages to users +- Exception handling in coroutine Proxy handlers now correctly propagates exceptions + +### Documentation + +- Updated `external-purchase.tsx` with Billing Programs API (8.2.0+) documentation +- Added API Migration Guide table for legacy to new API mapping +- Updated Implementation Flow section with new step-by-step guide +- Added code examples for TypeScript, Kotlin, and Dart + +--- + +## [openiap-gql 1.3.1 / openiap-google 1.3.11] - 2025-12-10 + +### Added + +#### One-Time Purchase Discount Offers (Google Play Billing 7.0+) + +- **`oneTimePurchaseOfferDetailsAndroid`**: Changed from single object to array to support multiple discount offers +- **`DiscountDisplayInfoAndroid`**: Discount display information + - `discountPercent`: Percentage off + - `discountAmount`: Discount amount details +- **`DiscountAmountAndroid`**: Discount amount with currency +- **`ValidTimeWindowAndroid`**: Start and end time for limited-time offers +- **`LimitedQuantityInfoAndroid`**: Limited quantity information +- **`RentalDetailsAndroid`**: Rental product metadata + +#### Google Play Billing 8.1.0 Support + +- **`PreorderDetailsAndroid`**: Pre-order product details + - `preorderPresaleEndTimeMillis`: Presale end time + - `preorderReleaseTimeMillis`: Release time +- **`isSuspendedAndroid`**: Detect suspended subscriptions due to payment failures + +### Changed + +- **Upgraded Google Play Billing Library**: 8.0.0 → 8.1.0 +- **Increased minSdk**: 21 → 23 (Android 6.0) +- **Upgraded Kotlin**: 2.0.21 → 2.2.0 + +### Breaking Changes + +- **`oneTimePurchaseOfferDetailsAndroid`** type changed from single object to array + - Before: `product.oneTimePurchaseOfferDetailsAndroid?.formattedPrice` + - After: `product.oneTimePurchaseOfferDetailsAndroid?.firstOrNull()?.formattedPrice` + +### Documentation + +- Added comprehensive "Discounts (Android)" documentation +- Updated types page with new Android fields + +--- + +## [openiap-gql 1.3.0 / openiap-google 1.3.0 / openiap-apple 1.3.0] - 2025-12-08 + +### Added + +- **`IapStore` enum**: Unified store identification + - `Unknown`: Unknown store + - `Apple`: Apple App Store + - `Google`: Google Play Store + - `Horizon`: Meta Horizon Store +- **`store` field**: Added to `PurchaseCommon` interface for consistent store identification +- **`verifyPurchaseWithProvider()`**: Server-side purchase verification API + +### Changed + +- **Renamed request props**: `ios`/`android` → `apple`/`google` in request payloads + - `RequestPurchasePropsByPlatforms`: `ios` → `apple`, `android` → `google` + - `RequestSubscriptionPropsByPlatforms`: `ios` → `apple`, `android` → `google` + +### Deprecated + +- **`platform` field**: Use `store` field instead +- **`ios`/`android` props**: Use `apple`/`google` props in request payloads + +### Migration Guide + +**Request props migration:** +```typescript +// Before (deprecated) +requestPurchase({ + request: { + ios: { sku: 'product_id' }, + android: { skus: ['product_id'] } + } +}); + +// After (recommended) +requestPurchase({ + request: { + apple: { sku: 'product_id' }, + google: { skus: ['product_id'] } + } +}); +``` + +**Platform field migration:** +```typescript +// Before (deprecated) +if (purchase.platform === 'ios') { ... } + +// After (recommended) +if (purchase.store === 'Apple') { ... } +``` + +--- + ## [1.2.2] - 2025-10-16 ### Added diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift index 1d17f520..e4681362 100644 --- a/packages/apple/Sources/Models/Types.swift +++ b/packages/apple/Sources/Models/Types.swift @@ -20,6 +20,19 @@ 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+ +public enum BillingProgramAndroid: String, Codable, CaseIterable { + /// Unspecified billing program. Do not use. + case unspecified = "unspecified" + /// External Content Links program. + /// Allows linking to external content outside the app. + case externalContentLink = "external-content-link" + /// External Offers program. + /// Allows offering digital content purchases outside the app. + case externalOffer = "external-offer" +} + public enum ErrorCode: String, Codable, CaseIterable { case unknown = "unknown" case userCancelled = "user-cancelled" @@ -144,6 +157,30 @@ public enum ErrorCode: String, Codable, CaseIterable { } } +/// Launch mode for external link flow (Android) +/// Determines how the external URL is launched +/// Available in Google Play Billing Library 8.2.0+ +public enum ExternalLinkLaunchModeAndroid: String, Codable, CaseIterable { + /// Unspecified launch mode. Do not use. + case unspecified = "unspecified" + /// Play will launch the URL in an external browser or eligible app + case launchInExternalBrowserOrApp = "launch-in-external-browser-or-app" + /// Play will not launch the URL. The app handles launching the URL after Play returns control. + case callerWillLaunchLink = "caller-will-launch-link" +} + +/// Link type for external link flow (Android) +/// Specifies the type of external link destination +/// Available in Google Play Billing Library 8.2.0+ +public enum ExternalLinkTypeAndroid: String, Codable, CaseIterable { + /// Unspecified link type. Do not use. + case unspecified = "unspecified" + /// The link will direct users to a digital content offer + case linkToDigitalContentOffer = "link-to-digital-content-offer" + /// The link will direct users to download an app + case linkToAppDownload = "link-to-app-download" +} + /// User actions on external purchase notice sheet (iOS 18.2+) public enum ExternalPurchaseNoticeAction: String, Codable, CaseIterable { /// User chose to continue to external purchase @@ -244,6 +281,26 @@ public enum SubscriptionPeriodIOS: String, Codable, CaseIterable { case empty = "empty" } +/// Replacement mode for subscription changes (Android) +/// These modes determine how the subscription replacement affects billing. +/// Available in Google Play Billing Library 8.1.0+ +public enum SubscriptionReplacementModeAndroid: String, Codable, CaseIterable { + /// Unknown replacement mode. Do not use. + case unknownReplacementMode = "unknown-replacement-mode" + /// Replacement takes effect immediately, and the new expiration time will be prorated. + case withTimeProration = "with-time-proration" + /// Replacement takes effect immediately, and the billing cycle remains the same. + case chargeProratedPrice = "charge-prorated-price" + /// Replacement takes effect immediately, and the user is charged full price immediately. + case chargeFullPrice = "charge-full-price" + /// Replacement takes effect when the old plan expires. + case withoutProration = "without-proration" + /// Replacement takes effect when the old plan expires, and the user is not charged. + case deferred = "deferred" + /// Keep the existing payment schedule unchanged for the item (8.1.0+) + case keepExisting = "keep-existing" +} + // MARK: - Interfaces public protocol ProductCommon: Codable { @@ -324,6 +381,26 @@ public struct AppTransaction: Codable { public var signedDate: Double } +/// Result of checking billing program availability (Android) +/// Available in Google Play Billing Library 8.2.0+ +public struct BillingProgramAvailabilityResultAndroid: Codable { + /// The billing program that was checked + public var billingProgram: BillingProgramAndroid + /// Whether the billing program is available for the user + public var isAvailable: Bool +} + +/// Reporting details for transactions made outside of Google Play Billing (Android) +/// Contains the external transaction token needed for reporting +/// Available in Google Play Billing Library 8.2.0+ +public struct BillingProgramReportingDetailsAndroid: Codable { + /// The billing program that the reporting details are associated with + public var billingProgram: BillingProgramAndroid + /// External transaction token used to report transactions made outside of Google Play Billing. + /// This token must be used when reporting the external transaction to Google. + 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 { @@ -374,6 +451,22 @@ public struct EntitlementIOS: Codable { public var transactionId: String } +/// External offer availability result (Android) +/// @deprecated Use BillingProgramAvailabilityResultAndroid with isBillingProgramAvailableAsync instead +/// Available in Google Play Billing Library 6.2.0+, deprecated in 8.2.0 +public struct ExternalOfferAvailabilityResultAndroid: Codable { + /// Whether external offers are available for the user + public var isAvailable: Bool +} + +/// External offer reporting details (Android) +/// @deprecated Use BillingProgramReportingDetailsAndroid with createBillingProgramReportingDetailsAsync instead +/// Available in Google Play Billing Library 6.2.0+, deprecated in 8.2.0 +public struct ExternalOfferReportingDetailsAndroid: Codable { + /// External transaction token for reporting external offer transactions + public var externalTransactionToken: String +} + /// Result of presenting an external purchase link (iOS 18.2+) public struct ExternalPurchaseLinkResultIOS: Codable { /// Optional error message if the presentation failed @@ -873,6 +966,32 @@ public struct InitConnectionConfig: Codable { } } +/// Parameters for launching an external link (Android) +/// Used with launchExternalLink to initiate external offer or app install flows +/// Available in Google Play Billing Library 8.2.0+ +public struct LaunchExternalLinkParamsAndroid: Codable { + /// The billing program (EXTERNAL_CONTENT_LINK or EXTERNAL_OFFER) + public var billingProgram: BillingProgramAndroid + /// The external link launch mode + public var launchMode: ExternalLinkLaunchModeAndroid + /// The type of the external link + public var linkType: ExternalLinkTypeAndroid + /// The URI where the content will be accessed from + public var linkUri: String + + public init( + billingProgram: BillingProgramAndroid, + launchMode: ExternalLinkLaunchModeAndroid, + linkType: ExternalLinkTypeAndroid, + linkUri: String + ) { + self.billingProgram = billingProgram + self.launchMode = launchMode + self.linkType = linkType + self.linkUri = linkUri + } +} + public struct ProductRequest: Codable { public var skus: [String] public var type: ProductQueryType? @@ -1056,11 +1175,15 @@ public struct RequestSubscriptionAndroidProps: Codable { /// Purchase token for upgrades/downgrades public var purchaseTokenAndroid: String? /// Replacement mode for subscription changes + /// @deprecated Use subscriptionProductReplacementParams instead for item-level replacement (8.1.0+) public var replacementModeAndroid: Int? /// List of subscription SKUs public var skus: [String] /// Subscription offers public var subscriptionOffers: [AndroidSubscriptionOfferInput]? + /// Product-level replacement parameters (8.1.0+) + /// Use this instead of replacementModeAndroid for item-level replacement + public var subscriptionProductReplacementParams: SubscriptionProductReplacementParamsAndroid? public init( isOfferPersonalized: Bool? = nil, @@ -1069,7 +1192,8 @@ public struct RequestSubscriptionAndroidProps: Codable { purchaseTokenAndroid: String? = nil, replacementModeAndroid: Int? = nil, skus: [String], - subscriptionOffers: [AndroidSubscriptionOfferInput]? = nil + subscriptionOffers: [AndroidSubscriptionOfferInput]? = nil, + subscriptionProductReplacementParams: SubscriptionProductReplacementParamsAndroid? = nil ) { self.isOfferPersonalized = isOfferPersonalized self.obfuscatedAccountIdAndroid = obfuscatedAccountIdAndroid @@ -1078,6 +1202,7 @@ public struct RequestSubscriptionAndroidProps: Codable { self.replacementModeAndroid = replacementModeAndroid self.skus = skus self.subscriptionOffers = subscriptionOffers + self.subscriptionProductReplacementParams = subscriptionProductReplacementParams } } @@ -1167,6 +1292,24 @@ public struct RequestVerifyPurchaseWithIapkitProps: Codable { } } +/// Product-level subscription replacement parameters (Android) +/// Used with setSubscriptionProductReplacementParams in BillingFlowParams.ProductDetailsParams +/// Available in Google Play Billing Library 8.1.0+ +public struct SubscriptionProductReplacementParamsAndroid: Codable { + /// The old product ID that needs to be replaced + public var oldProductId: String + /// The replacement mode for this product change + public var replacementMode: SubscriptionReplacementModeAndroid + + public init( + oldProductId: String, + replacementMode: SubscriptionReplacementModeAndroid + ) { + self.oldProductId = oldProductId + self.replacementMode = replacementMode + } +} + public struct VerifyPurchaseAndroidOptions: Codable { public var accessToken: String public var isSub: Bool? diff --git a/packages/docs/src/pages/docs/features/external-purchase.tsx b/packages/docs/src/pages/docs/features/external-purchase.tsx index 2ea8b28f..343906dd 100644 --- a/packages/docs/src/pages/docs/features/external-purchase.tsx +++ b/packages/docs/src/pages/docs/features/external-purchase.tsx @@ -57,9 +57,9 @@ function ExternalPurchase() { Android - Alternative Billing + Alternative Billing / Billing Programs Android 6.0+ (API 23) - Google Play Billing 6.2+ + Google Play Billing 6.2+ (legacy), 8.2.0+ (recommended) @@ -985,16 +985,292 @@ Future handleUserChoicePurchase(String productId) async { }} +
+

+ ⚠️ Deprecated APIs: The above APIs ( + checkAlternativeBillingAvailability,{' '} + showAlternativeBillingInformationDialog,{' '} + createAlternativeBillingReportingToken) are + deprecated in Google Play Billing Library 8.2.0+. For new + implementations, use the Billing Programs API{' '} + described below. +

+
+ +

Billing Programs API (8.2.0+)

+

+ Google Play Billing Library 8.2.0 introduces the new{' '} + Billing Programs API which replaces the legacy + alternative billing APIs. This provides better support for + External Content Links and External Offers. +

+ +
Program Types
+ + + + {{ + typescript: ( + {`import { + initConnection, + enableBillingProgramAndroid, + isBillingProgramAvailableAndroid, + createBillingProgramReportingDetailsAndroid, + launchExternalLinkAndroid, +} from 'expo-iap'; + +// Step 0: Enable billing program BEFORE initConnection +enableBillingProgramAndroid('EXTERNAL_OFFER'); +// or enableBillingProgramAndroid('EXTERNAL_CONTENT_LINK'); + +await initConnection(); + +// Purchase flow with Billing Programs API (8.2.0+) +async function handleExternalPurchaseWithBillingPrograms(productId: string) { + try { + // Step 1: Check if billing program is available + const result = await isBillingProgramAvailableAndroid('EXTERNAL_OFFER'); + if (!result.isAvailable) { + console.log('External offer program not available'); + return; + } + + // Step 2: Launch external link (replaces showAlternativeBillingDialog) + const launched = await launchExternalLinkAndroid({ + billingProgram: 'EXTERNAL_OFFER', + launchMode: 'LAUNCH_IN_EXTERNAL_BROWSER_OR_APP', + linkType: 'LINK_TO_DIGITAL_CONTENT_OFFER', + linkUri: 'https://your-payment-site.com/checkout', + }); + + if (!launched) { + console.log('Failed to launch external link'); + return; + } + + // Step 3: Process payment with your backend API + const paymentResult = await yourBackend.createPayment({ + productId, + userId, + amount: productPrice, + }); + + if (!paymentResult.success) { + console.log(\`Payment failed: \${paymentResult.error}\`); + return; + } + + // Step 4: Create reporting details (replaces createAlternativeBillingToken) + const reportingDetails = await createBillingProgramReportingDetailsAndroid('EXTERNAL_OFFER'); + console.log(\`Token created: \${reportingDetails.externalTransactionToken.slice(0, 20)}...\`); + + // Step 5: Send token to your backend server + await yourBackend.reportToken({ + token: reportingDetails.externalTransactionToken, + orderId: paymentResult.orderId, + productId, + }); + + console.log('Purchase completed!'); + } catch (error) { + console.error('Purchase error:', error); + } +}`} + ), + kotlin: ( + {`import dev.hyo.openiap.store.OpenIapStore +import dev.hyo.openiap.BillingProgramAndroid +import dev.hyo.openiap.LaunchExternalLinkParamsAndroid +import dev.hyo.openiap.ExternalLinkLaunchModeAndroid +import dev.hyo.openiap.ExternalLinkTypeAndroid + +// Initialize store +val iapStore = OpenIapStore(context = applicationContext) + +// Step 0: Enable billing program BEFORE initConnection +iapStore.enableBillingProgram(BillingProgramAndroid.ExternalOffer) +// or BillingProgramAndroid.ExternalContentLink + +iapStore.initConnection(null) + +// Purchase flow with Billing Programs API (8.2.0+) +suspend fun handleExternalPurchaseWithBillingPrograms(productId: String) { + try { + // Step 1: Check if billing program is available + val result = iapStore.isBillingProgramAvailable(BillingProgramAndroid.ExternalOffer) + if (!result.isAvailable) { + Log.e("IAP", "External offer program not available") + return + } + + // Step 2: Launch external link (replaces showAlternativeBillingDialog) + val launched = iapStore.launchExternalLink( + activity, + LaunchExternalLinkParamsAndroid( + billingProgram = BillingProgramAndroid.ExternalOffer, + launchMode = ExternalLinkLaunchModeAndroid.LaunchInExternalBrowserOrApp, + linkType = ExternalLinkTypeAndroid.LinkToDigitalContentOffer, + linkUri = "https://your-payment-site.com/checkout" + ) + ) + + if (!launched) { + Log.e("IAP", "Failed to launch external link") + return + } + + // Step 3: Process payment with your backend API + val paymentResult = yourBackend.createPayment( + productId = productId, + userId = userId, + amount = productPrice + ) + + if (!paymentResult.success) { + Log.e("IAP", "Payment failed: \${paymentResult.error}") + return + } + + // Step 4: Create reporting details (replaces createAlternativeBillingToken) + val reportingDetails = iapStore.createBillingProgramReportingDetails( + BillingProgramAndroid.ExternalOffer + ) + Log.d("IAP", "Token created: \${reportingDetails.externalTransactionToken.take(20)}...") + + // Step 5: Send token to your backend server + yourBackend.reportToken( + token = reportingDetails.externalTransactionToken, + orderId = paymentResult.orderId, + productId = productId + ) + + Log.d("IAP", "Purchase completed!") + } catch (e: Exception) { + Log.e("IAP", "Purchase error: \${e.message}") + } +}`} + ), + dart: ( + {`import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; + +// Step 0: Enable billing program BEFORE initConnection +FlutterInappPurchase.instance.enableBillingProgramAndroid( + BillingProgramAndroid.externalOffer, +); +// or BillingProgramAndroid.externalContentLink + +await FlutterInappPurchase.instance.initConnection(); + +// Purchase flow with Billing Programs API (8.2.0+) +Future handleExternalPurchaseWithBillingPrograms(String productId) async { + try { + // Step 1: Check if billing program is available + final result = await FlutterInappPurchase.instance + .isBillingProgramAvailableAndroid(BillingProgramAndroid.externalOffer); + if (!result.isAvailable) { + print('External offer program not available'); + return; + } + + // Step 2: Launch external link (replaces showAlternativeBillingDialog) + final launched = await FlutterInappPurchase.instance.launchExternalLinkAndroid( + LaunchExternalLinkParamsAndroid( + billingProgram: BillingProgramAndroid.externalOffer, + launchMode: ExternalLinkLaunchModeAndroid.launchInExternalBrowserOrApp, + linkType: ExternalLinkTypeAndroid.linkToDigitalContentOffer, + linkUri: 'https://your-payment-site.com/checkout', + ), + ); + + if (!launched) { + print('Failed to launch external link'); + return; + } + + // Step 3: Process payment with your backend API + final paymentResult = await yourBackend.createPayment( + productId: productId, + userId: userId, + amount: productPrice, + ); + + if (!paymentResult.success) { + print('Payment failed: \${paymentResult.error}'); + return; + } + + // Step 4: Create reporting details (replaces createAlternativeBillingToken) + final reportingDetails = await FlutterInappPurchase.instance + .createBillingProgramReportingDetailsAndroid(BillingProgramAndroid.externalOffer); + print('Token created: \${reportingDetails.externalTransactionToken.substring(0, 20)}...'); + + // Step 5: Send token to your backend server + await yourBackend.reportToken( + token: reportingDetails.externalTransactionToken, + orderId: paymentResult.orderId, + productId: productId, + ); + + print('Purchase completed!'); + } catch (e) { + print('Purchase error: $e'); + } +}`} + ), + }} + + +
API Migration Guide
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Legacy API (6.2+)New API (8.2.0+)
checkAlternativeBillingAvailability()isBillingProgramAvailable(program)
showAlternativeBillingInformationDialog()launchExternalLink(activity, params)
createAlternativeBillingReportingToken()createBillingProgramReportingDetails(program)
enableAlternativeBillingOnly()enableBillingProgram(program)
+

Requirements