Getting Started
Copy a setup prompt with the install steps and the full markdown guide for this plugin.
Set up this Capacitor plugin in the project.
Use the package manager already used by the project.
Install these package(s): `@capgo/native-purchases`
Run the required Capacitor sync/update step after installation.
Read this markdown guide for the full setup steps: https://raw.githubusercontent.com/Cap-go/website/refs/heads/main/apps/docs/src/content/docs/docs/plugins/native-purchases/getting-started.mdx
Use that guide for platform-specific steps, native file edits, permissions, config changes, imports, and usage setup.
If that guide references other docs pages, read them too.
-
Install the package
Terminal window bun add @capgo/native-purchases -
Sync with native projects
Terminal window bunx cap sync -
Check billing support
import { NativePurchases } from '@capgo/native-purchases';const { isBillingSupported } = await NativePurchases.isBillingSupported();if (!isBillingSupported) {throw new Error('Billing is not available on this device');} -
Load products directly from the stores
import { NativePurchases, PURCHASE_TYPE } from '@capgo/native-purchases';const { products } = await NativePurchases.getProducts({productIdentifiers: ['com.example.premium.monthly','com.example.premium.yearly','com.example.one_time_unlock'],productType: PURCHASE_TYPE.SUBS, // Use PURCHASE_TYPE.INAPP for one‑time products});products.forEach((product) => {console.log(product.title, product.priceString);}); -
Implement purchase & restore flows
import { NativePurchases, PURCHASE_TYPE } from '@capgo/native-purchases';const monthlyPlanId = 'monthly-plan'; // Base Plan ID from Google Play Consoleconst transaction = await NativePurchases.purchaseProduct({productIdentifier: 'com.example.premium.monthly',planIdentifier: monthlyPlanId, // REQUIRED for Android subscriptions, ignored on iOSproductType: PURCHASE_TYPE.SUBS,quantity: 1,});console.log('Transaction ID', transaction.transactionId);await NativePurchases.restorePurchases();- Create in-app products and subscriptions in App Store Connect.
- Use StoreKit Local Testing or Sandbox testers for QA.
- No manifest edits required. Make sure your products are approved.
- Create in-app products and subscriptions in Google Play Console.
- Upload at least an internal test build and add license testers.
- Add the billing permission to
AndroidManifest.xml:
<uses-permission android:name="com.android.vending.BILLING" /> - Create in-app products and subscriptions in App Store Connect.
Purchase service example
Section titled “Purchase service example”import { NativePurchases, PURCHASE_TYPE, Transaction } from '@capgo/native-purchases';import { Capacitor } from '@capacitor/core';
class PurchaseService { private premiumProduct = 'com.example.premium.unlock'; private monthlySubId = 'com.example.premium.monthly'; private monthlyPlanId = 'monthly-plan'; // Base Plan ID (Android only)
async initialize() { const { isBillingSupported } = await NativePurchases.isBillingSupported(); if (!isBillingSupported) throw new Error('Billing unavailable');
const { products } = await NativePurchases.getProducts({ productIdentifiers: [this.premiumProduct, this.monthlySubId], productType: PURCHASE_TYPE.SUBS, });
console.log('Loaded products', products);
if (Capacitor.getPlatform() === 'ios') { NativePurchases.addListener('transactionUpdated', (transaction) => { this.handleTransaction(transaction); }); } }
async buyPremium(appAccountToken?: string) { const transaction = await NativePurchases.purchaseProduct({ productIdentifier: this.premiumProduct, productType: PURCHASE_TYPE.INAPP, appAccountToken, });
await this.processTransaction(transaction); }
async buyMonthly(appAccountToken?: string) { const transaction = await NativePurchases.purchaseProduct({ productIdentifier: this.monthlySubId, planIdentifier: this.monthlyPlanId, // REQUIRED for Android subscriptions productType: PURCHASE_TYPE.SUBS, appAccountToken, });
await this.processTransaction(transaction); }
async restore() { await NativePurchases.restorePurchases(); await this.refreshEntitlements(); }
async openManageSubscriptions() { await NativePurchases.manageSubscriptions(); }
private async processTransaction(transaction: Transaction) { this.unlockContent(transaction.productIdentifier); this.validateOnServer(transaction).catch(console.error); }
private unlockContent(productIdentifier: string) { // persist entitlement locally console.log('Unlocked', productIdentifier); }
private async refreshEntitlements() { const { purchases } = await NativePurchases.getPurchases({ productType: PURCHASE_TYPE.SUBS, }); console.log('Current purchases', purchases); }
private async handleTransaction(transaction: Transaction) { console.log('StoreKit transaction update:', transaction); await this.processTransaction(transaction); }
private async validateOnServer(transaction: Transaction) { await fetch('/api/validate-purchase', { method: 'POST', body: JSON.stringify({ transactionId: transaction.transactionId, receipt: transaction.receipt, purchaseToken: transaction.purchaseToken, }), }); }}Required purchase options
Section titled “Required purchase options”| Option | Platform | Description |
|---|---|---|
productIdentifier | iOS + Android | SKU/Product ID configured in App Store Connect / Google Play Console. |
productType | Android only | PURCHASE_TYPE.INAPP or PURCHASE_TYPE.SUBS. Defaults to INAPP. Always set to SUBS for subscriptions. |
planIdentifier | Android subscriptions | Base Plan ID from Google Play Console. Required for subscriptions, ignored on iOS and in-app purchases. |
billingPlanType | iOS subscriptions | StoreKit billing plan to purchase. Use 'monthly' for monthly billing with a 12-month commitment when product.pricingTerms exposes that option. |
quantity | iOS | Only for in-app purchases, defaults to 1. Android always purchases one item. |
appAccountToken | iOS + Android | UUID/string linking the purchase to your user. Required to be UUID on iOS; Android accepts any obfuscated string up to 64 chars. |
isConsumable | Android | Set to true to auto-consume tokens after granting entitlement for consumables. Defaults to false. |
Checking entitlement status
Section titled “Checking entitlement status”Use getPurchases() for a cross-platform view of every transaction the stores report:
import { NativePurchases, PURCHASE_TYPE } from '@capgo/native-purchases';
const { purchases } = await NativePurchases.getPurchases({ productType: PURCHASE_TYPE.SUBS,});
purchases.forEach((purchase) => { if (purchase.isActive && purchase.expirationDate) { console.log('iOS sub active until', purchase.expirationDate); }
const isAndroidIapValid = ['PURCHASED', '1'].includes(purchase.purchaseState ?? '') && purchase.isAcknowledged;
if (isAndroidIapValid) { console.log('Grant in-app entitlement for', purchase.productIdentifier); }});Platform behavior
Section titled “Platform behavior”- iOS: Subscriptions include
isActive,expirationDate,willCancel, and StoreKit 2 listener support. In-app purchases require server receipt validation. - Android:
isActive/expirationDateare not populated; call the Google Play Developer API with thepurchaseTokenfor authoritative status.purchaseStatemust bePURCHASEDandisAcknowledgedmust betrue.
API quick reference
Section titled “API quick reference”isBillingSupported()– check for StoreKit / Google Play availability.getProduct()/getProducts()– fetch price, localized title, description, intro offers, and supported iOS pricing terms.purchaseProduct()– initiate StoreKit 2 or Billing client purchase flow, including iOS monthly commitment billing plans.restorePurchases()– replay historical purchases and sync to current device.getPurchases()– list all iOS transactions or Play Billing purchases.manageSubscriptions()– open the native subscription management UI.addListener('transactionUpdated')– handle pending StoreKit 2 transactions when your app starts (iOS only).
Best practices
Section titled “Best practices”- Show store pricing – Apple requires displaying
product.titleandproduct.priceString; never hardcode. - Use
appAccountToken– deterministically generate a UUID (v5) from your user ID to link purchases to accounts. - Validate server-side – send
receipt(iOS) /purchaseToken(Android) to your backend for verification. - Handle errors gracefully – check for user cancellations, network failures, and unsupported billing environments.
- Test thoroughly – follow the iOS sandbox guide and Android sandbox guide.
- Offer restore & management – add UI buttons wired to
restorePurchases()andmanageSubscriptions().
Revenue next steps
Section titled “Revenue next steps”After the purchase flow works, use the Revenue Playbook to plan your first paid funnel: product scope, ASO, pricing, paywall placement, analytics, and churn feedback.
Troubleshooting
Section titled “Troubleshooting”Products not loading
- Make sure the bundle ID / application ID matches store configuration.
- Confirm the product IDs are active and approved (App Store) or activated (Google Play).
- Wait several hours after creating products; store propagation is not instant.
Purchase cancelled or stuck
- Users can cancel mid-flow; wrap calls in
try/catchand surface friendly error messages. - For Android, ensure test accounts install the app from Play Store (internal track) so Billing works.
- Check logcat/Xcode for billing errors when running on device.
Subscription state incorrect
- Use
getPurchases()to compare store data with your local entitlement cache. - On Android, always query the Google Play Developer API with the
purchaseTokento obtain expiration dates or refund status. - On iOS, check
isActive/expirationDateand validate receipts to detect refunds or revocations.