///
///
///
///
///
///
///
///
///
///
///
///
///
///
/**
* Namespace for the cordova-plugin-purchase plugin.
*
* All classes, enumerations and variables defined by the plugin are in this namespace.
*
* Throughout the documentation, in order to keep examples readable, we omit the `CdvPurchase` prefix.
*
* When you see, for example `ProductType.PAID_SUBSCRIPTION`, it refers to `CdvPurchase.ProductType.PAID_SUBSCRIPTION`.
*
* In your code, you should access members directly through the CdvPurchase namespace:
*
* ```ts
* // Recommended approach (works reliably with minification)
* CdvPurchase.store.initialize();
* CdvPurchase.store.register({
* id: 'my-product',
* type: CdvPurchase.ProductType.PAID_SUBSCRIPTION,
* platform: CdvPurchase.Platform.APPLE_APPSTORE
* });
* ```
*
* Note: Using destructuring with the namespace may cause issues with minification tools:
*
* ```ts
* // NOT recommended - may cause issues with minification tools like Terser
* const { store, ProductType, Platform, LogLevel } = CdvPurchase;
* ```
*/
namespace CdvPurchase {
/**
* Current release number of the plugin.
*/
export const PLUGIN_VERSION = '13.15.0';
/**
* Entry class of the plugin.
*/
export class Store {
/**
* Payment platform adapters.
*/
private adapters = new Internal.Adapters();
/**
* Retrieve a platform adapter.
*
* The platform adapter has to have been initialized before.
*
* @see {@link initialize}
*/
getAdapter(platform: Platform) {
return this.adapters.find(platform);
}
/**
* List of registered products.
*
* Products are added to this list of products by {@link Store.register}, an internal job will defer loading to the platform adapters.
*/
private registeredProducts = new Internal.RegisteredProducts();
/** Logger */
public log = new Logger(this);
/**
* Verbosity level used by the plugin logger
*
* Set to:
*
* - LogLevel.QUIET or 0 to disable all logging (default)
* - LogLevel.ERROR or 1 to show only error messages
* - LogLevel.WARNING or 2 to show warnings and errors
* - LogLevel.INFO or 3 to also show information messages
* - LogLevel.DEBUG or 4 to enable internal debugging messages.
*
* @see {@link LogLevel}
*/
public verbosity: LogLevel = LogLevel.ERROR;
/**
* Return the identifier of the user for your application.
*
* **Note:** Apple AppStore requires an UUIDv4 if you want it to appear as the "appAccountToken" in
* the transaction data.
*/
public applicationUsername?: string | (() => string | undefined);
/**
* Get the application username as a string by either calling or returning {@link Store.applicationUsername}
*/
getApplicationUsername(): string | undefined {
if (this.applicationUsername instanceof Function) return this.applicationUsername();
return this.applicationUsername;
}
/**
* URL or implementation of the receipt validation service
*
* @example
* Define the validator as a string
* ```ts
* CdvPurchase.store.validator = "https://validator.iaptic.com/v1/validate?appName=test"
* ```
*
* @example
* Define the validator as a function
* ```ts
* CdvPurchase.store.validator = (receipt, callback) => {
* callback({
* ok: true,
* data: {
* // see CdvPurchase.Validator.Response.Payload for details
* }
* })
* }
* ```
*
* @see {@link CdvPurchase.Validator.Response.Payload}
*/
public validator: string | Validator.Function | Validator.Target | undefined;
/**
* When adding information to receipt validation requests, those can serve different functions:
*
* - handling support requests
* - fraud detection
* - analytics
* - tracking
*
* Make sure the value your select is in line with your application's privacy policy and your users' tracking preference.
*
* @example
* CdvPurchase.store.validator_privacy_policy = [
* 'fraud', 'support', 'analytics', 'tracking'
* ]
*/
public validator_privacy_policy: PrivacyPolicyItem | PrivacyPolicyItem[] | undefined;
/** List of callbacks for the "ready" events */
private _readyCallbacks = new Internal.ReadyCallbacks(this.log);
/** Listens to adapters */
private listener: Internal.StoreAdapterListener;
/** Callbacks when a product definition was updated */
private updatedCallbacks = new Internal.Callbacks(this.log, 'productUpdated()');
/** Callback when a receipt was updated */
private updatedReceiptsCallbacks = new Internal.Callbacks(this.log, 'receiptUpdated()');
/** Callbacks when a product is owned */
// private ownedCallbacks = new Callbacks();
/** Callbacks when a transaction is initiated */
private initiatedCallbacks = new Internal.Callbacks(this.log, 'initiated()');
/** Callbacks when a transaction has been approved */
private approvedCallbacks = new Internal.Callbacks(this.log, 'approved()');
/** Callbacks when a transaction has been finished */
private finishedCallbacks = new Internal.Callbacks(this.log, 'finished()');
/** Callbacks when a transaction is pending */
private pendingCallbacks = new Internal.Callbacks(this.log, 'pending()');
/** Callbacks when a receipt has been validated */
private verifiedCallbacks = new Internal.Callbacks(this.log, 'verified()');
/** Callbacks when a receipt has been validated */
private unverifiedCallbacks = new Internal.Callbacks(this.log, 'unverified()');
/** Callbacks when all receipts have been loaded */
private receiptsReadyCallbacks = new Internal.Callbacks(this.log, 'receiptsReady()', true);
/** Callbacks when all receipts have been verified */
private receiptsVerifiedCallbacks = new Internal.Callbacks(this.log, 'receiptsVerified()', true);
/** Callbacks for errors */
private errorCallbacks = new Internal.Callbacks(this.log, 'error()');
/** Per-platform storefront cache and change notifications. */
private _storefronts = new Internal.Storefronts(this.log.child('Storefronts'));
/** Internal implementation of the receipt validation service integration */
private _validator: Internal.Validator;
/** Monitor state changes for transactions */
private transactionStateMonitors: Internal.TransactionStateMonitors;
/** Monitor subscription expiry */
private expiryMonitor: Internal.ExpiryMonitor;
constructor() {
const store = this;
this.listener = new Internal.StoreAdapterListener({
updatedCallbacks: this.updatedCallbacks,
updatedReceiptCallbacks: this.updatedReceiptsCallbacks,
initiatedCallbacks: this.initiatedCallbacks,
approvedCallbacks: this.approvedCallbacks,
finishedCallbacks: this.finishedCallbacks,
pendingCallbacks: this.pendingCallbacks,
receiptsReadyCallbacks: this.receiptsReadyCallbacks,
}, this.log);
this.transactionStateMonitors = new Internal.TransactionStateMonitors(this.when());
this._validator = new Internal.Validator({
adapters: this.adapters,
getApplicationUsername: this.getApplicationUsername.bind(this),
get localReceipts() { return store.localReceipts; },
get validator() { return store.validator; },
get validator_privacy_policy() { return store.validator_privacy_policy; },
verifiedCallbacks: this.verifiedCallbacks,
unverifiedCallbacks: this.unverifiedCallbacks,
finish: (receipt: VerifiedReceipt) => this.finish(receipt),
}, this.log);
new Internal.ReceiptsMonitor({
hasLocalReceipts: () => this.localReceipts.length > 0,
hasValidator: () => !!this.validator,
numValidationRequests: () => this._validator.numRequests,
numValidationResponses: () => this._validator.numResponses,
off: this.off.bind(this),
when: this.when.bind(this),
receiptsVerified: () => { store.receiptsVerifiedCallbacks.trigger(undefined, 'receipts_monitor_controller'); },
log: this.log,
}).launch();
this.expiryMonitor = new Internal.ExpiryMonitor({
get localReceipts() {
// Only use local receipts if there's no validator configured
return store.validator ? [] : store.localReceipts;
},
get verifiedReceipts() { return store.verifiedReceipts; },
onTransactionExpired(transaction) {
store.log.debug(`Local transaction expired (${transaction.transactionId}), refreshing purchases`);
if (!store.validator) {
const productId = transaction.products[0]?.id;
if (productId && !store.owned(productId)) {
store.updatedReceiptsCallbacks.trigger(transaction.parentReceipt, 'expiry_monitor_transaction_expired');
}
}
},
onVerifiedPurchaseExpired(verifiedPurchase, receipt) {
store.verify(receipt.sourceReceipt);
},
}, this.log);
this.expiryMonitor.launch();
}
/**
* Register a product.
*
* @example
* store.register([{
* id: 'subscription1',
* type: ProductType.PAID_SUBSCRIPTION,
* platform: Platform.APPLE_APPSTORE,
* }, {
* id: 'subscription1',
* type: ProductType.PAID_SUBSCRIPTION,
* platform: Platform.GOOGLE_PLAY,
* }, {
* id: 'consumable1',
* type: ProductType.CONSUMABLE,
* platform: Platform.BRAINTREE,
* }]);
*
* // Can also be used in development to register test products
* store.register([{
* id: 'my-custom-product',
* type: CdvPurchase.ProductType.CONSUMABLE,
* platform: CdvPurchase.Platform.TEST,
* title: '...',
* description: 'A custom test consumable product',
* pricing: {
* price: '$2.99',
* currency: 'USD',
* priceMicros: 2990000
* }
* }]);
*/
register(product: IRegisterProduct | Test.IRegisterTestProduct | (IRegisterProduct | Test.IRegisterTestProduct)[]) {
const errors = this.registeredProducts.add(product);
errors.forEach(error => {
store.errorCallbacks.trigger(error, 'register_error');
this.log.error(error);
});
}
private initializedHasBeenCalled = false;
/**
* Call to initialize the in-app purchase plugin.
*
* @param platforms - List of payment platforms to initialize, default to Store.defaultPlatform().
*/
async initialize(platforms: (Platform | PlatformWithOptions)[] = [this.defaultPlatform()]): Promise {
if (this.initializedHasBeenCalled) {
this.log.warn('store.initialized() has been called already.');
return [];
}
this.log.info('initialize(' + JSON.stringify(platforms) + ') v' + PLUGIN_VERSION);
this.initializedHasBeenCalled = true;
this.lastUpdate = +new Date();
const store = this;
const ret = this.adapters.initialize(platforms, {
error: this.triggerError.bind(this),
get verbosity() { return store.verbosity; },
getApplicationUsername() { return store.getApplicationUsername() },
get listener() { return store.listener; },
get log() { return store.log; },
get registeredProducts() { return store.registeredProducts; },
get storefronts() { return store._storefronts; },
apiDecorators: {
canPurchase: this.canPurchase.bind(this),
owned: this.owned.bind(this),
finish: this.finish.bind(this),
order: this.order.bind(this),
verify: this.verify.bind(this),
},
});
ret.then(() => {
this._readyCallbacks.trigger('initialize_promise_resolved');
this.listener.setSupportedPlatforms(this.adapters.list.filter(a => a.isSupported).map(a => a.id));
});
return ret;
}
/**
* @deprecated - use store.initialize(), store.update() or store.restorePurchases()
*/
refresh() {
throw new Error("use store.initialize() or store.update()");
}
/** Stores the last time the store was updated (or initialized), to skip calls in quick succession. */
private lastUpdate: number = 0;
/**
* Avoid invoking store.update() if the most recent call occurred within this specific number of milliseconds.
*/
minTimeBetweenUpdates: number = 600000;
/**
* Call to refresh the price of products and status of purchases.
*/
async update() {
this.log.info('update()');
if (!this._readyCallbacks.isReady) {
this.log.warn('Do not call store.update() at startup! It is meant to reload the price of products (if needed) long after initialization.');
return;
}
const now = +new Date();
if (this.lastUpdate > now - this.minTimeBetweenUpdates) {
this.log.info('Skipping store.update() as the last call occurred less than store.minTimeBetweenUpdates millis ago.');
return;
}
this.lastUpdate = now;
// Load products metadata
for (const registration of this.registeredProducts.byPlatform()) {
const adapter = this.adapters.findReady(registration.platform);
const products = await adapter?.loadProducts(registration.products);
products?.forEach(p => {
if (p instanceof Product) this.updatedCallbacks.trigger(p, 'update_has_loaded_products');
});
if (adapter) {
this._storefronts.refreshWith(adapter).catch(() => { /* tolerated */ });
}
}
}
/**
* Register a callback to be called when the plugin is ready.
*
* This happens when all the platforms are initialized and their products loaded.
*/
ready(cb: Callback): void { this._readyCallbacks.add(cb); }
/** true if the plugin is initialized and ready */
get isReady(): boolean { return this._readyCallbacks.isReady; }
/**
* Register event callbacks.
*
* Events overview:
* - `productUpdated`: Called when product metadata is loaded from the store
* - `receiptUpdated`: Called when local receipt information changes (ownership status change, for example)
* - `verified`: Called after successful receipt validation (requires a receipt validator)
*
* @example
* // Monitor ownership with receipt validation
* store.when()
* .approved(transaction => transaction.verify())
* .verified(receipt => {
* if (store.owned("my-product")) {
* // Product is owned and verified
* }
* });
*
* @example
* // Monitor ownership without receipt validation
* store.when().receiptUpdated(receipt => {
* if (store.owned("my-product")) {
* // Product is owned according to local data
* }
* });
*/
when() {
const ret: When = {
productUpdated: (cb: Callback, callbackName?: string) => (this.updatedCallbacks.push(cb, callbackName), ret),
receiptUpdated: (cb: Callback, callbackName?: string) => (this.updatedReceiptsCallbacks.push(cb, callbackName), ret),
updated: (cb: Callback, callbackName?: string) => (this.updatedCallbacks.push(cb, callbackName), this.updatedReceiptsCallbacks.push(cb, callbackName), ret),
// owned: (cb: Callback) => (this.ownedCallbacks.push(cb), ret),
approved: (cb: Callback, callbackName?: string) => (this.approvedCallbacks.push(cb, callbackName), ret),
initiated: (cb: Callback, callbackName?: string) => (this.initiatedCallbacks.push(cb, callbackName), ret),
pending: (cb: Callback, callbackName?: string) => (this.pendingCallbacks.push(cb, callbackName), ret),
finished: (cb: Callback, callbackName?: string) => (this.finishedCallbacks.push(cb, callbackName), ret),
verified: (cb: Callback, callbackName?: string) => (this.verifiedCallbacks.push(cb, callbackName), ret),
unverified: (cb: Callback, callbackName?: string) => (this.unverifiedCallbacks.push(cb, callbackName), ret),
receiptsReady: (cb: Callback, callbackName?: string) => (this.receiptsReadyCallbacks.push(cb, callbackName), ret),
receiptsVerified: (cb: Callback, callbackName?: string) => (this.receiptsVerifiedCallbacks.push(cb, callbackName), ret),
storefrontUpdated: (cb: Callback, callbackName?: string) =>
(this._storefronts.listen(cb, callbackName), ret),
};
return ret;
}
/**
* Remove a callback from any listener it might have been added to.
*/
off(callback: Callback) {
this.updatedCallbacks.remove(callback as any);
this.updatedReceiptsCallbacks.remove(callback as any);
this.approvedCallbacks.remove(callback as any);
this.finishedCallbacks.remove(callback as any);
this.pendingCallbacks.remove(callback as any);
this.verifiedCallbacks.remove(callback as any);
this.unverifiedCallbacks.remove(callback as any);
this.receiptsReadyCallbacks.remove(callback as any);
this.receiptsVerifiedCallbacks.remove(callback as any);
this.errorCallbacks.remove(callback as any);
this._readyCallbacks.remove(callback as any);
this._storefronts.off(callback as any);
}
/**
* Setup a function to be notified of changes to a transaction state.
*
* @param transaction The transaction to monitor.
* @param onChange Function to be called when the transaction status changes.
* @return A monitor which can be stopped with `monitor.stop()`
*
* @example
* const monitor = store.monitor(transaction, state => {
* console.log('new state: ' + state);
* if (state === TransactionState.FINISHED)
* monitor.stop();
* });
*/
monitor(transaction: Transaction, onChange: Callback, callbackName: string): TransactionMonitor {
return this.transactionStateMonitors.start(
transaction,
Utils.safeCallback(this.log, 'monitor()', onChange, callbackName, 'transactionStateMonitors_stateChanged'));
}
/**
* List of all active products.
*
* Products are active if their details have been successfully loaded from the store.
*/
get products(): Product[] {
// concatenate products all all active platforms
return ([] as Product[]).concat(...this.adapters.list.map(a => a.products));
}
/**
* Find a product from its id and platform
*
* @param productId Product identifier on the platform.
* @param platform The product the product exists in. Can be omitted if you're only using a single payment platform.
*/
get(productId: string, platform?: Platform): Product | undefined {
return this.adapters.findReady(platform)?.products.find(p => p.id === productId);
}
/**
* List of all receipts present on the device.
*/
get localReceipts(): Receipt[] {
// concatenate products all all active platforms
return ([] as Receipt[]).concat(...this.adapters.list.map(a => a.receipts));
}
/** List of all transaction from the local receipts. */
get localTransactions(): Transaction[] {
const ret: Transaction[] = [];
for (const receipt of this.localReceipts) {
ret.push(...receipt.transactions);
}
return ret;
}
/**
* List of receipts verified with the receipt validation service.
*
* Those receipt contains more information and are generally more up-to-date than the local ones.
*/
get verifiedReceipts(): VerifiedReceipt[] {
return this._validator.verifiedReceipts;
}
/**
* List of all purchases from the verified receipts.
*/
get verifiedPurchases(): VerifiedPurchase[] {
return Internal.VerifiedReceipts.getVerifiedPurchases(this.verifiedReceipts);
}
/**
* Find the last verified purchase for a given product, from those verified by the receipt validator.
*/
findInVerifiedReceipts(product: Product): VerifiedPurchase | undefined {
return Internal.VerifiedReceipts.find(this.verifiedReceipts, product);
}
/**
* Find the latest transaction for a given product, from those reported by the device.
*/
findInLocalReceipts(product: Product): Transaction | undefined {
return Internal.LocalReceipts.find(this.localReceipts, product);
}
/** Return true if a product or offer can be purchased */
private canPurchase(offer: Offer | Product) {
const product = (offer instanceof Offer) ? this.get(offer.productId, offer.platform) : offer;
const adapter = this.adapters.findReady(offer.platform);
if (!adapter?.checkSupport('order')) return false;
return Internal.LocalReceipts.canPurchase(this.localReceipts, product);
}
/**
* Return true if a product is owned
*
* Important: The value will be false when the app starts and will only become
* true after purchase receipts have been loaded and validated. Without receipt validation,
* it might remain false depending on the platform, make sure to store the ownership status
* of non-consumable products in some way.
*
* @param product - The product object or identifier of the product.
*/
owned(product: { id: string; platform?: Platform } | string) {
return Internal.owned({
product: typeof product === 'string' ? { id: product } : product,
verifiedReceipts: this.validator ? this.verifiedReceipts : undefined,
localReceipts: this.localReceipts,
});
}
/**
* Place an order for a given offer.
*/
async order(offer: Offer, additionalData?: AdditionalData): Promise {
this.log.info(`order(${offer.productId})`);
const adapter = this.adapters.findReady(offer.platform);
if (!adapter) return storeError(ErrorCode.PAYMENT_NOT_ALLOWED, 'Adapter not found or not ready (' + offer.platform + ')', offer.platform, null);
const ret = await adapter.order(offer, additionalData || {});
if (ret && 'isError' in ret) store.triggerError(ret);
// Account may have switched during checkout — refresh storefront in the background.
this._storefronts.refreshWith(adapter).catch(() => { /* tolerated */ });
return ret;
}
/**
* Request a payment.
*
* A payment is a custom amount to charge the user. Make sure the selected payment platform
* supports Payment Requests.
*
* @param paymentRequest Parameters of the payment request
* @param additionalData Additional parameters
*/
requestPayment(paymentRequest: PaymentRequest, additionalData?: AdditionalData): PaymentRequestPromise {
const adapter = this.adapters.findReady(paymentRequest.platform);
if (!adapter)
return PaymentRequestPromise.failed(ErrorCode.PAYMENT_NOT_ALLOWED, 'Adapter not found or not ready (' + paymentRequest.platform + ')', paymentRequest.platform, null);
// fill-in missing total amount as the sum of all items.
if (!paymentRequest.amountMicros) {
paymentRequest.amountMicros = 0;
for (const item of paymentRequest.items) {
paymentRequest.amountMicros += item?.pricing?.priceMicros ?? 0;
}
}
// fill-in the missing if set in the items.
if (!paymentRequest.currency) {
for (const item of paymentRequest.items) {
if (item?.pricing?.currency) {
paymentRequest.currency = item.pricing.currency;
}
}
}
else {
for (const item of paymentRequest.items) {
if (item?.pricing?.currency) {
if (paymentRequest.currency !== item.pricing.currency) {
return PaymentRequestPromise.failed(ErrorCode.PAYMENT_INVALID, 'Currencies do not match', paymentRequest.platform, item.id);
}
}
else if (item?.pricing) {
item.pricing.currency = paymentRequest.currency;
}
}
}
// fill-in item amount when there's just 1 item.
if (paymentRequest.items.length === 1) {
const item = paymentRequest.items[0];
if (item && !item.pricing) {
item.pricing = {
priceMicros: paymentRequest.amountMicros ?? 0,
currency: paymentRequest.currency,
}
}
}
const promise = new PaymentRequestPromise();
adapter.requestPayment(paymentRequest, additionalData).then(result => {
this._storefronts.refreshWith(adapter).catch(() => { /* tolerated */ });
promise.trigger(result);
if (result instanceof Transaction) {
const onStateChange = (state: TransactionState) => {
promise.trigger(result);
if (result.state === TransactionState.FINISHED)
monitor.stop();
}
const monitor = this.monitor(result, onStateChange, 'requestPayment_onStateChange');
}
});
return promise;
}
/**
* Returns true if a platform supports the requested functionality.
*
* @example
* store.checkSupport(Platform.APPLE_APPSTORE, 'requestPayment');
* // => false
*/
checkSupport(platform: Platform, functionality: PlatformFunctionality): boolean {
const adapter = this.adapters.find(platform);
if (!adapter) return false; // the selected adapter hasn't been initialized
return adapter.checkSupport(functionality);
}
/**
* Verify a receipt or transacting with the receipt validation service.
*
* This will be called from the Receipt or Transaction objects using the API decorators.
*/
private async verify(receiptOrTransaction: Receipt | Transaction) {
this.log.info(`verify(${receiptOrTransaction.className})`);
this._validator.add(receiptOrTransaction);
// Run validation after 200ms, so if the same receipt is to be validated multiple times it will just create one call.
setTimeout(() => this._validator.run(), 200);
}
/**
* Finalize a transaction.
*
* This will be called from the Receipt, Transaction or VerifiedReceipt objects using the API decorators.
*
* If the transaction has already been consumed or acknowledged according to the verification API,
* the native platform's finish method will be skipped to avoid errors.
*/
private async finish(receipt: Transaction | Receipt | VerifiedReceipt) {
this.log.info(`finish(${receipt.className})`);
const transactions =
receipt instanceof VerifiedReceipt
? receipt.sourceReceipt.transactions
: receipt instanceof Receipt
? receipt.transactions
: [receipt];
transactions.forEach(transaction => {
// Check if this transaction has already been consumed or acknowledged according to verification API
let skipNativeFinish = false;
if (this.validator && receipt instanceof VerifiedReceipt) {
// Find matching purchase in the verified collection
const verifiedPurchase = receipt.collection.find(p => {
// Match by transactionId if available
return (p.transactionId && p.transactionId === transaction.transactionId);
});
if (verifiedPurchase) {
// Check if transaction is acknowledged
if (verifiedPurchase.isAcknowledged === true) {
this.log.info(`Transaction ${transaction.transactionId} already acknowledged according to verification API`);
transaction.isAcknowledged = true;
skipNativeFinish = true;
}
// Check if transaction is consumed
if (verifiedPurchase.isConsumed === true) {
this.log.info(`Transaction ${transaction.transactionId} already consumed according to verification API`);
transaction.isConsumed = true;
skipNativeFinish = true;
}
}
}
const adapter = this.adapters.findReady(transaction.platform);
if (adapter?.canSkipFinish && skipNativeFinish && transaction.state === TransactionState.APPROVED) {
transaction.state = TransactionState.FINISHED;
}
else {
const adapter = this.adapters.findReady(transaction.platform)?.finish(transaction);
}
});
}
/**
* Replay the users transactions.
*
* This method exists to cover an Apple AppStore requirement.
*/
async restorePurchases() {
let error: IError | undefined;
for (const adapter of this.adapters.list) {
if (adapter.ready) {
error = error ?? await adapter.restorePurchases();
// Restore often implies a login or account switch — refresh storefront.
this._storefronts.refreshWith(adapter).catch(() => { /* tolerated */ });
}
}
return error;
}
/**
* Open the subscription management interface for the selected platform.
*
* If platform is not specified, the first available platform will be used.
*
* @example
* const activeSubscription: Purchase = // ...
* store.manageSubscriptions(activeSubscription.platform);
*/
async manageSubscriptions(platform?: Platform): Promise {
this.log.info('manageSubscriptions()');
const adapter = this.adapters.findReady(platform);
if (!adapter) return storeError(ErrorCode.SETUP, "Found no adapter ready to handle 'manageSubscription'", platform ?? null, null);
return adapter.manageSubscriptions();
}
/**
* Opens the billing methods page on AppStore, Play, Microsoft, ...
*
* From this page, the user can update their payment methods.
*
* If platform is not specified, the first available platform will be used.
*
* @example
* if (purchase.isBillingRetryPeriod)
* store.manageBilling(purchase.platform);
*/
async manageBilling(platform?: Platform): Promise {
this.log.info('manageBilling()');
const adapter = this.adapters.findReady(platform);
if (!adapter) return storeError(ErrorCode.SETUP, "Found no adapter ready to handle 'manageBilling'", platform ?? null, null);
return adapter.manageBilling();
}
/**
* Retrieve the billing country code from the platform's storefront.
*
* Returns a `Storefront` object with the platform and its ISO 3166-1
* alpha-2 country code (e.g., "US", "FR"). The country code may be
* undefined if the underlying fetch has not yet completed or failed —
* the platform is still reported. Returns `undefined` only when no
* matching adapter is ready.
*
* The cache is populated before the `storeReady` event fires (with a
* best-effort timeout), and refreshed after orders and `restorePurchases()`.
*
* @param platform - Optional platform. If omitted, returns the first
* cached non-empty storefront, or a `{ platform, countryCode: undefined }`
* object for the first ready adapter.
*
* @example
* const storefront = store.getStorefront();
* if (storefront?.countryCode) {
* console.log(`Billing country: ${storefront.countryCode}`);
* }
*/
getStorefront(platform?: Platform): Storefront | undefined {
if (platform) {
const adapter = this.adapters.findReady(platform);
if (!adapter) return undefined;
return this._storefronts.getValueFor(platform);
}
const cached = this._storefronts.getValueFor();
if (cached) return cached;
const firstReady = this.adapters.findReady();
if (!firstReady) return undefined;
return { platform: firstReady.id, countryCode: undefined };
}
/**
* The default payment platform to use depending on the OS.
*
* - on iOS: `APPLE_APPSTORE`
* - on Android: `GOOGLE_PLAY`
*/
defaultPlatform(): Platform {
switch (Utils.platformId()) {
case 'android': return Platform.GOOGLE_PLAY;
case 'ios': return Platform.APPLE_APPSTORE;
default: return Platform.TEST;
}
}
/**
* Register an error handler.
*
* @param error An error callback that takes the error as an argument
*
* @example
* store.error(function(error) {
* console.error('CdvPurchase ERROR: ' + error.message);
* });
*/
error(error: Callback): void {
this.errorCallbacks.push(error);
}
/**
* Trigger an error event.
*
* @internal
*/
triggerError(error: IError) {
this.errorCallbacks.trigger(error, 'triggerError');
}
/**
* Version of the plugin currently installed.
*/
public version = PLUGIN_VERSION;
}
/**
* The global store object.
*/
export let store: Store;
//
// Documentation for sub-namespaces
//
/**
* @internal
*
* This namespace contains things never meant for being used directly by the user of the plugin.
*/
export namespace Internal {}
}
// Create the CdvPurchase.store object at startup.
if (window.cordova) {
setTimeout(initCDVPurchase, 0); // somehow with Cordova this needs to be delayed.
}
else {
initCDVPurchase();
}
/** @private */
function initCDVPurchase() {
console.log('Create CdvPurchase...');
const oldStore = window.CdvPurchase?.store;
window.CdvPurchase = CdvPurchase;
if (oldStore) {
window.CdvPurchase.store = oldStore;
}
else {
window.CdvPurchase.store = new CdvPurchase.Store();
}
// Let's maximize backward compatibility
Object.assign(window.CdvPurchase.store, CdvPurchase.LogLevel, CdvPurchase.ProductType, CdvPurchase.ErrorCode, CdvPurchase.Platform);
}
// Ensure utility are included when compiling typescript.
///