IAP instance
The object returned by createIAP<TEntitlement>(config).
interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
initialize(): Promise<void>;
refresh(): Promise<void>;
destroy(): Promise<void>;
purchase(productId: string): Promise<PurchaseResult<TEntitlement>>;
restorePurchases(): Promise<RestoreResult<TEntitlement>>;
getProducts(): Promise<Product[]>;
hasEntitlement(key: string): boolean;
getEntitlements(): TEntitlement[];
getEntitlement(key: string): TEntitlement | null;
on<K extends EventName<TEntitlement>>(
event: K,
handler: (payload: EventPayload<K, TEntitlement>) => void,
): Unsubscribe;
}initialize()
initialize(): Promise<void>One-time setup. Idempotent — calling twice resolves immediately the second time.
Steps:
- Loads cached entitlements from storage (warm cache).
- Resolves the native adapter (web → no-op stub, iOS/Android → cordova-plugin-purchase).
- Recovers any unfinished transactions from prior sessions (capped at
recoveryMaxBatch). - Wires the app-resume listener if
refreshOnResume: trueand@capacitor/appis installed. - If cache age exceeds
entitlementCacheTtlMs, schedules a backgroundrefresh()afterready. - Emits
ready.
After resolution, all read methods are safe.
refresh()
refresh(): Promise<void>Fetches /entitlements from your backend and replaces the local cache. Emits entitlements-changed if the new list differs from the previous (shallow compared).
Throws on transport failure (BACKEND_UNAVAILABLE, BACKEND_TIMEOUT, etc.). Doesn't auto-retry beyond the configured retries count.
try {
await iap.refresh();
} catch (error) {
if (isIAPError(error) && error.recoverable) {
// transient — UI can offer retry
}
}destroy()
destroy(): Promise<void>Tears down event listeners, removes the resume listener, disposes the native adapter. Does NOT clear the persisted entitlement cache. For multi-user logout flows, also clear your storage adapter.
Not safe mid-purchase
Don't call destroy() while iap.purchase() is in flight — the native acknowledge() step can become a no-op, leading to a 3-day Google auto-refund. Await the in-flight purchase first.
purchase(productId)
purchase(productId: string): Promise<PurchaseResult<TEntitlement>>Starts a purchase. Returns a discriminated union — does NOT throw on user cancellation, pending payment, or backend rejection.
type PurchaseResult<T> =
| { status: 'success'; productId: string; transaction: VerifiedTransaction; entitlements: T[] }
| { status: 'cancelled'; productId: string }
| { status: 'pending'; productId: string }
| { status: 'verification_failed'; productId: string; error: IAPError }
| { status: 'failed'; productId: string; error: IAPError };Throws only on programming errors:
IAPError(NOT_INITIALIZED)IAPError(ALREADY_IN_PROGRESS)— a purchase for the same productId is already runningIAPError(PRODUCT_NOT_FOUND)— productId not inconfig.productsIAPError(PLATFORM_NOT_SUPPORTED)— called on web
Emits purchase-started, then exactly one of: purchase-success (+ entitlements-changed), purchase-cancelled, purchase-pending, verification-failed, purchase-failed.
restorePurchases()
restorePurchases(): Promise<RestoreResult<TEntitlement>>Re-verifies all owned transactions with the backend. Wire to a "Restore Purchases" button.
interface RestoreResult<T> {
restored: number; // count of native transactions submitted
entitlements: T[]; // consolidated list after backend verification
}Throws IAPError on transport / verification failure. Wrap in try/catch.
Emits restore-started, then restore-completed (+ entitlements-changed if the list changed).
Empty owned list
If the platform store reports no owned transactions (fresh install, signed-out Apple ID), the library short-circuits — it does NOT call the backend and preserves whatever entitlements were already cached. To force-reconcile, call iap.refresh() afterward.
getProducts()
getProducts(): Promise<Product[]>Returns native pricing merged with configured metadata. One entry per product the platform store recognizes.
interface Product {
id: string;
type: 'subscription' | 'product' | 'consumable';
title: string;
description: string;
priceString: string; // localized, e.g. "$4.99"
priceMicros: string; // BigInt as string
currency: string; // ISO 4217, e.g. "USD"
}Products configured in createIAP({ products }) but not yet ingested by the platform store are silently skipped. On web, returns [].
Always render priceString
Apple and Google's developer agreements require displaying the localized price from the native API, not a hardcoded one. Rendering "$4.99" when the user's region shows €4.99 is a reviewability risk.
hasEntitlement(key)
hasEntitlement(key: string): booleanSynchronous read against the in-memory cache. O(1) (linear scan over a typically-small array).
getEntitlements()
getEntitlements(): TEntitlement[]Returns a defensive shallow copy of the entitlement array. Each entitlement object is frozen — you can read its fields but not mutate them.
getEntitlement(key)
getEntitlement(key: string): TEntitlement | nullReturns the frozen entitlement matching key, or null if not found.
on(event, handler)
on<K extends EventName<TEntitlement>>(
event: K,
handler: (payload: EventPayload<K, TEntitlement>) => void,
): UnsubscribeSubscribe to an event. Returns an unsubscribe function — you must call it when tearing down the subscription.
const unsubscribe = iap.on('entitlements-changed', ({ entitlements }) => {
store.setEntitlements(entitlements);
});
// later
unsubscribe();Available events: ready, purchase-started, purchase-success, purchase-cancelled, purchase-pending, purchase-failed, verification-failed, restore-started, restore-completed, entitlements-changed, price-stale, error.
See Events guide and EventMap for full payload definitions.
See also
- Events — full payload reference
- Error handling — every code each method can throw
- Configuration — what gets passed to
createIAP