cordova-plugin-purchase

repository·master·Indexed 23 days ago

https://github.com/j3k0/cordova-plugin-purchase

A native Capacitor and Cordova plugin for handling In-App Purchases (IAP) across multiple platforms, including AppStore (iOS/macOS), Google Play, and Braintree. Version 13.18.0 supports StoreKit 2 for iOS 15+ and Google Play Billing 8.3 for Android API 23+. It provides a singleton store object to manage products and transactions, with support for receipt validation, subscriptions, and a dedicated Capacitor-native edition via the capacitor-plugin-cdv-purchase package.

Tokens
96.4K
Snippets
86
Records
665
Agent score
80%

What's inside cordova-plugin-purchase

  1. What is an Adapter in CdvPurchase?

    master

    An Adapter is an abstraction layer for a specific payment or in-app purchase platform. It allows the plugin to interact with different stores (like Apple AppStore, Google Play, Braintree, etc.) using a unified interface.

    Commonly implemented adapters include:

    • AppleAppStore.Adapter
    • GooglePlay.Adapter
    • Braintree.Adapter
    • IapticJS.Adapter
    • WindowsStore.Adapter
    • Test.Adapter
  2. Understand Subscription Cancellation Reasons

    master

    When a subscription is no longer auto-renewing, the cancelReason property identifies why. This is critical for debugging billing issues or understanding user behavior.

    Possible values for cancelReason:

    • 0: User canceled the subscription
    • 1: Subscription was canceled by the system (e.g., billing problem)
    • 2: Subscription was replaced with a new subscription
    • 3: Subscription was canceled by the developer
  3. Implement Non-Renewing iOS Subscriptions

    master

    Non-Renewing Subscriptions are a special iOS product type where Apple does not manage auto-renewals. The developer is responsible for syncing subscription status across devices and prompting users to renew.

    Important Requirements from Apple:

    • Manual Renewal: You must prompt users to renew before expiration.
    • Time Addition: If a user buys a new subscription before the old one expires, you must add the new time to the existing remaining time.
    • Syncing: You must sync subscription status across all devices using the same Apple ID (or your own custom auth system).

    Implementation Details:

    • Use store.NON_RENEWING_SUBSCRIPTION when registering the product.
    • These products trigger lifecycle events every time the app starts, so you must implement logic to handle these events (e.g., checking if you need to update your own server).
    • Always call p.finish() after a successful purchase to allow the product to be purchased again.
    // Register the non-renewing subscription product with the store.
    store.register({
        id: "my_product_id",
        alias: "My Product",
        type: store.NON_RENEWING_SUBSCRIPTION
    });
    
    // Called when store.order("my_product_id") is executed.
    store.when("my_product_id").initiated(function(p) {
        my_app_utils.setIsProductPurchaseInitiated("my_product_id", true);
    });
    
    // Called when the user has cancelled purchasing.
    store.when("my_product_id").cancelled(function(p) {
        my_app_utils.setIsProductPurchaseInitiated("my_product_id", false);
    });
    
    // Called when the product purchase is finished (triggers on app start for non-renewing).
    store.when("my_product_id").approved(function(p) {
        my_product_id.purchaseNonRenewingSubscription(p.id, function success() {
            // Must call finish to charge the user and allow repurchase.
            p.finish();
            my_app_utils.setIsProductPurchaseInitiated("my_product_id", false);
        }, function error(err)) {
            // Handle server error (do NOT call finish so it retries next launch)
            my_app_utils.alertUserAboutServerError({
                title: 'Subscription Purchase Error',
                template: 'We could not store your new subscription status on our server.'
            });
            my_app_utils.setIsProductPurchaseInitiated("my_product_id", false);
        });
    });
    
    // Handle StoreKit errors
    store.error(function(e){
        console.log("storekit ERROR " + e.code + ": " + e.message);
    });
    
    store.refresh();
  4. Use the Test Adapter for local testing

    master

    The Adapter class in the CdvPurchase.Test module is a test adapter used for local development and testing with mock products. It simulates a complete payment platform, supporting both In-App Products and Payment Requests.

    Key characteristics:

    • Mock Products: Uses a predefined list of test products (see Test.TEST_PRODUCTS).
    • Simulated Payments: When using requestPayment, it simulates a user prompt where entering "Y" approves the transaction and "E" returns an error.
    • Platform ID: Returns Platform.TEST.
    • Parallel Loading: Supports parallel loading of receipts and products (supportsParallelLoading is true).
  5. Configure 3D Secure authentication challenges

    master

    When using CdvPurchase.Braintree.ThreeDSecure.Request, you can control how authentication challenges are requested from the card issuer using these properties:

    • cardAddChallenge (boolean): Controls challenges specifically for adding a payment method to the merchant's vault.
      • true: Requests an authentication challenge to confirm adding a new card.
      • false: Does not request a challenge.
      • undefined: If the amount is greater than 0, the challenge will not be requested.
    • challengeRequested (boolean): If set to true, an authentication challenge will be forced if possible.
    • exemptionRequested (boolean): If set to true, an exemption to the authentication challenge will be requested.
  6. Identify linked purchases and subscription transitions

    master

    The linkedPurchaseToken property is used to track the relationship between a new subscription and a previous one. This token is present if the current purchase is:

    • A re-signup of a canceled but non-lapsed subscription.
    • An upgrade or downgrade from a previous subscription.
    • A conversion from a prepaid subscription to an auto-renewing one (or vice versa).
    • A top-up of a prepaid subscription.

    Additionally, latestOrderId provides the order ID of the most recent transaction associated with the subscription (e.g., the signup order for auto-renewing subscriptions or the specific token-associated order for prepaid subscriptions).

  7. Understand smoke test assertions and success criteria

    master

    The smoke test verifies the plugin's lifecycle through specific log assertions. Success is determined by whether the following assertions are met:

    Hard Assertions (Failure causes test to FAIL)

    • App launched + plugin JS ran: Verified by log line [CdvPurchase] INFO: initialize([...]) v<ver>.
    • Platform adapter initialized: Verified by GooglePlay initialized. or AppStore initialized..
    • Product-load round-trip completed: Verified by products loaded:.
    • iOS Product Validity: On iOS, known-good products must resolve as valid. This is a hard gate by default.
    • Offline Entitlements (Offline example only):
      • OfflineEntitlements ready — cache loaded (proves cache loading).
      • offline.isOwned( (proves ownership check execution).

    Soft Assertions (Reported but do not fail the test)

    • Android Product Validity: Checking if a product ID is seen as valid on Android is treated as a soft check because it is highly dependent on the Play Console environment.

    How to view logs

    • Android: Use adb logcat with the chromium tag.
    • iOS Cordova: Use the unified log via simctl spawn <sim> log stream.
    • iOS Capacitor: Use app stdout via simctl launch --console-pty (lines are prefixed with ⚡️ [log] - ).
  8. Handle Subscription Cancellation Contexts

    master

    The CanceledStateContext type provides specific information about why a subscription was canceled. Depending on the cause, the context will contain one of the following specialized objects:

    • userInitiatedCancellation: Information regarding cancellations started by the user (UserInitiatedCancellation).
    • systemInitiatedCancellation: Information regarding cancellations started by the Google system (SystemInitiatedCancellation).
    • developerInitiatedCancellation: Information regarding cancellations started by the developer (DeveloperInitiatedCancellation).
    • replacementCancellation: Information regarding cancellations caused by a subscription replacement (ReplacementCancellation).
  9. Understand Google Play purchase states and types

    master

    When inspecting ProductPurchaseExt objects, pay attention to these specific numeric states:

    Acknowledgement State (acknowledgementState)

    • 0: Yet to be acknowledged
    • 1: Acknowledged

    Consumption State (consumptionState)

    • 0: Yet to be consumed
    • 1: Consumed

    Purchase State (purchaseState)

    • 0: Purchased
    • 1: Canceled
    • 2: Pending

    Purchase Type (purchaseType) Note: Only set if the purchase was not made via the standard in-app billing flow.

    • 0: Test (license testing account)
    • 1: Promo (promo code)
    • 2: Rewarded (video ad)
  10. Understand the TransactionState lifecycle

    master

    The TransactionState enumeration represents the possible states of an in-app purchase transaction. A typical successful transaction follows this lifecycle flow:

    INITIATED $\rightarrow$ PENDING (optional) $\rightarrow$ APPROVED $\rightarrow$ FINISHED.

    Monitoring these states is essential for determining when to grant access to digital goods and when to finalize the transaction with the store.