Migrating to v13
Version 13 of the plugin introduces significant breaking changes compared to v11 and earlier. This guide outlines the key differences and how to update your existing code.
Ionic / Capacitor Users: Note that @ionic-native/in-app-purchase-2
is not compatible with cordova-plugin-purchase
v13+. You should remove the Ionic Native wrapper and use the plugin directly via the CdvPurchase
global namespace (e.g., CdvPurchase.store
). Ensure you wait for the platform to be ready (this.platform.ready()
in Ionic) before accessing CdvPurchase.store
.
1. Global Namespace Change
Before: The plugin exposed its API through the global
window.store
object.After (v13+): The API is now under the
window.CdvPurchase
namespace. The main entry point isCdvPurchase.store
.
Migration:
Option A (Recommended): Replace all instances of
store.
withCdvPurchase.store.
.Option B (Quick Fix, Less Recommended): Re-assign the global
store
variable afterdeviceready
. This maintains compatibility with old code but pollutes the global scope and might cause type issues if not declared properly.
2. Initialization and Refreshing
Before:
store.refresh()
was used ambiguously for initialization, refreshing prices/status, and restoring purchases.After (v13+): These actions are split into separate, clearer methods:
store.initialize([...platforms])
: Mandatory first call. Initializes specified platforms (e.g.,[Platform.APPLE_APPSTORE, Platform.GOOGLE_PLAY]
), loads product details, and fetches initial receipt/purchase status. Call this once afterdeviceready
.store.update()
: Refreshes product pricing and potentially updates local purchase status by communicating with the stores. Use this if you need to refresh prices long after initialization (e.g., when the user enters the store screen). It's rate-limited by default (store.minTimeBetweenUpdates
) to avoid excessive calls.store.restorePurchases()
: Specifically triggers the platform's restore mechanism (primarily for non-consumables/subscriptions on iOS/AppStore) to retrieve purchases made with the same store account on other devices.
Migration:
Replace your initial
store.refresh()
call withstore.initialize([...platforms])
. Ensure you pass the platforms you intend to use.If you used
store.refresh()
later to update prices, replace it withstore.update()
.If you used
store.refresh()
specifically for restoring purchases (often via a "Restore Purchases" button), replace it withstore.restorePurchases()
.
3. Product Registration
Before:
store.register({ id, type, alias })
After (v13+):
store.register({ id, type, platform })
. Theplatform
field (e.g.,CdvPurchase.Platform.APPLE_APPSTORE
) is now mandatory to specify which store the product belongs to. Thealias
field has been removed.
Migration:
Add the correct
platform: CdvPurchase.Platform.XXX
field to each product registration object.Remove the
alias
field if present.
4. Product Information Refactoring
Before: The
Product
object mixed metadata (title, price), local purchase state (approved, finished), and validated data (expiryDate, owned).After (v13+): Information is separated into distinct objects for clarity and accuracy:
Product
: Contains store metadata (title, description, offers/pricing). Accessed viastore.products
orstore.get()
.Offer
: Represents a specific pricing option for a product (e.g., monthly vs. annual, introductory vs. regular). Accessed viaproduct.offers
orproduct.getOffer()
. ContainspricingPhases
.PricingPhase
: Details a specific period within an offer (e.g., free trial, intro price, regular price). Containsprice
,priceMicros
,currency
,billingPeriod
, etc.Receipt
: Represents the purchase data reported locally by the device's SDK. Accessed viastore.localReceipts
. Containstransactions
. Do not rely solely on this for entitlement.Transaction
: Represents a single purchase attempt within a receipt. ContainstransactionId
,state
,purchaseDate
, etc.VerifiedReceipt
: The result from yourstore.validator
. Accessed via theverified
event orstore.verifiedReceipts
. Contains acollection
ofVerifiedPurchase
objects and is the trusted source for entitlement.VerifiedPurchase
: Authoritative purchase data from the validator (includesid
,purchaseDate
,expiryDate
,isExpired
,renewalIntent
, etc.).
Migration:
Access metadata like
title
,description
directly from theProduct
object (store.get('id', platform).title
).Access pricing via
product.pricing
(for simple cases:product.offers[0].pricingPhases[0]
) or iterate throughproduct.offers[].pricingPhases[]
for complex offers/subscriptions.Check local transaction state via
store.findInLocalReceipts(product)?.state
.Check validated ownership and expiry via
store.owned(product)
or by inspectingstore.verifiedPurchases
. Do not rely on local receipts for subscription status or permanent non-consumable unlocks.
See the table in the main Migration Guide for a detailed mapping of old fields to new locations.
5. Event Handling (store.when()
)
store.when()
)Before:
store.when("filter").event(callback)
used filters (product id, type, alias) directly in thewhen
call. Events often related directly to aProduct
object that mixed states.After (v13+):
store.when().event(callback)
. Filters are removed fromwhen()
. Events are more specific to the data they relate to (productUpdated
,receiptUpdated
,approved
(for Transaction),verified
(for VerifiedReceipt),finished
(for Transaction)).
Migration:
Remove filters from
when()
calls (e.g., changestore.when('my_product').approved(...)
tostore.when().approved(...)
).Implement filtering logic inside your callback functions if needed (check
transaction.products[0].id
,receipt.collection[0].id
,product.id
).Adjust callbacks based on the new event signatures (e.g.,
approved
receives aTransaction
,verified
receives aVerifiedReceipt
).
updated
Event: Split intoproductUpdated
(for metadata changes) andreceiptUpdated
(for local purchase data changes). Update your listeners accordingly.owned
Event: Removed. Check ownership status withinverified
orreceiptUpdated
handlers usingstore.owned(product)
or by inspectingstore.verifiedPurchases
. See the main Migration Guide for strategies.error
Event (Product): Removed. Handle purchase errors via the promise returned byoffer.order()
orstore.requestPayment()
. General plugin errors are still caught bystore.error()
.
6. Ordering Products
Before:
store.order("productId")
orstore.order(product)
After (v13+):
offer.order(additionalData?)
. You now order a specificOffer
object obtained from aProduct
.
Migration:
Get the
Product
object:const product = CdvPurchase.store.get('productId', platform);
Get the desired
Offer
:const offer = product?.getOffer();
(orproduct?.getOffer('offerId')
if multiple offers exist).Call order on the offer:
if (offer) offer.order(additionalData);
7. Finishing and Verifying
Before:
product.verify()
andproduct.finish()
were called on the mixedProduct
object.After (v13+): Methods are now on
Transaction
,Receipt
, orVerifiedReceipt
objects.transaction.verify()
: Verifies the specific transaction's receipt data. Typically called in theapproved
handler.transaction.finish()
: Finishes/Acknowledges/Consumes the specific transaction.receipt.verify()
: Verifies all transactions within the local receipt (less common).receipt.finish()
: Finishes all transactions within the local receipt.verifiedReceipt.finish()
: Finishes the transaction(s) associated with the verified receipt. Typically called in theverified
handler.
Migration:
Typically, you'll call
transaction.verify()
in theapproved
handler andreceipt.finish()
(orverifiedReceipt.finish()
) in theverified
handler.
8. Enumerations
Before: Constants were attached directly to the
store
object (e.g.,store.CONSUMABLE
,store.ERR_SETUP
,store.DEBUG
).After (v13+): Constants are organized into proper enums within the
CdvPurchase
namespace:CdvPurchase.ProductType.CONSUMABLE
,CdvPurchase.ProductType.PAID_SUBSCRIPTION
, etc.CdvPurchase.Platform.APPLE_APPSTORE
,CdvPurchase.Platform.GOOGLE_PLAY
, etc.CdvPurchase.ErrorCode.SETUP
,CdvPurchase.ErrorCode.PURCHASE
, etc.CdvPurchase.LogLevel.DEBUG
,CdvPurchase.LogLevel.INFO
, etc.CdvPurchase.TransactionState.APPROVED
,CdvPurchase.TransactionState.FINISHED
, etc.
Migration:
Update references to use the new enum syntax (e.g., change
store.CONSUMABLE
toCdvPurchase.ProductType.CONSUMABLE
).Backward Compatibility: For easier migration, the old constants are currently still attached to the
store
object as properties (e.g.,CdvPurchase.store.CONSUMABLE
), but using the enums (CdvPurchase.ProductType.CONSUMABLE
) is strongly recommended for clarity, type safety, and future-proofing.
9. Braintree Integration
Version 13 introduces official Braintree support via the cordova-plugin-purchase-braintree
extension. If you were using previous unofficial Braintree integrations, you'll need to migrate to the new adapter structure, using store.initialize()
with Platform.BRAINTREE
and store.requestPayment()
. Refer to the Braintree Use Case.
Review these changes carefully and update your application code accordingly. Test thoroughly after migration, especially the purchase and validation flows. Consult the API documentation for detailed information on the new methods and classes.
Last updated