Skip to content

iaptic/react-native-iaptic

Repository files navigation

Iaptic React Native SDK

npm version npm downloads types included License: MIT Made by Iaptic

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 →

IapticSubscriptionView drop-in UI screenshot
The IapticSubscriptionView drop-in paywall, rendered on iOS.

Table of Contents

✨ Features

  • 🛒 Unified iOS + Android purchasing API
  • 🔄 Subscription tracking with renewal events
  • 🔄 Subscription change support (changeSubscription with 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

Requirements

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)

Installation

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.0

Expo

Add 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' }]
    ],
    // ...
  },
};

Quick Start

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.

Drop-in Subscription UI

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 on iOS

<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.

Props reference

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

Customization

<IapticSubscriptionView
  styles={{
    productCard: { backgroundColor: '#FFFFFF', borderRadius: 12 },
    ctaButton:   { backgroundColor: '#4CAF50' },
  }}
/>

For the full list of overridable style slots, see IapticSubscriptionViewStyles.

Core Concepts

Product definitions

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 },
]);

Purchase flow

try {
  await IapticRN.order(productOffer);
} catch (error) {
  showError(error);
}

Restore purchases

try {
  await IapticRN.restorePurchases((processed, total) => {
    console.log(`Processed ${processed} of ${total} purchases`);
  });
} catch (error) {
  showError(error);
}

Event handling

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}`);
});

Feature access control

if (IapticRN.checkEntitlement('premium')) {
  showPremiumContent();
} else {
  showUpgradePrompt();
}

// All currently active entitlements
const unlocked = IapticRN.listEntitlements(); // ['basic', 'premium', 'cool_feature']

Subscription changes (Android)

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().

Storefront detection

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.

Suspended subscriptions

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.suspended event — fires when a subscription transitions to suspended state
  • IapticRN.checkEntitlement() returns false for suspended subscriptions
IapticRN.addEventListener('subscription.suspended', (purchase) => {
  console.log(`Subscription ${purchase.productId} is suspended`);
});

Manual Purchase Flow

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
}

Error Handling

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);
  }
}

Troubleshooting

  • 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 upstream react-native-iap instead 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-iap to ^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 — versions 2.2.03.0.2 are broken on Android due to an unpublished Maven artifact. Use ^3.1.0 or stay on ~2.1.0. See release notes for details.

For more, see INTEGRATION_GUIDE.md → Troubleshooting.

Upgrading

From 1.3.x → 2.0.0

  • Bump @iaptic/react-native-iap to ^13.0.0 for Google Play Billing Library V9 support. This is a breaking change — the fork now requires GPBL 9.0.0 and drops the getPurchaseHistory API (removed in GPBL V9). Consumers pinning to @iaptic/react-native-iap@12.x can stay on react-native-iaptic@1.x.
  • Install @react-native-async-storage/async-storage explicitly if (and only if) you use IapticTokensManager. It is now an optional peer dependency.

From 1.2.x → 1.3.0

  • Install @react-native-async-storage/async-storage explicitly if (and only if) you use IapticTokensManager. It is now an optional peer dependency.
  • Bump @iaptic/react-native-iap to ^12.16.6 to pick up the Kotlin currentActivity fix on RN 0.83+.

From 1.0.x → 1.1+

  • Install @iaptic/react-native-iap explicitly — it moved from a regular dependency to a peer dependency.
  • The JavaScript API surface and Expo withIAP plugin behaviour are identical to upstream react-native-iap@12.16.4.

See RELEASE_NOTES.md for the full changelog.

Why the fork?

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.

Documentation

  • 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

Support & Community

Contributing

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.

Security

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.

License

MIT © Iaptic

About

React Native SDK for In-App Purchases

Resources

License

Stars

4 stars

Watchers

3 watching

Forks

Packages

 
 
 

Contributors

Languages