Troubleshooting (v13+)
This page lists common issues encountered when implementing In-App Purchases with the Cordova Purchase plugin (v13+).
First Steps:
- Enable Debug Logging: Set
CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG;early in your code (afterdevicereadybut beforeinitialize) to get detailed logs. - Check Device Logs: This is crucial.
- Android: Use
adb logcat CordovaPurchase:V CordovaLog:V chromium:D *:S(or similar filters) via Android Studio or command line. Look for messages taggedCordovaPurchaseor from the native Billing Library. - iOS/macOS: Use the Xcode Console (Window -> Devices and Simulators -> Select Device -> Open Console). Filter for messages related to your app or StoreKit.
- Android: Use
- Verify Plugin Version: Ensure you are using
cordova-plugin-purchasev13 or later (cordova plugin ls).
General Setup & Initialization Issues
CdvPurchaseorCdvPurchase.storeis undefined:- Cause: Accessing the plugin before the
devicereadyevent fires. - Solution: Ensure all plugin interactions (
CdvPurchase.store...) happen inside or after thedevicereadyevent listener callback. For Ionic/Capacitor, usethis.platform.ready().then(() => { ... });. - Cause: Plugin not installed correctly or build issue.
- Solution: Verify installation (
cordova plugin ls). Remove and re-add the plugin. Check for build errors (cordova prepare/build,npx cap sync). For Capacitor, ensurenpx cap syncwas run after adding the plugin.
- Cause: Accessing the plugin before the
store.initialize([...])Fails or Returns Errors:- Check Device Logs: Look for specific native errors (StoreKit, BillingClient).
- Network Connectivity: Device needs internet access. Use
cordova-plugin-network-informationto check status. - Platform Setup: Double-check all platform-specific prerequisites (see Setup guides in Use Cases). This is a very common source of errors.
- iOS/macOS: Active "Agreements, Tax, and Banking" in App Store Connect? "In-App Purchase" capability enabled in Xcode? Correct Bundle ID?
- Android: Correct Package Name?
BILLINGpermission inAndroidManifest.xml(added automatically by plugin)? Signed release build uploaded to a testing track? Test account configured on device and in Play Console?
- Validator Issues: If
store.validatoris set, ensure the URL is correct, reachable, and your server is running. Temporarily remove the validator setting to isolate the issue. Check server logs. - Invalid Platform Argument: Ensure you pass an array of
Platformenums orPlatformWithOptionsobjects toinitialize(), e.g.,store.initialize([Platform.APPLE_APPSTORE]).
store.ready()Callback Never Fires:- Cause:
store.initialize()failed or never completed. - Solution: Check for errors during initialization (see above). Ensure
initialize()is actually being called.
- Cause:
Product Loading Issues
- Products Not Loading (Empty
store.productsorproductUpdatednot firing):- Initialization Order: Call
store.register([...])before callingstore.initialize([...]). - Exact Product IDs: Verify IDs in
store.register()exactly match those in App Store Connect / Google Play Console (case-sensitive, no extra spaces). - Correct Platform: Ensure the
platformspecified instore.register()matches the platform you are running on and initializing. - Product Status: Ensure products are in an active/approved state ("Ready to Submit" or "Approved" on iOS, "Active" on Android) in the respective consoles. They must be "Cleared for Sale" (iOS) or have a price set (Android).
- Platform Propagation Delay: Changes in App Store Connect / Play Console can take several hours (sometimes up to 24) to become available in the sandbox/testing environments. Be patient after creating or modifying products.
- Check Device Logs: Look for specific errors during the product loading phase after
initialize(). - (iOS) Agreements: Re-check "Agreements, Tax, and Banking" in App Store Connect. Inactive agreements prevent product loading.
- (Android) Signed Build: Ensure a signed release build has been uploaded to a testing track in the Play Console. Product loading often fails without this.
- Initialization Order: Call
Purchase Flow Issues
offer.order()orstore.requestPayment()Fails Immediately:- Check
offer.canPurchase/store.checkSupport(): Is the product/offer loaded and marked as purchasable? Is the platform adapter ready and does it support ordering/payments? - Platform Setup: Re-verify platform prerequisites (Agreements, Capabilities, Test Accounts).
- (iOS) Sandbox Account: Are you logged into a Sandbox Tester account on the device (
Settings -> App Store -> Sandbox Account)? Sign out of production accounts first. - (iOS) Device Restrictions: Check iOS Settings -> Screen Time -> Content & Privacy Restrictions -> iTunes & App Store Purchases -> In-app Purchases -> Allow.
- (Android) Test Account: Is the primary account on the device a configured License Tester? Is the app installed via the Play Store testing track?
- Check Device Logs: Look for immediate errors from the native SDK.
- Check
- Purchase Dialog Appears but Fails/Errors Out:
- Check Device Logs: Critical for diagnosing platform-specific payment errors (e.g., invalid payment method, network issues during authorization).
- Sandbox/Test Environment Issues: Platform test environments can sometimes be unstable. Try again later or with a different test account.
- (iOS) Sandbox Login Prompts Repeatedly / Fails: Log out completely (App Store + Sandbox). Sign in only when prompted by the app during purchase. Try a new Sandbox Tester account.
.approved()Callback Not Firing After Successful Payment UI:- Cause: Native event listener might not be set up correctly or an earlier error occurred.
- Solution: Ensure
store.initialize()completed successfully. Check for any errors logged before the purchase attempt. Verify thestore.when().approved(...)listener is correctly registered before the purchase is initiated. Check device logs for native listener issues.
.verified()Callback Not Firing After.approved():- Cause: Receipt validation failed.
- Solution: Check
store.error()or.unverified()listeners for validation errors. Verify yourstore.validatorURL/function. Check your validation server logs for errors (communication with Apple/Google, invalid receipt data, API key issues). Check network connectivity from the device and the server. Ensure CSP allows connection to the validator URL.
.finished()Callback Not Firing After.verified():- Cause: Error during the native
finishTransactioncall or the.verified()callback threw an unhandled exception beforereceipt.finish()was called. - Solution: Check device logs for errors during the finish call. Ensure your
.verified()logic correctly callsreceipt.finish()and doesn't throw errors beforehand.
- Cause: Error during the native
Receipt Validation Issues
- Validation Request Fails (Error Code
COMMUNICATIONorBAD_RESPONSE):- URL/Endpoint: Verify
store.validatorURL is correct, reachable, and doesn't have typos. - Network: Check device network connectivity. Check server network connectivity (can it reach Apple/Google?).
- CORS (If using browser-based validation): Ensure your server sends correct CORS headers. Using
cordova-plugin-advanced-httpcan bypass browser CORS issues. - CSP: Ensure
Content-Security-Policyinindex.htmlallows connections to your validator domain (connect-src https://your-validator.com;). - Server Issues: Check validator server logs. Firewall issues? Service downtime? Incorrect API keys/secrets used by the validator? Invalid request format received?
- Timeout: Request might be timing out. If using
Validator.Target, increase thetimeoutvalue. Check server processing time.
- URL/Endpoint: Verify
- Validation Fails (Error Code
VERIFICATION_FAILEDor platform-specific codes):- Check Validator Logs: Essential for diagnosis. Why did the server reject the validation?
- Incorrect Environment: Is your validator calling the correct Apple/Google endpoint (Sandbox vs. Production)? Receipts generated in Sandbox must be validated against the Sandbox endpoint, and vice-versa.
- Invalid Credentials: Is the App-Specific Shared Secret (iOS) or Service Account Key (Android) used by the validator correct and valid?
- Invalid Receipt Data: Was the receipt data corrupted before sending? Is the plugin sending the correct fields? (Check
receiptValidationBodyin adapter source if necessary). - Incorrect Validator Logic: Is your server correctly parsing the platform response and determining entitlement?
Subscription Issues
- Subscription Status Incorrect (
product.owned, Expiry Date):- Cause: Almost always due to lack of server-side receipt validation or incorrect validator logic.
- Solution: Implement or fix your
store.validator. Only a validator communicating with Apple/Google servers can provide the authoritative expiry date, renewal status, grace period status, etc. Do not rely on localReceiptorTransactiondata for subscription status. Ensure your validator returns correctexpiryDate,isExpired,renewalIntentetc. in theVerifiedPurchaseobjects.
- Renewals Not Detected / Status Not Updating:
- Cause: Lack of receipt validation or validator not being called periodically.
- Solution: Use
store.validator. Callstore.update()periodically or when the user visits relevant screens to refresh validated status. Implement server-to-server notifications from Apple/Google to your backend for real-time updates (handled by services like Iaptic).
- Cannot Upgrade/Downgrade:
- (iOS): Ensure products are in the same Subscription Group in App Store Connect.
- (Android): Ensure products are registered with the same
groupinstore.register(). Pass the correctoldPurchaseTokenandreplacementModeinadditionalDatawhen callingoffer.order(). Check device logs for specific Billing Library errors.
StoreKit 2 Issues (v13.14+)
-
Purchases work but validation returns unexpected format:
- Cause: With
cordova-plugin-purchase-storekit2installed on iOS 15+, the adapter sends per-transaction JWS tokens (typeapple-sk2) instead of the monolithicappStoreReceipt. - Solution: Ensure your validator supports the
apple-sk2transaction type. If using Iaptic, this is handled automatically. If using a custom validator, update it to acceptjwsRepresentationfields.
- Cause: With
-
Duplicate transactions or double validation calls after installing StoreKit 2 extension:
- Cause: On older plugin versions (< 13.15.2), both SK1 and SK2 observers could fire for the same purchase.
- Solution: Update to v13.15.2+. The plugin now automatically disables the SK1 payment queue observer when the SK2 extension is detected on iOS 15+.
-
StoreKit 2 extension not activating (still using SK1):
- Cause: The
cordova-plugin-purchase-storekit2plugin is not installed, or the device runs iOS < 15. - Solution: Verify installation (
cordova plugin lsor checkpackage.json). The SK2 extension requires cordova-ios 7+ (tested with cordova-ios 8). On iOS < 15, SK1 is used automatically -- this is expected behavior.
- Cause: The
-
store.getStorefront()returns error on Mac Catalyst:- Cause: SK1's
SKPaymentQueue.storefrontreturns nil on Mac Catalyst / "Designed for iPad" mode. - Solution: Update to v13.15.1+ and install
cordova-plugin-purchase-storekit2v1.0.3+. The plugin falls back to SK2'sStorefront.currentautomatically.
- Cause: SK1's
-
Sandbox sign-in dialog loops endlessly (iOS):
- Cause: The app receipt fails to load, and the native transaction never finishes, causing repeated sign-in prompts.
- Solution: Update to v13.15.3+. The plugin now falls back to a synthetic receipt so the transaction resolves instead of looping.
-
Existing subscriptions not visible after app relaunch (Capacitor + SK2):
- Cause: On earlier versions, current entitlements were not emitted on init.
- Solution: Update to v13.15.2+. The Capacitor SK2 plugin now emits current entitlements as restored transactions on
init().
Capacitor Installation Issues (v13.15+)
-
npx cap sync iosfails with missingPackage.swiftor podspec:- Cause: Earlier versions of
capacitor-plugin-cdv-purchaseomitted the SPM manifest and root podspec from the npm tarball. - Solution: Update to
capacitor-plugin-cdv-purchasev13.15.2+. BothPackage.swift(for Capacitor 8/SPM) andCapacitorPluginCdvPurchase.podspec(for Capacitor 6-7/CocoaPods) are now included.
- Cause: Earlier versions of
-
Peer dependency conflict with Capacitor 7 or 8:
- Cause: The
@capacitor/corepeer dependency was pinned to^6.0.0. - Solution: Update to
capacitor-plugin-cdv-purchasev13.15.2+ which supports^6.0.0 || ^7.0.0 || ^8.0.0.
- Cause: The
-
CdvPurchaseundefined in Capacitor app:- Cause: The plugin was not synced or the Capacitor bridge has not loaded yet.
- Solution: Run
npx cap syncafter installing. AccessCdvPurchase.storeonly afterCapacitor.Pluginsis ready. In Ionic, usethis.platform.ready().
-
Using both Cordova plugin and Capacitor plugin simultaneously:
- Cause: Installing both
cordova-plugin-purchase(via Cordova compatibility) andcapacitor-plugin-cdv-purchasecauses conflicts. - Solution: Use only one. For Capacitor apps, prefer
capacitor-plugin-cdv-purchase. Remove the Cordova plugin:npm uninstall cordova-plugin-purchasethennpx cap sync.
- Cause: Installing both
Multi-Quantity Purchase Issues (v13.15+)
-
quantityparameter ignored on iOS:- Cause: Multi-quantity requires
cordova-plugin-purchasev13.15.0+ on iOS. - Solution: Update to v13.15.0+. Verify the platform supports it:
if (!store.checkSupport(CdvPurchase.Platform.APPLE_APPSTORE, 'orderQuantity')) {
console.warn('Multi-quantity not supported on this platform version');
}
- Cause: Multi-quantity requires
-
quantitynot appearing inVerifiedPurchase:- Cause: Your validator does not return the
quantityfield, or you are on an older plugin version. - Solution: Update to v13.15.0+. Ensure your validator extracts and returns the quantity from the transaction data. If using Iaptic, this is handled automatically.
- Cause: Your validator does not return the
-
Apple rejects quantity > 10:
- Cause: Apple limits consumable purchases to 10 units per transaction.
- Solution: Cap the quantity picker at 10. For larger quantities, perform multiple sequential purchases or adjust your product's unit value.
Google Play Billing 8.x Issues (v13.13+)
-
Build fails with
minSdkVersionerror:- Cause: Google Play Billing Library 8.1+ requires
minSdkVersion23. - Solution: In
config.xmlorbuild.gradle, setminSdkVersionto 23 or higher:<preference name="android-minSdkVersion" value="23" />
- Cause: Google Play Billing Library 8.1+ requires
-
Suspended subscriptions now appear as "owned" locally but validation says expired:
- Cause: Google Play now returns suspended (paused / payment-on-hold) subscriptions in the purchases list (aligning with Apple). The local transaction exists but the subscription is not active.
- Solution: This is expected behavior. Always rely on
store.owned()(which checks validated expiry) rather than the presence of a local transaction. TheexpirationDateon suspended subscriptions is in the past, sostore.owned()correctly returnsfalse.
-
"Product Owned" event never fires (Android):
- Cause: On v13.15.3 and earlier, a null
AccountIdentifiersfrom the Play Billing Library could crash the purchase-to-JS serialization silently. - Solution: Update to v13.15.3+ which includes a null-guard for
Purchase.getAccountIdentifiers().
- Cause: On v13.15.3 and earlier, a null
This is not an exhaustive list. If you encounter issues not listed here, please re-check device logs, consult platform-specific documentation (StoreKit, Google Play Billing), and consider opening an issue on the plugin's GitHub repository with detailed information (logs, code snippets, platform versions).