Use this page with AI

Copy this into your coding assistant and add your request.

Read https://openiap.dev/docs/updates/migration and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.

My request: [describe what customers should be able to do]
See an example request →

Migration

One section per coordinated major train. Each train lists the versions that drop the previously deprecated, OpenIAP-owned compatibility surface and the canonical call to use instead.

Scheduled removals#

These deprecated keys and fields remain available in the community provider release train. Their removal moves to the versions below; shared Gradle flags are removed together across the packages that read them. Migrate using How the Store Is Selected.

Removal in openiap-google 5.0, react-native-iap 18.0, expo-iap 7.0, and flutter_inapp_purchase 12.0

Deprecated keyMigrate to
horizonEnabled=trueopeniapStore=horizon, or no pin: a debug build follows the connected Quest
openiapPlatform=none (flutter_inapp_purchase)openiapStore=none

Removal in expo-iap 7.0

Deprecated keyMigrate to
modules.horizon / EXPO_IAP_HORIZON=1ORG_GRADLE_PROJECT_openiapStore=horizon in the EAS profile env; a local debug build follows the connected Quest
EXPO_IAP_FIREOS=1modules.amazon.fireOS, which also turns Vega auto-detection off, or ORG_GRADLE_PROJECT_openiapStore=amazon in the EAS profile env
EXPO_IAP_VEGA=1modules.amazon.vegaOS, or a root manifest.toml when Fire OS is not declared
EXPO_IAP_ONSIDE=1modules.onside

Removal in OpenIap.Maui 4.0

Deprecated keyMigrate to
OpenIapAndroidStoreOpenIapStore

Removal in react-native-iap 18.0

Deprecated keyMigrate to
NitroProduct.originalPriceAndroid, originalPriceAmountMicrosAndroid, introductoryPriceValueAndroid, introductoryPriceCyclesAndroid, introductoryPricePeriodAndroid, subscriptionPeriodAndroid, freeTrialPeriodAndroidsubscriptionOffers and discountOffers on the product that fetchProducts returns

Community providers and Client Protocol 1.0.0#

The October 2026 train adds community store providers. Each change below says who is affected and what to do; upgrade native and framework dependencies together (the release card lists the versions).

Replace the aliases removed in 1.0.0#

Client Protocol 1.0.0 removes the previously deprecated names below. Update imports, hook calls, mocks, and native bindings when upgrading to this release train.

RemovedUse instead
openRedeemOfferCodeAndroid / presentCodeRedemptionSheetIOSopenRedeemOfferCode(), which returns a purchase or null
Godot open_redeem_offer_code_android / present_code_redemption_sheet_iosawait open_redeem_offer_code()
Apple presentCodeRedemptionSheetResultIOSopenRedeemOfferCode()
ObjC presentCodeRedemptionSheetIOSWithCompletion:openRedeemOfferCodeWithCompletion:
Horizon verification successisValid
OpenIapVersion.specVersionOpenIapVersion.clientProtocolVersion

A null redemption result can mean the store opened its UI without reporting a purchase immediately, or has no redemption flow. Observe purchase updates and refresh available purchases when the app resumes. React Native, Expo, Flutter, and MAUI now reject with a typed error when the Play redeem page cannot open; earlier versions resolved null. Catch that error. Godot emits purchase_error on failed or unsupported redemption while returning null.

Purchases and verification results require storeId#

What changed: storeId is required at construction on Purchase, PurchaseInput, and RequestVerifyPurchaseWithIapkitResult in TypeScript, Dart, Kotlin purchases, and the new Swift public initializers; C# and the Kotlin IAPKit result infer official ids instead. A community purchase uses store unknown with its own provider id; see Preserve store identity for the full rule. The unknown store spells IapStore.Unknown in Dart, Kotlin, and C#, IapStore.unknown in Swift, IapStore.UNKNOWN in GDScript, and 'unknown' in TypeScript.

Who is affected: apps that hand-build purchase, PurchaseInput, or verification values (fixtures, mocks, custom adapters, server-driven finish flows), and custom types that implement PurchaseCommon, which must add storeId. Passing SDK-returned purchases through needs no change. Swift apps are affected only through decoding and custom PurchaseCommon conformers: base had no public purchase initializer.

