Drop-in subscription paywall and server-validated in-app purchases for React Native, backed by the Iaptic service. Works on iOS (StoreKit) and Android (Google Play Billing), with first-class TypeScript support.
What is Iaptic? Iaptic is a hosted receipt-validation and subscription-management service. The SDK never trusts a local receipt — every purchase is verified server-side. Learn more →

The IapticSubscriptionView drop-in paywall, rendered on iOS.
- Requirements
- Installation
- Quick Start
- Drop-in Subscription UI
- Core Concepts
- Manual Purchase Flow
- Error Handling
- Troubleshooting
- Upgrading
- Why the fork?
- Documentation
- Support & Community
- Contributing
- Security
- License
- 🛒 Unified iOS + Android purchasing API
- 🔄 Subscription tracking with renewal events
- 🔄 Subscription change support (
changeSubscriptionwith all 6 GPBL replacement modes) - 🌍 Storefront detection (
getStorefront) - ⏸️ Suspended subscription handling (
isSuspended/subscription.suspended) - 🔒 Server-side receipt validation via Iaptic
- 🎨 Themable drop-in paywall (
IapticSubscriptionView) - 📦 Product catalog with entitlements model
- 🛡 Typed errors with localized messages
- 🪙 Optional token/credit tracking (
IapticTokensManager) - ⚛️ First-class TypeScript types
| Requirement | Version |
|---|---|
| React Native | ≥ 0.71 (tested through 0.85) |
| Expo SDK | ≥ 49 (new architecture supported on SDK 55+) |
| iOS deployment target | 13.0 |
Android minSdkVersion |
24 |
| Node | ≥ 18 |
| TypeScript | ≥ 4.7 (types ship with the package) |
@iaptic/react-native-iap |
^13.0.0 (peer) |
react |
≥ 17 (peer) |
@react-native-async-storage/async-storage |
optional — ^3.1.0 or ~2.1.0 (only if using IapticTokensManager) |
react-native-iaptic requires @iaptic/react-native-iap, an Iaptic-maintained fork of react-native-iap@12.16.4 with GPBL V9 support, the iOS new-architecture pod fix, and the Kotlin currentActivity fix baked in. See Why the fork? below.
npm install @iaptic/react-native-iap react-native-iaptic
# iOS only
cd ios && pod install && cd ..
# Only if using IapticTokensManager (consumable token tracking):
npm install @react-native-async-storage/async-storage@^3.1.0Add the config plugin to your app.json / app.config.js so the Android missingDimensionStrategy (Play Store flavor) is wired up at prebuild time:
// app.config.js
export default {
expo: {
plugins: [
['@iaptic/react-native-iap', { paymentProvider: 'Play Store' }]
],
// ...
},
};import { IapticRN } from 'react-native-iaptic';
IapticRN.initialize({
appName: 'app.example.com',
publicKey: 'YOUR_PUBLIC_KEY',
iosBundleId: 'com.yourcompany.app',
products: [
{ id: 'premium_monthly', type: 'paid subscription', entitlements: ['premium'] },
{ id: 'coins_100', type: 'consumable', tokenType: 'coins', tokenValue: 100 },
],
});That's the minimum needed to load products. From here you can either drop in the prebuilt subscription UI, or wire up the manual purchase flow yourself.
IapticSubscriptionView is a full-screen subscription picker with built-in purchase, restore, and active-subscription management. Render it once near your app root and open it on demand.
<IapticSubscriptionView
entitlementLabels={{
premium: {
label: 'Premium Features',
detail: 'Exclusive content and advanced tools',
},
}}
onPurchaseComplete={() => {
setEntitlements(IapticRN.listEntitlements());
}}
termsUrl="https://yourdomain.com/terms"
/>The component automatically handles landscape/portrait layouts, localization, purchase states, active-subscription management, and receipt validation.
| Prop | Type | Description |
|---|---|---|
entitlementLabels |
Record<string, { label: string, detail?: string }> |
Labels and descriptions for each entitlement |
onPurchaseComplete |
() => void |
Callback after successful purchase |
termsUrl |
string |
URL for terms & conditions |
theme |
IapticTheme |
Customize colors — see IapticTheme |
styles |
Partial<IapticSubscriptionViewStyles> |
Per-element style overrides — see IapticSubscriptionViewStyles |
<IapticSubscriptionView
styles={{
productCard: { backgroundColor: '#FFFFFF', borderRadius: 12 },
ctaButton: { backgroundColor: '#4CAF50' },
}}
/>For the full list of overridable style slots, see IapticSubscriptionViewStyles.
Products can be subscriptions, consumables, or non-consumables. Each can grant one or more entitlements:
IapticRN.setProductDefinitions([
// Subscription that unlocks premium features
{ id: 'premium_monthly', type: 'paid subscription', entitlements: ['premium'] },
// Non-consumable that unlocks a specific feature
{ id: 'dark_theme', type: 'non consumable', entitlements: ['cool_feature'] },
// Consumable tokens / currency
{ id: 'coins_100', type: 'consumable', tokenType: 'coins', tokenValue: 100 },
]);try {
await IapticRN.order(productOffer);
} catch (error) {
showError(error);
}try {
await IapticRN.restorePurchases((processed, total) => {
console.log(`Processed ${processed} of ${total} purchases`);
});
} catch (error) {
showError(error);
}IapticRN.addEventListener('subscription.updated', (reason, purchase) => {
console.log(`Subscription ${purchase.id} ${reason}`);
});
IapticRN.addEventListener('pendingPurchase.updated', (pendingPurchase) => {
console.log(`Purchase ${pendingPurchase.productId} is now ${pendingPurchase.status}`);
});
IapticRN.addEventListener('purchase.updated', (purchase) => {
console.log(`Purchase ${purchase.id} ${purchase.status}`);
});if (IapticRN.checkEntitlement('premium')) {
showPremiumContent();
} else {
showUpgradePrompt();
}
// All currently active entitlements
const unlocked = IapticRN.listEntitlements(); // ['basic', 'premium', 'cool_feature']On Android, use changeSubscription to switch between subscription tiers. Pass one of the six IapticReplacementMode values to control proration:
import { IapticRN, IapticReplacementMode } from 'react-native-iaptic';
const newOffer = IapticRN.getProduct('premium_yearly')?.offers[0];
const currentSub = IapticRN.getActiveSubscription();
if (newOffer && currentSub?.purchaseToken) {
await IapticRN.changeSubscription(
newOffer,
currentSub.purchaseToken,
IapticReplacementMode.WITH_TIME_PRORATION, // default
);
}Available modes: WITH_TIME_PRORATION (1), CHARGE_PRORATED_PRICE (2), WITHOUT_PRORATION (3), CHARGE_FULL_PRICE (4), DEFERRED (5), KEEP_EXISTING (6). On iOS, changeSubscription falls back to a regular order().
const storefront = await IapticRN.getStorefront();
console.log(`User is in ${storefront}`); // e.g. "US", "DE", "JP"Returns an ISO 3166-1 alpha-2 country code. Uses StoreKit 2 on iOS 16+ and Google Play Billing Library on Android. Returns an empty string if the storefront cannot be determined.
On Android, subscriptions can be suspended (paused by the user or on hold due to payment decline). The SDK exposes this through:
IapticVerifiedPurchase.isSuspended— boolean flag on the purchase object- The
subscription.suspendedevent — fires when a subscription transitions to suspended state IapticRN.checkEntitlement()returnsfalsefor suspended subscriptions
IapticRN.addEventListener('subscription.suspended', (purchase) => {
console.log(`Subscription ${purchase.productId} is suspended`);
});If you don't want the drop-in UI, drive purchases yourself:
const offer = IapticRN.getProduct('premium_monthly')?.offers[0];
if (offer) {
await IapticRN.order(offer);
}
if (IapticRN.checkEntitlement('premium')) {
// Unlock premium features
}function showError(error: Error | IapticError) {
if (error instanceof IapticError) {
trackAnalyticsEvent(error.code);
if (error.severity === IapticSeverity.INFO) {
console.log('Info:', error.localizedMessage);
return;
}
Alert.alert(error.localizedTitle, error.localizedMessage);
} else {
Alert.alert('Unknown error', error.message);
}
}-
Products won't load on iOS — verify your Xcode project has the In-App Purchase capability enabled (Xcode → Project → Targets → your app → Signing & Capabilities → + Capability → In-App Purchase).
-
iOS build fails on React Native ≥ 0.83 / Expo SDK ≥ 55 with
Unable to find a specification for RCT-Folly depended upon by RNIap— you're depending on upstreamreact-native-iapinstead of@iaptic/react-native-iap. Switch to the fork (see Installation) and the error goes away. Background: Why the fork?. -
Android build fails with
Unresolved reference 'currentActivity'— bump@iaptic/react-native-iapto^13.0.0, which contains GPBL V9 support and the Kotlin fix for RN 0.83+ / new architecture. -
Android Gradle resolution fails inside
@react-native-async-storage/async-storage— versions2.2.0–3.0.2are broken on Android due to an unpublished Maven artifact. Use^3.1.0or stay on~2.1.0. See release notes for details.
For more, see INTEGRATION_GUIDE.md → Troubleshooting.
- Bump
@iaptic/react-native-iapto^13.0.0for Google Play Billing Library V9 support. This is a breaking change — the fork now requires GPBL 9.0.0 and drops thegetPurchaseHistoryAPI (removed in GPBL V9). Consumers pinning to@iaptic/react-native-iap@12.xcan stay onreact-native-iaptic@1.x. - Install
@react-native-async-storage/async-storageexplicitly if (and only if) you useIapticTokensManager. It is now an optional peer dependency.
- Install
@react-native-async-storage/async-storageexplicitly if (and only if) you useIapticTokensManager. It is now an optional peer dependency. - Bump
@iaptic/react-native-iapto^12.16.6to pick up the KotlincurrentActivityfix on RN 0.83+.
- Install
@iaptic/react-native-iapexplicitly — it moved from a regular dependency to a peer dependency. - The JavaScript API surface and Expo
withIAPplugin behaviour are identical to upstreamreact-native-iap@12.16.4.
See RELEASE_NOTES.md for the full changelog.
Upstream hyochan/react-native-iap was archived on 2026-04-26; development moved to the OpenIAP monorepo, where it shipped as a Nitro Modules rewrite (v15+, different API). The 12.x line therefore won't receive any further patches upstream — including the iOS pod fix needed for React Native ≥ 0.83 / Expo SDK ≥ 55 / new architecture (Unable to find a specification for RCT-Folly depended upon by RNIap), and the Kotlin currentActivity fix for the same RN versions.
@iaptic/react-native-iap@13.0.0 upgrades Google Play Billing Library to V9, drops the getPurchaseHistory API (removed in GPBL V9), and includes the iOS new-architecture pod fix and the Kotlin currentActivity fix. Consumers who need GPBL V7 can stay on 12.16.6.
- Integration guide — step-by-step setup for subscriptions (single reference combining install, dashboard, example app, and API)
- API reference — generated TypeDoc for all public types
- Release notes — changelog and upgrade notes
- Demo app — a runnable example app
- 🐛 Issue tracker — bug reports and feature requests
- 📧 support@iaptic.com — direct support for Iaptic customers
- 🌐 iaptic.com — service status, pricing, dashboard
This repository is maintained by Iaptic. PRs and issue reports are welcome — please file an issue first for non-trivial changes so we can align on direction before you invest the work.
Receipts are validated server-side by Iaptic; no validation logic runs in the app bundle, and your validation secret never ships to clients. To report a security issue, email security@iaptic.com rather than opening a public GitHub issue.
MIT © Iaptic