Core Methods
This section covers the core methods available in expo-iap for managing in-app purchases.
Note: expo-iap aligns with the OpenIAP API surface. For canonical cross-SDK API docs, see:
Unified APIs
These cross‑platform methods work on both iOS and Android. For StoreKit/Play‑specific helpers, see the Platform‑specific APIs section below.
initConnection()— Initialize the store connectionendConnection()— End the store connection and cleanupfetchProducts()— Fetch product and subscription metadatarequestPurchase()— Start a purchase for products or subscriptionsfinishTransaction()— Complete a transaction after validationgetAvailablePurchases()— Restore non‑consumables and subscriptionsdeepLinkToSubscriptions()— Open native subscription management UIgetStorefront()— Get current storefront country codehasActiveSubscriptions()— Check if user has active subscriptions
initConnection()
Initializes the connection to the store. This method must be called before any other store operations.
import {initConnection} from 'expo-iap';
const initialize = async () => {
try {
await initConnection();
console.log('Store connection initialized');
} catch (error) {
console.error('Failed to initialize connection:', error);
}
};
Returns: Promise<boolean>
Note: When using the useIAP hook, connection is automatically managed.
endConnection()
Ends the connection to the store and cleans up resources.
import {endConnection} from 'expo-iap';
const cleanup = async () => {
try {
await endConnection();
console.log('Store connection ended');
} catch (error) {
console.error('Failed to end connection:', error);
}
};
Returns: Promise<void>
Note: When using the useIAP hook, connection cleanup is automatic.
fetchProducts()
Fetches product or subscription information from the store.
import {fetchProducts} from 'expo-iap';
// Fetch in-app products
const loadProducts = async () => {
try {
const products = await fetchProducts({
skus: ['com.example.product1', 'com.example.product2'],
type: 'in-app',
});
console.log('Products:', products);
return products;
} catch (error) {
console.error('Failed to fetch products:', error);
}
};
// Fetch subscriptions
const loadSubscriptions = async () => {
try {
const subscriptions = await fetchProducts({
skus: ['com.example.premium_monthly', 'com.example.premium_yearly'],
type: 'subs',
});
console.log('Subscriptions:', subscriptions);
return subscriptions;
} catch (error) {
console.error('Failed to fetch subscriptions:', error);
}
};
Parameters:
params(object):skus(string[]): Array of product or subscription IDs to fetchtype('in-app' | 'subs'): Product type - 'in-app' for products, 'subs' for subscriptions
Returns: Promise<Product[]>
requestPurchase()
Initiates a purchase request for products or subscriptions.
⚠️ Platform Differences:
- iOS: Can only purchase one product at a time (uses
sku: string)- Android: Can purchase multiple products at once (uses
skus: string[])This exists because the iOS App Store processes purchases individually, while Google Play supports batch purchases.
Recommended usage (no Platform checks)
import {requestPurchase} from 'expo-iap';
// Product purchase
const buyProduct = async (productId: string) => {
try {
await requestPurchase({
request: {
ios: {
sku: productId,
quantity: 1,
},
android: {
skus: [productId],
},
},
type: 'in-app',
});
} catch (error) {
console.error('Purchase failed:', error);
}
};
// Subscription purchase
const buySubscription = async (subscriptionId: string, subscription?: any) => {
try {
await requestPurchase({
request: {
ios: {
sku: subscriptionId,
appAccountToken: 'user-123',
},
android: {
skus: [subscriptionId],
subscriptionOffers:
subscription?.subscriptionOfferDetails?.map((offer) => ({
sku: subscriptionId,
offerToken: offer.offerToken,
})) || [],
},
},
type: 'subs',
});
} catch (error) {
console.error('Subscription failed:', error);
}
};
Detailed Platform Examples
iOS Only
await requestPurchase({
request: {
sku: productId,
quantity: 1,
appAccountToken: 'user-account-token',
},
type: 'in-app',
});
Android Only
await requestPurchase({
request: {
skus: [productId],
obfuscatedAccountIdAndroid: 'user-account-id',
obfuscatedProfileIdAndroid: 'user-profile-id',
},
type: 'in-app',
});
Parameters:
params(object):request(object): Purchase request configuration- iOS:
sku(string) - Product ID to purchase - Android:
skus(string[]) - Array of product IDs to purchase - Cross-platform: Include both
skuandskusfor compatibility quantity?(number, iOS only): Purchase quantityappAccountToken?(string, iOS only): User identifier for receipt validationobfuscatedAccountIdAndroid?(string, Android only): Obfuscated account IDobfuscatedProfileIdAndroid?(string, Android only): Obfuscated profile IDisOfferPersonalized?(boolean, Android only): Whether offer is personalized
- iOS:
type?('in-app' | 'subs'): Purchase type, defaults to 'in-app'
Returns: Promise<Purchase | Purchase[] | void>
Note: The actual purchase result is delivered through purchase listeners or the useIAP hook callbacks, not as a return value.
Important Subscription Properties
For subscription status checks after a purchase or when listing entitlements:
- iOS: Check
expirationDateIOSto determine if the subscription is still active - Android: Check
autoRenewingAndroidto see if auto‑renewal has been canceled
finishTransaction()
Completes a purchase transaction. Must be called after successful receipt validation.
import {finishTransaction} from 'expo-iap';
const completePurchase = async (purchase) => {
try {
// Validate receipt on your server first
const isValid = await validateReceiptOnServer(purchase);
if (isValid) {
// Grant purchase to user
await grantPurchaseToUser(purchase);
// Finish the transaction
await finishTransaction({
purchase,
isConsumable: true, // Set to true for consumable products
});
console.log('Transaction completed');
}
} catch (error) {
console.error('Failed to finish transaction:', error);
}
};
Parameters:
params(object):purchase(Purchase): The purchase object to finishisConsumable?(boolean): Whether the product is consumable (Android)
Returns: Promise<VoidResult | boolean>
getAvailablePurchases()
Retrieves available purchases for restoration (non-consumable products and subscriptions).
import {getAvailablePurchases} from 'expo-iap';
const restorePurchases = async () => {
try {
const purchases = await getAvailablePurchases();
for (const purchase of purchases) {
// Validate and restore each purchase
const isValid = await validateReceiptOnServer(purchase);
if (isValid) {
await grantPurchaseToUser(purchase);
}
}
console.log('Purchases restored');
} catch (error) {
console.error('Failed to restore purchases:', error);
}
};
Parameters:
options?(iOS only):alsoPublishToEventListenerIOS?: booleanonlyIncludeActiveItemsIOS?: boolean
Returns: Promise<Purchase[]>
deepLinkToSubscriptions()
Opens the platform-specific subscription management UI.
import {deepLinkToSubscriptions} from 'expo-iap';
const openSubscriptionSettings = () => {
try {
deepLinkToSubscriptions({skuAndroid: 'your_subscription_sku'});
} catch (error) {
console.error('Failed to open subscription settings:', error);
}
};
Returns: Promise<void>
getStorefront()
Return the storefront in ISO 3166-1 alpha-2 or ISO 3166-1 alpha-3 format
import {getStorefront} from 'expo-iap';
const storeFront = await getStorefront();
Returns: Promise<string>
getActiveSubscriptions()
Retrieves all active subscriptions with detailed status information. This method follows the OpenIAP specification for cross-platform subscription management.
import {getActiveSubscriptions} from 'expo-iap';
const checkSubscriptions = async () => {
try {
// Get all active subscriptions
const allActiveSubscriptions = await getActiveSubscriptions();
// Or filter by specific subscription IDs
const specificSubscriptions = await getActiveSubscriptions([
'premium_monthly',
'premium_yearly',
]);
for (const subscription of allActiveSubscriptions) {
console.log('Product ID:', subscription.productId);
console.log('Is Active:', subscription.isActive);
if (Platform.OS === 'ios') {
console.log('Expiration Date:', subscription.expirationDateIOS);
console.log(
'Days until expiration:',
subscription.daysUntilExpirationIOS,
);
console.log('Environment:', subscription.environmentIOS);
} else if (Platform.OS === 'android') {
console.log('Auto Renewing:', subscription.autoRenewingAndroid);
}
console.log('Will expire soon:', subscription.willExpireSoon);
}
} catch (error) {
console.error('Failed to get active subscriptions:', error);
}
};
Parameters:
subscriptionIds?(string[]): Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
Returns: Promise<ActiveSubscription[]>
ActiveSubscription Interface:
interface ActiveSubscription {
productId: string;
isActive: boolean;
expirationDateIOS?: Date;
autoRenewingAndroid?: boolean;
environmentIOS?: string;
willExpireSoon?: boolean;
daysUntilExpirationIOS?: number;
}
Platform Behavior:
- iOS: Uses
expirationDateIosto determine subscription status. Includes expiration date and days until expiration. - Android: Uses purchase list presence and
autoRenewingAndroidflag to determine status.
hasActiveSubscriptions()
Checks if the user has any active subscriptions. This is a convenience method that returns a boolean result.
import {hasActiveSubscriptions} from 'expo-iap';
const checkIfUserHasSubscription = async () => {
try {
// Check if user has any active subscriptions
const hasAny = await hasActiveSubscriptions();
// Or check for specific subscriptions
const hasPremium = await hasActiveSubscriptions([
'premium_monthly',
'premium_yearly',
]);
if (hasAny) {
console.log('User has active subscriptions');
}
if (hasPremium) {
console.log('User has premium subscription');
}
} catch (error) {
console.error('Failed to check subscription status:', error);
}
};
Parameters:
subscriptionIds?(string[]): Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
Returns: Promise<boolean> - Returns true if user has at least one active subscription
Purchase Interface
interface Purchase {
id: string; // Transaction identifier
productId: string;
transactionDate: number;
purchaseToken?: string; // Unified token (iOS JWS or Android token)
// iOS-specific properties
originalTransactionDateIOS?: number;
originalTransactionIdentifierIOS?: string;
expirationDateIOS?: number;
environmentIOS?: 'Production' | 'Sandbox';
// Android-specific properties
dataAndroid?: string;
signatureAndroid?: string;
purchaseStateAndroid?: number;
isAcknowledgedAndroid?: boolean;
packageNameAndroid?: string;
developerPayloadAndroid?: string;
obfuscatedAccountIdAndroid?: string;
obfuscatedProfileIdAndroid?: string;
autoRenewingAndroid?: boolean;
}
Platform-specific APIs
iOS Specific
The following iOS‑only helpers expose StoreKit and App Store specific capabilities. Most day‑to‑day flows are covered by the cross‑platform Core Methods above; use these only when you need iOS features.
clearTransactionIOS()
Clears all pending transactions from the iOS payment queue. Useful if your app previously crashed or missed finishing transactions.
import {clearTransactionIOS, getPendingTransactionsIOS} from 'expo-iap';
// Inspect then clear
const pending = await getPendingTransactionsIOS();
if (pending.length) {
await clearTransactionIOS();
}
Returns: Promise<void>
getPromotedProductIOS()
Gets the currently promoted product, if any. Requires iOS 11+.
import {getPromotedProductIOS} from 'expo-iap';
const promoted = await getPromotedProductIOS();
if (promoted) {
// Show your purchase UI for the promoted product
}
Returns: Promise<Product | null>
requestPurchaseOnPromotedProductIOS()
Initiates the purchase flow for the currently promoted product. Requires iOS 11+.
import {requestPurchaseOnPromotedProductIOS} from 'expo-iap';
await requestPurchaseOnPromotedProductIOS();
// Purchase result is delivered via purchase listeners/useIAP callbacks
Returns: Promise<void>
getPendingTransactionsIOS()
Returns all transactions that are pending completion in the StoreKit payment queue.
import {getPendingTransactionsIOS} from 'expo-iap';
const pending = await getPendingTransactionsIOS();
Returns: Promise<Purchase[]>
isEligibleForIntroOfferIOS()
Checks if the user is eligible for an introductory offer for a subscription group. Requires iOS 12.2+.
import {isEligibleForIntroOfferIOS, fetchProducts} from 'expo-iap';
// Example: derive group ID from a fetched subscription product
const [sub] = await fetchProducts({skus: ['your_sub_sku'], type: 'subs'});
const groupId = sub?.subscriptionInfoIOS?.subscriptionGroupId ?? '';
const eligible = groupId ? await isEligibleForIntroOfferIOS(groupId) : false;
Returns: Promise<boolean>
subscriptionStatusIOS()
Returns detailed subscription status information using StoreKit 2. Requires iOS 15+.
import {subscriptionStatusIOS} from 'expo-iap';
const statuses = await subscriptionStatusIOS('your_sub_sku');
Returns: Promise<SubscriptionStatusIOS[]>
currentEntitlementIOS()
Returns the current entitlement for a given SKU using StoreKit 2. Requires iOS 15+.
import {currentEntitlementIOS} from 'expo-iap';
const entitlement = await currentEntitlementIOS('your_sub_or_product_sku');
Returns: Promise<Purchase | null>
latestTransactionIOS()
Returns the most recent transaction for a given SKU using StoreKit 2. Requires iOS 15+.
import {latestTransactionIOS} from 'expo-iap';
const last = await latestTransactionIOS('your_sku');
Returns: Promise<Purchase | null>
showManageSubscriptionsIOS()
Opens the native subscription management interface and returns purchases for subscriptions whose auto‑renewal status changed while the sheet was open. Requires iOS 15+.
import {showManageSubscriptionsIOS} from 'expo-iap';
const changed = await showManageSubscriptionsIOS();
if (changed.length > 0) {
// Update your UI / server using returned purchases
}
Returns: Promise<Purchase[]>
beginRefundRequestIOS()
Presents the refund request sheet for a specific SKU. Requires iOS 15+.
import {beginRefundRequestIOS} from 'expo-iap';
const status = await beginRefundRequestIOS('your_sku');
// status: 'success' | 'userCancelled'
Returns: Promise<'success' | 'userCancelled'>
isTransactionVerifiedIOS()
Verifies the latest transaction for a given SKU using StoreKit 2. Requires iOS 15+.
import {isTransactionVerifiedIOS} from 'expo-iap';
const ok = await isTransactionVerifiedIOS('your_sku');
Returns: Promise<boolean>
getTransactionJwsIOS()
Returns the JSON Web Signature (JWS) for a transaction derived from a given SKU. Use this for server‑side validation. Requires iOS 15+.
import {getTransactionJwsIOS} from 'expo-iap';
const jws = await getTransactionJwsIOS('your_sku');
Returns: Promise<string>
getReceiptDataIOS()
Returns the base64‑encoded receipt data for server validation.
import {getReceiptDataIOS} from 'expo-iap';
const receipt = await getReceiptDataIOS();
Returns: Promise<string>
syncIOS()
Forces a sync with StoreKit to ensure all transactions are up to date. Requires iOS 15+.
import {syncIOS} from 'expo-iap';
await syncIOS();
Returns: Promise<void>
presentCodeRedemptionSheetIOS()
Presents the system sheet for redeeming App Store promo/offer codes.
import {presentCodeRedemptionSheetIOS} from 'expo-iap';
await presentCodeRedemptionSheetIOS();
Returns: Promise<boolean>
getAppTransactionIOS()
Gets app transaction information for iOS apps (iOS 16.0+). AppTransaction represents the initial purchase that unlocked the app, useful for premium apps or apps that were previously paid.
Runtime: iOS 16.0+; Build: Xcode 15.0+ with iOS 16.0 SDK. Older SDKs will throw.
import {getAppTransactionIOS} from 'expo-iap';
const fetchAppTransaction = async () => {
try {
const appTransaction = await getAppTransactionIOS();
if (appTransaction) {
console.log('App Transaction ID:', appTransaction.appTransactionId);
console.log(
'Original Purchase Date:',
new Date(appTransaction.originalPurchaseDate),
);
console.log('Device Verification:', appTransaction.deviceVerification);
}
} catch (error) {
console.error('Failed to get app transaction:', error);
}
};
Returns: Promise<AppTransaction | null>
interface AppTransaction {
appTransactionId?: string; // iOS 18.4+
originalPlatform?: string; // iOS 18.4+
bundleId: string;
appVersion: string;
originalAppVersion: string;
originalPurchaseDate: number; // ms since epoch
deviceVerification: string;
deviceVerificationNonce: string;
environment: string;
signedDate: number;
appId?: number;
appVersionId?: number;
preorderDate?: number;
}
Android Specific
acknowledgePurchaseAndroid
Acknowledge a non‑consumable purchase or subscription on Android.
import {acknowledgePurchaseAndroid} from 'expo-iap';
await acknowledgePurchaseAndroid({token: purchase.purchaseToken!});
Notes:
- finishTransaction() calls this automatically when
isConsumableis false. You typically do not need to call it directly.
consumePurchaseAndroid
Consume a purchase (consumables only). This marks an item as consumed so it can be purchased again.
Notes:
- finishTransaction() calls Android consumption automatically when
isConsumableis true. - A direct JS helper is not exposed; consumption is handled internally via the native module.
flushFailedPurchasesCachedAsPendingAndroid (Removed)
This legacy helper from older libraries has been removed. The modern flow is:
// On app startup (Android)
const purchases = await getAvailablePurchases();
for (const p of purchases) {
if (/* consumable */) {
// finishTransaction will consume on Android when isConsumable is true
await finishTransaction({ purchase: p, isConsumable: true });
} else {
// finishTransaction will acknowledge on Android when isConsumable is false
await finishTransaction({ purchase: p, isConsumable: false });
}
}
This ensures pending transactions are surfaced and properly resolved without a separate “flush” API.
Removed APIs
requestProducts()— Removed in v3.0.0. UsefetchProducts({ skus, type })instead.