What to do: add storeId when constructing the value:

  • Saved official JSON without it still decodes to the canonical id, but a community value needs an explicit valid id.
  • A mismatched or invalid id fails to decode: TypeScript, Dart, Kotlin, and Swift decoders throw, GDScript from_dict returns null (null-check decoded values), and C# deserialization throws JsonException. A hand-built C# record with a missing community id or a mismatched id throws whenever StoreId is read, including Equals, GetHashCode, and serialization.
  • store_id defaults to "" in GDScript. from_dict rejects a blank id for every store, so a hand-built object round-tripped outside finish_transaction decodes to null. The Godot finish helpers stamp the connected provider's id onto blank finish inputs (a blank id for an official store is stripped instead), and GDScript PurchaseInput has no store_id.
kt
val purchase = PurchaseAndroid(
    // ...existing fields,
    store = IapStore.Google,
    storeId = "play",
)

An iOS purchase uses store apple with storeId apple.

KMP Store gains UNKNOWN#

What changed: the public Store enum has a new UNKNOWN case, returned for community-provider builds (the Android provider build and any non-Apple iOS provider) and on iOS when the Apple provider selection fails.

Who is affected: exhaustive when (getStore()) expressions without an else branch, which no longer compile.

What to do: add the UNKNOWN branch.

kt
// kmpIAP is your KmpInAppPurchase instance.
when (kmpIAP.getStore()) {
    Store.PLAY_STORE -> TODO("Play")
    Store.AMAZON -> TODO("Amazon")
    Store.APP_STORE -> TODO("App Store")
    Store.HORIZON -> TODO("Horizon")
    Store.UNKNOWN -> TODO("community provider")
    // NONE is never returned; it only completes the exhaustive when.
    Store.NONE -> TODO("no store")
}

Kotlin IAPKit result is no longer a data class#

What changed: RequestVerifyPurchaseWithIapkitResult is now a plain class with a private primary constructor. The old 3-, 5-, and 6-argument constructors keep their signatures, but copy lost binary compatibility; component1-3 still exist, and equals/hashCode now compare all seven fields instead of three. PurchaseAndroid gains a required storeId parameter after store (after storefrontCountryCodeIOS on PurchaseIOS), so their constructor and copy signatures change and later componentN indexes shift.

Who is affected: Kotlin callers on OpenIAP Google 4.0.0 and KMP 4.0.0 that construct or copy purchases or results, or decode saved JSON.

What to do: recompile against 4.0.0 and pass storeId to purchase constructors. The result's copy keeps storeId while store is unchanged (a new store resets it to the official id, or throws for Unknown without storeId) and now preserves clientPayload, productId, and environment. A result built with IapStore.Unknown and no storeId still compiles but throws IllegalArgumentException. A purchase's copy keeps its storeId, so pass a matching storeId when you change store. JSON with a missing or unrecognized store value used to decode as Unknown and now throws unless it carries a valid community storeId.

Shared classes move to openiap-core#

What changed: the shared dev.hyo.openiap classes moved into the new openiap-core artifact, which the store artifacts depend on.

Who is affected: apps that link the openiap-google AAR by file.

What to do: add openiap-core next to it; Maven consumers get it transitively.

Apple requires tvOS 16#

What changed: the SwiftPM floor moves from tvOS 15 to tvOS 16. The CocoaPods deployment target already required 16.0.

Who is affected: tvOS apps on OpenIAP Apple 4.0.0 that build through SwiftPM with a 15.x deployment target.

What to do: raise the tvOS deployment target to 16.0.

Cancelled Apple operations throw CancellationError#

What changed: cancelling the calling task of requestPurchase or another StoreKit operation now throws CancellationError with no purchase-error event. Previously requestPurchase, fetchProducts, getPromotedProductIOS, and subscriptionStatusIOS wrapped the cancellation into an emitted PurchaseError, and syncIOS, restorePurchases, and openRedeemOfferCode threw it as a PurchaseError without an event. initConnection still returns false when endConnection interrupts its init task. The SwiftUI store's getAvailablePurchases and getActiveSubscriptions also throw CancellationError, without updating state, when the task is cancelled or endConnection completes while they wait; its initConnection throws it when its task is cancelled, even after connecting.

