This plugin allows you to implement in-app purchases and subscriptions in your Capacitor app using native APIs.
The only free, battle-tested in-app purchase plugin for Capacitor with full feature parity:
Perfect for apps monetizing through one-time purchases or recurring subscriptions.
The most complete doc is available here: https://capgo.app/docs/plugins/native-purchases/
| Plugin version | Capacitor compatibility | Maintained |
|---|---|---|
| v8.*.* | v8.*.* | ✅ |
| v7.*.* | v7.*.* | On demand |
| v6.*.* | v6.*.* | ❌ |
| v5.*.* | v5.*.* | ❌ |
Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.
You can use our AI-Assisted Setup to install the plugin. Add the Capgo skills to your AI tool using the following command:
npx skills add https://github.com/cap-go/capacitor-skills --skill capacitor-plugins
Then use the following prompt:
Use the `capacitor-plugins` skill from `cap-go/capacitor-skills` to install the `@capgo/native-purchases` plugin in my project.
If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:
npm install @capgo/native-purchases
npx cap sync
Complete visual testing guides for both platforms:
| Platform | Guide | Content |
|---|---|---|
| 🍎 iOS | iOS Testing Guide | StoreKit Local Testing, Sandbox Testing, Developer Mode setup |
| 🤖 Android | Android Testing Guide | Internal Testing, License Testing, Internal App Sharing |
💡 Quick Start: Choose StoreKit Local Testing for iOS or Internal Testing for Android for the fastest development experience.
Add this to manifest
<uses-permission android:name="com.android.vending.BILLING" />
📖 Complete Android Testing Guide - Comprehensive guide covering Internal Testing, License Testing, and Internal App Sharing methods with step-by-step instructions, troubleshooting, and best practices.
For testing in-app purchases on Android:
Add the "In-App Purchase" capability to your Xcode project:
⚠️ App Store Requirement: You MUST display product names and prices using data from the plugin (
product.title,product.priceString). Hardcoded values will cause App Store rejection.📖 Complete iOS Testing Guide - Comprehensive guide covering both Sandbox and StoreKit local testing methods with step-by-step instructions, troubleshooting, and best practices.
On supported iOS versions, the plugin exposes Apple's monthly subscription with 12-month commitment billing plan configuration from StoreKit:
product.pricingTerms so users can compare the standard up-front plan and the monthly commitment plan before purchasing.billingPlanType: 'monthly' to purchaseProduct() when the user chooses the monthly commitment plan.transaction.billingPlanType, transaction.commitmentInfo, and transaction.renewalInfo.commitmentInfo from purchases, restored purchases, and entitlement checks.const { products } = await NativePurchases.getProducts({
productIdentifiers: ['com.yourapp.premium.yearly'],
productType: PURCHASE_TYPE.SUBS,
});
const premiumYearly = products[0];
const monthlyCommitment = premiumYearly.pricingTerms?.find(
(term) => term.billingPlanType === 'monthly'
);
if (monthlyCommitment) {
// Display both billingDisplayPrice and commitmentInfo.priceString before purchase.
await NativePurchases.purchaseProduct({
productIdentifier: premiumYearly.identifier,
productType: PURCHASE_TYPE.SUBS,
billingPlanType: 'monthly',
});
}
For testing in-app purchases on iOS:
Import the plugin in your TypeScript file:
import { NativePurchases, PURCHASE_TYPE } from '@capgo/native-purchases';
There are two types of purchases with different requirements:
| Purchase Type | productType | planIdentifier | Use Case |
|---|---|---|---|
| In-App Purchase | PURCHASE_TYPE.INAPP |
❌ Not needed | One-time purchases (premium features, remove ads, etc.) |
| Subscription | PURCHASE_TYPE.SUBS |
✅ REQUIRED (Android only) | Recurring purchases (monthly/yearly subscriptions) |
Key Rules:
- ✅ In-App Products: Use productType: PURCHASE_TYPE.INAPP, no planIdentifier needed on any platform
- ✅ Subscriptions on Android: Must use productType: PURCHASE_TYPE.SUBS AND planIdentifier: "your-plan-id" (the Base Plan ID from Google Play Console)
- ✅ Subscriptions on iOS: Use productType: PURCHASE_TYPE.SUBS, planIdentifier is optional and ignored
- ❌ Missing planIdentifier for Android subscriptions will cause purchase failures
About planIdentifier (Android-specific):
The planIdentifier parameter is only required for Android subscriptions. It should be set to the Base Plan ID that you configure in the Google Play Console when creating your subscription product. For example, if you create a monthly subscription with base plan ID "monthly-plan" in Google Play Console, you would use planIdentifier: "monthly-plan" when purchasing that subscription.
iOS does not use this parameter - subscriptions on iOS only require the product identifier.
Here's a complete example showing how to get product information and make purchases for both in-app products and subscriptions:
```typescript import { NativePurchases, PURCHASE_TYPE } from '@capgo/native-purchases';
class PurchaseManager { // In-app product (one-time purchase) private premiumProductId = 'com.yourapp.premium_features';
// Subscription products (require planIdentifier on Android) private monthlySubId = 'com.yourapp.premium.monthly'; private monthlyPlanId = 'monthly-plan'; // Base plan ID from Google Play Console (Android only)
private yearlySubId = 'com.yourapp.premium.yearly'; private yearlyPlanId = 'yearly-plan'; // Base plan ID from Google Play Console (Android only)
async initializeStore() { try { // 1. Check if billing is supported const { isBillingSupported } = await NativePurchases.isBillingSupported(); if (!isBillingSupported) { throw new Error('Billing not supported on this device'); }
// 2. Get product information (REQUIRED by Apple - no hardcoded prices!)
await this.loadProducts();
} catch (error) {
console.error('Store initialization failed:', error);
}
}
async loadProducts() { try { // Load in-app products const { product: premiumProduct } = await NativePurchases.getProduct({ productIdentifier: this.premiumProductId, productType: PURCHASE_TYPE.INAPP });
// Load subscription products
const { products: subscriptions } = await NativePurchases.getProducts({
productIdentifiers: [this.monthlySubId, this.yearlySubId],
productType: PURCHASE_TYPE.SUBS
});
// Android note: subscriptions can include multiple entries per product (one per offer/base plan).
// Use `identifier` (base plan), `offerToken`, and optional `offerId` to pick a specific offer.
console.log('Products loaded:', {
premium: premiumProduct,
subscriptions: subscriptions
});
// Display products with dynamic info from store
this.displayProducts(premiumProduct, subscriptions);
} catch (error) {
console.error('Failed to load products:', error);
throw error;
}
}
displayProducts(premiumProduct: any, subscriptions: any[]) { // ✅ CORRECT: Use dynamic product info (required by Apple)
// Display one-time purchase
document.getElementById('premium-title')!.textContent = premiumProduct.title;
document.getElementById('premium-price')!.textContent = premiumProduct.priceString;
// Display subscriptions
subscriptions.forEach(sub => {
const element = document.getElementById(`sub-${sub.identifier}`);
if (element) {
element.textContent = `${sub.title} - ${sub.priceString}`;
}
});
// ❌ WRONG: Never hardcode prices - Apple will reject your app
// document.getElementById('premium-price')!.textContent = '$9.99';
}
// Purchase one-time product (no planIdentifier needed) async purchaseInAppProduct() { try { console.log('Starting in-app purchase...');
const result = await NativePurchases.purchaseProduct({
productIdentifier: this.premiumProductId,
productType: PURCHASE_TYPE.INAPP,
quantity: 1
});
console.log('In-app purchase successful!', result.transactionId);
await this.handleSuccessfulPurchase(result.transactionId, 'premium');
} catch (error) {
console.error('In-app purchase failed:', error);
this.handlePurchaseError(error);
}
}
// Purchase subscription (planIdentifier REQUIRED for Android) async purchaseMonthlySubscription() { try { console.log('Starting subscription purchase...');
const result = await NativePurchases.purchaseProduct({
productIdentifier: this.monthlySubId,
planIdentifier: this.monthlyPlanId, // REQUIRED for Android subscriptions, ignored on iOS
productType: PURCHASE_TYPE.SUBS, // REQUIRED for subscriptions
quantity: 1
});
console.log('Subscription purchase successful!', result.transactionId);
await this.handleSuccessfulPurchase(result.transactionId, 'monthly');
} catch (error) {
console.error('Subscription purchase failed:', error);
this.handlePurchaseError(error);
}
}
// Purchase yearly subscription (planIdentifier REQUIRED for Android) async purchaseYearlySubscription() { try { console.log('Starting yearly subscription purchase...');
const result = await NativePurchases.purchaseProduct({
productIdentifier: this.yearlySubId,
planIdentifier: this.yearlyPlanId, // REQUIRED for Android subscriptions, ignored on iOS
productType: PURCHASE_TYPE.SUBS, // REQUIRED for subscriptions
quantity: 1
});
console.log('Yearly subscription successful!', result.transactionId);
await this.handleSuccessfulPurchase(result.transactionId, 'yearly');
} catch (error) {
console.error('Yearly subscription failed:', error);
this.handlePurchaseError(error);
}
}
async handleSuccessfulPurchase(transactionId: string, purchaseType: string) { // 1. Grant access to premium features localStorage.setItem('premium_active', 'true'); localStorage.setItem('purchase_type', purchaseType);
// 2. Update UI
const statusText = purchaseType === 'premium' ? 'Premium Unlocked' : `${purchaseType} Subscription Active`;
document.getElementById('subscription-status')!.textContent = statusText;
// 3. Optional: Verify purchase on your server
await this.verifyPurchaseOnServer(transactionId);
}
handlePurchaseError(error: any) {
$ claude mcp add capacitor-native-purchases \
-- python -m otcore.mcp_server <graph>