Who is affected: Swift apps that call OpenIAP from a task that can be cancelled, such as a SwiftUI .task.

What to do: catch CancellationError at the call site alongside the existing PurchaseError path; keep using the listener for real purchase failures. A user dismissing the purchase sheet still reports user-cancelled through the listener.

Flutter initConnection returns the native result on Apple#

What changed: on iOS and macOS initConnection() now returns the native Boolean, false when StoreKit cannot make payments; the plugins returned nil (read as true) before.

Who is affected: Flutter apps that treat the Apple init result as always true.

What to do: branch on the result and show the store as unavailable when it is false.

Android errors carry the provider's code#

What changed: the wrappers forward the provider's Android error code where they returned fixed codes:

  • React Native: calls before init already reported not-prepared; thrown init errors, verifyPurchase, and verifyPurchaseWithProvider now forward the provider's code, and deep-link and endConnection errors arrive as OpenIAP errors.
  • Expo: Android calls that rejected every failure with service-error now forward the provider's code (not-prepared before init on Play and Horizon; Amazon queries work before init); thrown init errors and verification forward it too.
  • Flutter: calls that answered native failures with service-error now forward the provider's code (Dart-guarded calls still throw not-prepared before init), and thrown init errors forward it instead of init-connection.
  • Godot: failure results now carry a code field (the provider's code, else service-error, or purchase-verification-failed for verification); verify_purchase_with_provider before init now reports not-prepared, as restore_purchases and the entitlement reads already did; init_connection still returns only a Boolean.

On Amazon, getStorefront and IAPKit verification without a userId now throw StoreConnectionFailure instead of InitConnection when the user-data request cannot start (code still init-connection; initConnection still returns false), so Kotlin callers matching by type must handle the new class.

Who is affected: apps that match literal Android error codes or messages.

What to do: handle specific codes such as not-prepared.

ts
import {
  ErrorCode,
  getAvailablePurchases,
  initConnection,
} from 'expo-iap';
import type { PurchaseError } from 'expo-iap';

async function loadPurchases() {
  try {
    return await getAvailablePurchases();
  } catch (error) {
    // Before: expo-iap rejected this Android call with service-error.
    if ((error as PurchaseError).code !== ErrorCode.NotPrepared) throw error;
    const connected = await initConnection();
    if (!connected) throw error;
    return getAvailablePurchases();
  }
}

Failed verification reports the native code#

What changed: Flutter reports the native error code from verifyPurchase and verifyPurchaseWithProvider on every platform instead of always purchase-verification-failed; KMP does the same for verify-with-provider on iOS, and Godot on iOS and Android. Generic Android failures in Flutter now report transaction-validation-failed.

Who is affected: apps that match purchase-verification-failed from these calls.

What to do: match the specific codes.

Android restore runs the provider first#

What changed: React Native, Expo, and Flutter run the provider's Android restore before querying ownership. On Horizon each owned purchase then reaches the purchase listeners; Play and Amazon restores deliver nothing by themselves. Godot runs the same restore but emits no purchase_updated signal on Horizon, and a failed restore still emits purchase_error: read owned purchases with get_available_purchases_result. Flutter restore failures on iOS and macOS now read Failed to restore purchases [code]: ... instead of Failed to sync iOS purchases [code]: ... (the code still forwards the native code).

Who is affected: Horizon apps with purchase listeners, and Flutter apps that match the Apple restore failure message.

What to do: make the purchase handler idempotent so a repeated delivery grants once, and keep reading owned purchases after the restore resolves.

ts
import { purchaseUpdatedListener } from 'react-native-iap';

// Horizon redelivers every owned purchase on restore; grantOnce is your code
// and must grant at most once per token against your stored entitlements.
purchaseUpdatedListener((purchase) => {
  if (purchase.purchaseState !== 'purchased') return;
  void grantOnce(purchase.purchaseToken ?? purchase.id, purchase.productId);
});

Godot purchase_error events change#

What changed: a failed Apple restore now emits one purchase_error carrying OpenIAP's code instead of two (sync-error then service-error); verify_purchase now emits purchase_error on failure instead of returning null silently (on Apple every failure, on Android when the native result carries a code). On Apple, get_storefront failures, the products_fetched failure payloads for fetch_products, and the failure results of the iOS-only methods now carry OpenIAP's code instead of service-error or no code.

Who is affected: Godot apps that count restore errors, call verify_purchase or get_storefront, or read failure codes from products_fetched.

What to do: handle one restore event, null-check the verify result alongside the signal, and match specific codes.

Godot export rejects an unknown Android store#

What changed: an unrecognized openiap/android_store value now fails the export instead of falling back to Play.

Who is affected: Godot Android builds with a misspelled store value.

What to do: fix the store value.

iOS subscription checks use the active flag#

What changed: hasActiveSubscriptions on iOS now answers from OpenIAP's active flag (expiration in the future): React Native and Flutter on every call (Flutter on iOS and macOS), KMP and MAUI on id-filtered calls (MAUI also on Mac Catalyst), as Expo, Godot, and native already did. A subscriber in billing grace reads inactive. Flutter hasActiveSubscriptions no longer checks init in Dart: before initConnection, iOS and macOS connect and answer instead of throwing not-prepared, Play and Horizon report the provider's not-prepared, and failures read Failed to check active subscriptions [code]: ... instead of Failed to get active subscriptions [code]: ...; match codes, not messages.

Who is affected: apps that grant access during billing grace, and Flutter apps that match the failure message or call it before initConnection.

What to do: to keep grace access, read the renewal info from getActiveSubscriptions instead of the boolean.

ts
import { getActiveSubscriptions } from 'react-native-iap';

// hasActiveSubscriptions() is false in billing grace; use renewal info
// when grace keeps access.
const subs = await getActiveSubscriptions();
const inGrace = subs.some(
  (sub) => (sub.renewalInfoIOS?.gracePeriodExpirationDate ?? 0) > Date.now(),
);

2.x → 3.0#

The coordinated major train removes the previously deprecated, OpenIAP-owned compatibility surface. Use this catalog to update calls before upgrading.

Removal boundaries#

Native packages

PackageLast compatible majorRemoved in
openiap-apple2.x3.0.0
openiap-google2.x3.0.0

Framework libraries

LibraryLast compatible majorRemoved in
expo-iap4.x5.0.0
react-native-iap15.x16.0.0
flutter_inapp_purchase9.x10.0.0
kmp-iap2.x3.0.0
maui-iap1.x2.0.0
godot-iap2.x3.0.0

IAPKit is a hosted service rather than a versioned framework library. Its scoped keys, client payloads, verification, and staged data migrations are unchanged by this SDK-only major train.

Generated Swift, Kotlin, TypeScript, Dart, GDScript, and C# contracts now expose only the canonical schema. The former declarations remain listed below solely as migration reference.

Raw JavaScript objects, plugin configuration, custom MethodChannel payloads, and direct Godot dictionaries must also use canonical keys. Removed aliases are rejected or ignored; they never override missing canonical input.

Flutter purchase payload compatibility#

PurchaseAndroid.dataAndroid is the only public, schema-defined field for Google Play's raw signed purchase JSON. originalJsonAndroid is not a public Purchase field and is never the preferred output key.

Flutter 9.x accepted the following legacy native or custom MethodChannel payload shapes. Flutter 10 accepts only the canonical forms in the middle column.

Legacy Flutter 9.x inputEmit insteadPlatform
originalJsonAndroiddataAndroidAndroid
purchaseStateAndroidpurchaseStateAndroid
transactionStateIOSpurchaseStateiOS
transactionReceiptpurchaseTokeniOS
id used as a transactionId fallbackan explicit transactionId; keep id as the purchase identityAndroid and iOS
top-level { sku } for verifyPurchase / validateReceiptIOS{ apple: { sku } }iOS and macOS

The canonical id purchase identity is not deprecated. Flutter 10 requires an explicit transactionId.

Issue #248 and Android raw purchase JSON

  • Before Flutter 9.6.1, issue #248 caused canonical dataAndroid input to be lost by the Dart compatibility converter.
  • The patch in PR #251 read dataAndroid first and accepted originalJsonAndroid only as a temporary Flutter 9.x input fallback. If both keys exist, dataAndroid wins.
  • Flutter 10 removes that fallback. Custom native adapters, MethodChannel fixtures, and mocks must emit dataAndroid.

See the canonical field reference in PurchaseAndroid.

Flutter 10 package-specific migrations#

In addition to the generated OpenIAP schema surfaces below, flutter_inapp_purchase 10.0.0 removes these Flutter-only compatibility APIs:

Deprecated Flutter surfaceMigrate to
ReplacementMode / ReplaceModeAndroidReplacementMode
TypeInAppProductQueryType
builder replacementModesubscriptionProductReplacementParams
builder useAlternativeBillingInitConnectionConfig.enableBillingProgramAndroid
purchaseUpdatedpurchaseUpdatedListener
PurchaseResult / purchaseErrorPurchaseError / purchaseErrorListener
ConnectionResult / connectionUpdatedthe initConnection result
requestPurchaseOnPromotedProductIOSpurchasePromoted, then requestPurchase

Custom MethodChannel integrations

Applications normally use the Dart API and never call these internal channel names. Flutter 10 custom integrations must use the replacements:

Legacy channel methodMigrate to
getAvailableItemsByTypegetAvailablePurchases
getPurchaseHistoryByTypegetAvailablePurchases for active purchases
buyItemByTyperequestPurchase
acknowledgePurchasefinishTransaction or acknowledgePurchaseAndroid
consumeProduct / consumePurchasefinishTransaction with isConsumable
showInAppMessagesshowInAppMessagesAndroid
getAppTransactiongetAppTransactionIOS
getSubscriptionStatussubscriptionStatusIOS

Custom MethodChannel payloads

The official Dart API emits the canonical forms below. Flutter 10 no longer normalizes the historical custom-channel inputs.

Legacy payload shapeEmit instead
product type 'inapp''in-app'
requestPurchase.request.ios / requestSubscription.request.iosapple
requestPurchase.request.android / requestSubscription.request.androidgoogle
productId / sku used as a product idid
discounts / subscription product metadatadiscountOffers or subscriptionOffers, plus subscriptionGroupIdIOS when applicable
subResponseCodesubResponseCodeAndroid
fetchProducts skuArr / productIdsskus
offerTokenArrofferToken for one-time products or subscriptionOffers for subscriptions
obfuscatedAccountIdAndroid / obfuscatedProfileIdAndroidobfuscatedAccountId / obfuscatedProfileId
purchaseTokenAndroid / tokenpurchaseToken
finishTransaction transactionIdentifiertransactionId
replacementModeAndroid / replacementModesubscriptionProductReplacementParams
unsuffixed deep-link sku / packageNameskuAndroid / packageNameAndroid
numeric-indexed iOS SKU maps{ skus: [...] }

Removed schema migration catalog#

The following OpenIAP-owned schema surfaces were removed by the package versions above.

Validation and storefront APIs

Deprecated surfaceMigrate to
validateReceiptverifyPurchase
validateReceiptIOSverifyPurchase
getStorefrontIOSgetStorefront
requestPurchaseOnPromotedProductIOSthe SDK's promoted-product listener or callback, then requestPurchase
checkAlternativeBillingAvailabilityAndroidisBillingProgramAvailableAndroid with the BillingProgramAndroid value your app is enrolled in
showAlternativeBillingDialogAndroidshowBillingProgramInformationDialogAndroid (the in-app Billing Programs dialog); launchExternalLinkAndroid covers the external-link flows (External Offer, External Content Link, Billing Choice external links)
createAlternativeBillingTokenAndroidcreateBillingProgramReportingDetailsAndroid with the BillingProgramAndroid value your app is enrolled in

Purchase and connection inputs

Deprecated surfaceMigrate to
RequestPurchasePropsByPlatforms.ios / RequestSubscriptionPropsByPlatforms.iosapple
RequestPurchasePropsByPlatforms.android / RequestSubscriptionPropsByPlatforms.androidgoogle
useAlternativeBillingenableBillingProgramAndroid in InitConnectionConfig
alternativeBillingModeAndroidenableBillingProgramAndroid
RequestSubscriptionAndroidProps.replacementModesubscriptionProductReplacementParams for item-level replacement (Play Billing 8.1+)

Shared fields and errors

Deprecated surfaceMigrate to
PurchaseCommon.platform / PurchaseInput.platform / PurchaseIOS.platform / PurchaseAndroid.platformstore
willExpireSoondaysUntilExpirationIOS
presentCodeRedemptionSheetIOS Boolean resultnullable PurchaseIOS result: verified on Apple 27+ with Xcode 27+; null after the system sheet on iOS 15–26 and visionOS 1–26; Catalyst 16–26 fails with OpenIAP error code unknown and Catalyst 15 has no effect
receipt-failedpurchase-verification-failed
receipt-finishedpurchase-verification-finished
receipt-finished-failedpurchase-verification-finish-failed

Offer and billing-program models

Deprecated surfaceMigrate to
SubscriptionOfferIOSSubscriptionOffer
DiscountIOS / DiscountOfferIOSSubscriptionOffer
ProductAndroidOneTimePurchaseOfferDetailDiscountOffer
ProductSubscriptionAndroidOfferDetailsSubscriptionOffer
ProductAndroid.oneTimePurchaseOfferDetailsAndroidProductAndroid.discountOffers
ProductSubscriptionAndroid.oneTimePurchaseOfferDetailsAndroidsubscriptionOffers; one-time offer fields do not apply to subscriptions
ProductSubscriptionAndroid.discountOfferssubscriptionOffers; one-time offer fields do not apply to subscriptions
ProductAndroid.subscriptionOfferDetailsAndroid / ProductSubscriptionAndroid.subscriptionOfferDetailsAndroidsubscriptionOffers
ProductIOS.subscriptionInfoIOS / ProductSubscriptionIOS.discountsIOSsubscriptionOffers
ProductSubscriptionIOS.subscriptionInfoIOSsubscriptionOffers for offers and subscriptionGroupIdIOS for the group identifier
AlternativeBillingModeAndroidBillingProgramAndroid
AlternativeBillingModeAndroid value 'user-choice'BillingProgramAndroid value 'user-choice-billing'
AlternativeBillingModeAndroid value 'alternative-only'BillingProgramAndroid value 'external-offer'
ExternalOfferAvailabilityResultAndroidBillingProgramAvailabilityResultAndroid from isBillingProgramAvailableAndroid
ExternalOfferReportingDetailsAndroidBillingProgramReportingDetailsAndroid from createBillingProgramReportingDetailsAndroid

Removed package-specific compatibility shims#

These public aliases and wrappers were package-local rather than GraphQL schema members. They are absent from the major versions named in each heading.

openiap-apple (OpenIAP 3.0)

Deprecated package surfaceMigrate to
ReceiptValidationProps / ReceiptValidationResult / ReceiptValidationResultIOSVerifyPurchaseProps / VerifyPurchaseResult / VerifyPurchaseResultIOS
OpenIapErrorCode / OpenIapEvent / OpenIapPlatformErrorCode / IapEvent / IapPlatform
getStorefrontIOSWithCompletiongetStorefrontWithCompletion
requestPurchaseOnPromotedProductIOSWithCompletionpromotedProductListenerIOS followed by requestPurchase
short requestSubscriptionWithSku(_:offer:completion:) overloadthe extended overload with compactJWS, promotionalOfferJWS, winBackOfferId, and billingPlanType
raw/custom purchase id used as a transactionId fallbackan explicit transactionId; keep id as the canonical purchase identity
OpenIapStore.deepLinkToSubscriptionsIOSdeepLinkToSubscriptions
OpenIapVersion.gqlVersion / OpenIapVersionInfo.gqlVersionOpenIapVersion.clientProtocolVersion

openiap-google (OpenIAP 3.0)

Deprecated package surfaceMigrate to
ReceiptValidationProps / ReceiptValidationResult / ReceiptValidationResultIOSVerifyPurchaseProps / VerifyPurchaseResult / VerifyPurchaseResultIOS
AlternativeBillingModeBillingProgramAndroid through InitConnectionConfig.enableBillingProgramAndroid
Play OpenIapModule(context, AlternativeBillingMode, legacy listeners)OpenIapModule(context), then register listeners and pass InitConnectionConfig.enableBillingProgramAndroid to initConnection
Play OpenIapModule(context, enableAlternativeBilling) / OpenIapStore(context, enableAlternativeBilling)construct normally, then pass InitConnectionConfig.enableBillingProgramAndroid to initConnection
Play OpenIapStore(context, AlternativeBillingMode, userChoiceBillingListener)OpenIapStore(context), then register listeners and pass InitConnectionConfig.enableBillingProgramAndroid to initConnection
Amazon OpenIapModule(context, enableAlternativeBilling)OpenIapModule(context); Amazon ignores the legacy option
Amazon OpenIapModule(context, AlternativeBillingMode, legacy listeners)OpenIapModule(context); Amazon ignores the legacy options, then register listeners with add/remove APIs
Amazon OpenIapStore(context, AlternativeBillingMode, userChoiceBillingListener)OpenIapStore(context); Amazon ignores the legacy options
Horizon OpenIapModule / OpenIapStore constructors with AlternativeBillingMode or legacy listenersOpenIapModule(context) / OpenIapStore(context); Horizon ignores the legacy options
Horizon manifest keys com.meta.horizon.platform.ovr.OCULUS_APP_ID / com.meta.horizon.platform.ovr.HORIZON_APP_ID / com.oculus.vr.APP_IDcom.meta.horizon.platform.HORIZON_APP_ID
setUserChoiceBillingListener / setDeveloperProvidedBillingListenerthe corresponding add/remove listener APIs
UserChoiceDetails / UserChoiceBillingListenerUserChoiceBillingDetails / OpenIapUserChoiceBillingListener
DeveloperProvidedBillingDetails / DeveloperProvidedBillingListenerDeveloperProvidedBillingDetailsAndroid / OpenIapDeveloperProvidedBillingListener
OpenIapStore.connectionStatusOpenIapStore.isConnected
OpenIapError.InvalidReceiptOpenIapError.InvalidPurchaseVerification
checkAlternativeBillingAvailabilityisBillingProgramAvailable with BillingProgramAndroid.ExternalOffer
showAlternativeBillingInformationDialoglaunchExternalLink
createAlternativeBillingReportingTokencreateBillingProgramReportingDetails with BillingProgramAndroid.ExternalOffer
OpenIapLog.d / i / w / edebug / info / warn / error

react-native-iap 16.0.0

Deprecated package surfaceMigrate to
ProductTypeInput 'inapp''in-app'
request.ios / request.androidrequest.apple / request.google
replacementModesubscriptionProductReplacementParams
useIAP().alternativeBillingModeAndroidenableBillingProgramAndroid
acknowledgePurchaseacknowledgePurchaseAndroid
consumePurchaseconsumePurchaseAndroid
requestPromotedProductIOSgetPromotedProductIOS
getReceiptIOSgetReceiptDataIOS
requestPurchaseOnPromotedProductIOSpromotedProductListenerIOS, then requestPurchase
useIAP().requestPurchaseOnPromotedProductIOSonPromotedProductIOS, then requestPurchase

expo-iap 5.0.0

Deprecated package surfaceMigrate to
ProductTypeInput 'inapp''in-app'
request.ios / request.androidrequest.apple / request.google
Android custom-channel skuArrskus
Android custom-channel offerTokenArrsubscriptionOffers for subscriptions
replacementModesubscriptionProductReplacementParams
useIAP().alternativeBillingModeAndroidenableBillingProgramAndroid
acknowledgePurchaseacknowledgePurchaseAndroid
consumePurchaseconsumePurchaseAndroid
getReceiptIOSgetReceiptDataIOS
validateReceiptAndroidverifyPurchase
Android deep-link sku / packageNameskuAndroid / packageNameAndroid
requestPurchaseOnPromotedProductIOSpromotedProductListenerIOS, then requestPurchase
useIAP().requestPurchaseOnPromotedProductIOSonPromotedProductIOS, then requestPurchase
config.iosAlternativeBillingconfig.ios.alternativeBilling
config.horizonAppId / config.android.horizonAppIdconfig.android.horizon.appId
config.android.amazon.fireOS / boolean config.android.amazon.vegaOSconfig.modules.amazon.fireOS / config.modules.amazon.vegaOS

godot-iap 3.0.0

Deprecated package surfaceMigrate to
godot-iap get_storefront_iosget_storefront
godot-iap validate_receipt_ios / validate_receiptverify_purchase
godot-iap request_purchase_on_promoted_product_iospromoted_product_ios, then request_purchase
godot-iap check_alternative_billing_availability_androidis_billing_program_available_android with BillingProgramAndroid.EXTERNAL_OFFER
godot-iap show_alternative_billing_dialog_androidlaunch_external_link_android
godot-iap create_alternative_billing_token_androidcreate_billing_program_reporting_details_android with BillingProgramAndroid.EXTERNAL_OFFER
flattened verify_purchase_with_provider IAPKit keyskeep provider at the top level and nest apiKey, baseUrl, includeClientPayload, apple, google, and amazon under iapkit
ProductQueryType 'inapp' / 'in_app''in-app'
ProductQueryType 'subscription''subs'
raw request selector and ios / android purchase envelopesrequestPurchase or requestSubscription with apple / google
raw offer_tokenofferToken
raw obfuscatedAccountIdAndroid / obfuscatedProfileIdAndroid / purchaseTokenAndroidthe corresponding unsuffixed Google request keys
raw replacementModeAndroid / replacementModesubscriptionProductReplacementParams
raw skuArr / numeric-indexed iOS SKU mapsskus
raw offerTokenArrofferToken for a one-time product or subscriptionOffers for a subscription
Android native requestPurchaseJsonrequestPurchase
iOS simple requestPurchase(sku:) / top-level sku requestrequestPurchaseWithPayload using an apple request envelope

kmp-iap 3.0.0

Deprecated package surfaceMigrate to
kmp-iap requestPurchaseOnPromotedProductIOSpromotedProductListener, then requestPurchase
kmp-iap getStorefrontIOSgetStorefront
kmp-iap validateReceiptIOS / validateReceiptverifyPurchase
PurchaseRequestBuilder.ios / PurchaseRequestBuilder.androidPurchaseRequestBuilder.apple / PurchaseRequestBuilder.google
AndroidOptionsBuilder.replacementModesubscriptionProductReplacementParams
generated RequestPurchasePropsByPlatforms.ios / .android and RequestSubscriptionPropsByPlatforms.ios / .androidapple / google
generated RequestPurchaseProps.useAlternativeBilling / InitConnectionConfig.alternativeBillingModeAndroidInitConnectionConfig.enableBillingProgramAndroid
generated RequestSubscriptionAndroidProps.replacementModesubscriptionProductReplacementParams

OpenIap.Maui 2.0.0

Deprecated package surfaceMigrate to
OpenIap.Maui Iap facadeOpenIapClient
net9.0, net9.0-android, net9.0-ios, and net9.0-maccatalyst targetsthe matching net10.0 target frameworks with the .NET 10 MAUI workload
RequestPurchaseOnPromotedProductIOSAsyncPromotedProductIOS, then RequestPurchaseAsync

What this schedule does not remove#

The schedule applies to OpenIAP-owned deprecated schema members and explicit compatibility shims. It does not automatically remove:

  • redirects kept so existing documentation links continue to work;
  • documentation that describes an upstream StoreKit or Play Billing legacy technology still supported by the stores; or
  • StoreKit, Play Billing, Amazon, or Horizon response-shape normalization, including upstream names such as productIdentifier, localizedPrice, and historical receipt payload labels;
  • internal React Native, Expo, KMP, or Godot recovery of native response fields that applications do not author;
  • input normalization that accepts historical error-code spellings;
  • safe fallbacks used when an operating-system version does not support a newer store API; or
  • staged IAPKit storage migrations with their own retention and rollback requirements.

Check the release notes before every major upgrade for the final removal list and package-specific migration steps.