useIAP Hook
The useIAP hook is the main interface for interacting with in-app purchases in React Native IAP. It provides a comprehensive API for managing purchases, subscriptions, and error handling.
Import
import {useIAP} from 'react-native-iap';
Important: Hook Behavior
The useIAP hook follows React Hooks conventions and differs from calling functions directly from react-native-iap (index exports):
- Automatic connection: Automatically calls
initConnectionon mount andendConnectionon unmount. - Void-returning methods: Methods like
fetchProducts,requestPurchase,getAvailablePurchases, etc. returnPromise<void>in the hook. They do not resolve to data. Instead, they update internal state exposed by the hook:products,subscriptions,availablePurchases, etc. - Don't await for data: When using the hook, do not write
const x = await fetchProducts(...). Call the method, then read the corresponding state from the hook.
Basic Usage
const {
connected,
products,
subscriptions,
availablePurchases,
fetchProducts,
requestPurchase,
validateReceipt,
} = useIAP({
onPurchaseSuccess: (purchase) => {
// Validate on your backend, then finish the transaction
console.log('Purchase successful:', purchase);
},
onPurchaseError: (error) => {
console.error('Purchase failed:', error);
},
});
Configuration Options
useIAP(options)
| Parameter | Type | Required | Description |
|---|---|---|---|
options | UseIAPOptions | No | Configuration object |
UseIAPOptions
interface UseIAPOptions {
onPurchaseSuccess?: (purchase: Purchase) => void;
onPurchaseError?: (error: PurchaseError) => void;
shouldAutoSyncPurchases?: boolean; // Controls auto sync behavior inside the hook
onPromotedProductIOS?: (product: Product) => void; // iOS promoted products
}
Configuration Properties
onPurchaseSuccess
-
Type:
(purchase: Purchase) => void -
Description: Called when a purchase completes successfully
-
Example:
onPurchaseSuccess: (purchase) => {
// Grant user access to purchased content
unlockFeature(purchase.productId);
};
onPurchaseError
-
Type:
(error: PurchaseError) => void -
Description: Called when a purchase fails
-
Example:
onPurchaseError: (error) => {
if (error.code !== ErrorCode.UserCancelled) {
Alert.alert('Purchase Failed', error.message);
}
};
autoFinishTransactions
- Type:
boolean - Default:
true - Description: Whether to automatically finish transactions after successful purchases
Return Values
State Properties
connected
-
Type:
boolean -
Description: Whether the IAP service is connected and ready
-
Example:
if (connected) {
// Safe to make IAP calls
fetchProducts({skus: ['product.id'], type: 'in-app'});
}
products
-
Type:
Product[] -
Description: Array of available products
-
Example:
products.map((product) => <ProductItem key={product.id} product={product} />);
subscriptions
-
Type:
ProductSubscription[] -
Description: Array of available subscription products
-
Example:
subscriptions.map((subscription) => (
<SubscriptionItem key={subscription.id} subscription={subscription} />
));
currentPurchaseError
-
Type:
PurchaseError | null -
Description: Current purchase error (if any)
-
Example:
useEffect(() => {
if (currentPurchaseError) {
handlePurchaseError(currentPurchaseError);
}
}, [currentPurchaseError]);
availablePurchases
-
Type:
Purchase[] -
Description: Array of available purchases (restorable items)
-
Example:
availablePurchases.map((purchase) => (
<RestorableItem key={purchase.id} purchase={purchase} />
));
promotedProductIOS
-
Type:
Product | undefined -
Description: The promoted product details (iOS only)
-
Example:
useEffect(() => {
if (promotedProductIOS) {
// Handle promoted product
handlePromotedProduct(promotedProductIOS);
}
}, [promotedProductIOS]);
Methods
fetchProducts
-
Type:
(params: { skus: string[]; type?: 'in-app' | 'subs' }) => Promise<void> -
Description: Fetch products or subscriptions and update
products/subscriptionsstate. In the hook this returnsvoid(no data result), by design. -
Do not await for data: Call it, then consume
products/subscriptionsstate from the hook. -
Example:
useEffect(() => {
if (!connected) return;
// In hook: returns void, updates state
fetchProducts({
skus: ['com.app.premium', 'com.app.coins_100'],
type: 'in-app',
});
fetchProducts({skus: ['com.app.premium_monthly'], type: 'subs'});
}, [connected, fetchProducts]);
// Later in render/effects
products.forEach((p) => console.log('product', p.id));
subscriptions.forEach((s) => console.log('sub', s.id));
requestPurchase
-
Type:
(request: RequestPurchaseProps) => Promise<void> -
Description: Initiate a purchase request
-
Parameters:
request: Purchase request configuration
-
Example:
const buyProduct = async (productId: string) => {
try {
// In hook: returns void. Listen via callbacks.
await requestPurchase({
request: {
apple: {sku: productId},
google: {skus: [productId]},
},
type: 'in-app',
});
} catch (error) {
console.error('Purchase request failed:', error);
}
};
Subscription Offers
When purchasing subscriptions, you need to specify the pricing plan (offer) for each platform:
Android Subscription Offers
Android requires subscriptionOffers array containing offer tokens from fetchProducts(). Each offer token represents a specific pricing plan (base plan, introductory offer, etc.).
const buySubscription = async (subscriptionId: string) => {
// 1) Fetch subscription products first
await fetchProducts({skus: [subscriptionId], type: 'subs'});
// 2) Find the subscription and build offers
const subscription = subscriptions.find((s) => s.id === subscriptionId);
if (!subscription) return;
const subscriptionOffers = (
subscription.subscriptionOfferDetailsAndroid ?? []
).map((offer) => ({
sku: subscriptionId,
offerToken: offer.offerToken,
}));
// 3) Request purchase with offers
await requestPurchase({
request: {
apple: {sku: subscriptionId},
google: {
skus: [subscriptionId],
// Only include subscriptionOffers when offers are available
...(subscriptionOffers.length > 0 && {subscriptionOffers}),
},
},
type: 'subs',
});
};
Note: subscriptionOffers should only be included when subscription offers are available from fetchProducts(). Without offers, Android purchases will fail.
iOS Subscription Offers
iOS uses withOffer for promotional discounts configured in App Store Connect. This is optional and only needed for special promotional pricing.
const buySubscriptionWithOffer = async (
subscriptionId: string,
discountOffer?: DiscountOfferInputIOS,
) => {
await requestPurchase({
request: {
apple: {
sku: subscriptionId,
// Optional: apply promotional offer
...(discountOffer && {withOffer: discountOffer}),
},
google: {skus: [subscriptionId]},
},
type: 'subs',
});
};
Subscription helpers (hook)
-
getActiveSubscriptions(subscriptionIds?) => Promise<ActiveSubscription[]>-
Returns active subscription info and also updates
activeSubscriptionsstate. -
Exception to the hook’s void-return design: this method returns data for convenience.
-
Example:
const {getActiveSubscriptions, activeSubscriptions} = useIAP();
useEffect(() => {
if (!connected) return;
(async () => {
const subs = await getActiveSubscriptions(['premium_monthly']);
console.log('Subs from return:', subs.length);
console.log('Subs from state:', activeSubscriptions.length);
})();
}, [connected]);
-
-
hasActiveSubscriptions(subscriptionIds?) => Promise<boolean>- Boolean convenience check to see if any active subscriptions exist (optionally filtered by IDs).
Removed in v2.9.0:
purchaseHistoriesstate andgetPurchaseHistories()method. UsegetAvailablePurchases()andavailablePurchasesinstead.
getAvailablePurchases
-
Type:
() => Promise<void> -
Description: Fetch available purchases (restorable items) from the store
-
Example:
const restorePurchases = async () => {
try {
// Updates `availablePurchases` state; do not expect a return value
await getAvailablePurchases();
// Read from state afterwards
console.log('Available purchases count:', availablePurchases.length);
} catch (error) {
console.error('Failed to fetch available purchases:', error);
}
};
validateReceipt (Deprecated)
⚠️ Deprecated: Use
verifyPurchaseinstead. This function will be removed in a future version.
- Type:
(options: VerifyPurchaseProps) => Promise<VerifyPurchaseResult> - Description: Validate a purchase receipt using platform-specific verification
- Parameters:
options(object): Platform-specific verification parametersapple?(object): Apple App Store verification optionssku(string): Product SKU to validate
google?(object): Google Play Store verification optionssku(string): Product SKU to validateaccessToken(string): Google OAuth2 access token for API authenticationpackageName(string): Android package name (e.g.,com.example.app)purchaseToken(string): Purchase token from the purchase responseisSub?(boolean): Whether this is a subscription purchase
horizon?(object): Meta Horizon verification optionssku(string): SKU for the add-on itemaccessToken(string): Meta API access tokenuserId(string): User ID to verify purchase for
- Returns: Promise resolving to platform-specific validation result
Important Platform Differences:
-
iOS: Only requires
apple.sku- uses StoreKit 2's built-in verification -
Android: Requires all
googleparameters - calls Google Play Developer API -
Horizon: Requires all
horizonparameters - calls Meta's S2S API -
Example:
const validatePurchase = async (purchase: Purchase) => {
try {
// Cross-platform validation
const result = await validateReceipt({
// iOS options (only needs SKU)
apple: {
sku: purchase.productId,
},
// Android options (requires all parameters)
google: {
sku: purchase.productId,
packageName: purchase.packageNameAndroid ?? '',
purchaseToken: purchase.purchaseToken ?? '',
accessToken: 'your-google-api-access-token',
isSub: false, // Set to true for subscriptions
},
});
console.log('Validation result:', result);
return result;
} catch (error) {
console.error('Validation failed:', error);
throw error;
}
};
// Platform-specific examples
const validateIOSOnly = async (sku: string) => {
const result = await validateReceipt({
apple: {sku},
});
return result;
};
const validateAndroidOnly = async (purchase: Purchase) => {
const result = await validateReceipt({
google: {
sku: purchase.productId,
packageName: purchase.packageNameAndroid!,
purchaseToken: purchase.purchaseToken!,
accessToken: 'your-google-api-access-token',
isSub: false,
},
});
return result;
};
getPromotedProductIOS
-
Type:
() => Promise<any | null> -
Description: Get the promoted product details (iOS only)
-
Example:
const handlePromotedProduct = async () => {
const promotedProduct = await getPromotedProductIOS();
if (promotedProduct) {
console.log('Promoted product:', promotedProduct);
// Show custom purchase UI
}
};
requestPurchaseOnPromotedProductIOS
-
Type:
() => Promise<boolean> -
Description: Complete the purchase of a promoted product (iOS only)
Deprecated: In StoreKit 2, promoted products can be purchased directly via the standard
requestPurchase()flow. UsepromotedProductListenerIOSto receive the product ID when a user taps a promoted product, then callrequestPurchase()with the received SKU directly. -
Example:
// Recommended approach (StoreKit 2)
promotedProductListenerIOS(async (product) => {
await requestPurchase({
request: { apple: { sku: product.id } },
type: 'in-app',
});
});
// Legacy approach (deprecated)
const completePurchase = async () => {
try {
const success = await requestPurchaseOnPromotedProductIOS();
if (success) {
console.log('Promoted product purchase completed');
}
} catch (error) {
console.error('Failed to purchase promoted product:', error);
}
};
Platform-Specific Usage
iOS Example
const IOSPurchaseExample = () => {
const {connected, products, requestPurchase, validateReceipt} = useIAP({
onPurchaseSuccess: async (purchase) => {
// Validate receipt on iOS
const validation = await validateReceipt(purchase.productId);
if (validation.isValid) {
unlockContent(purchase.productId);
}
},
});
const buyProduct = (product: Product) => {
requestPurchase({
request: {
apple: {sku: product.id},
google: {skus: [product.id]},
},
type: 'in-app',
});
};
return (
<View>
{products
.filter((p) => p.platform === 'ios')
.map((product) => (
<Button
key={product.id}
title={`${product.title} - ${product.displayPrice}`}
onPress={() => buyProduct(product)}
/>
))}
</View>
);
};
Android Example
const AndroidPurchaseExample = () => {
const {connected, products, requestPurchase} = useIAP({
onPurchaseSuccess: (purchase) => {
// Android purchases are automatically validated by Google Play
unlockContent(purchase.productId);
},
});
const buyProduct = (product: Product) => {
requestPurchase({
request: {
apple: {sku: product.id},
google: {skus: [product.id]},
},
type: 'in-app',
});
};
return (
<View>
{products
.filter((p) => p.platform === 'android')
.map((product) => (
<Button
key={product.id}
title={`${product.title} - ${product.displayPrice}`}
onPress={() => buyProduct(product)}
/>
))}
</View>
);
};
Error Handling
The useIAP hook integrates with the centralized error handling system:
const {requestPurchase} = useIAP({
onPurchaseError: (error) => {
// Error is automatically typed as PurchaseError
switch (error.code) {
case ErrorCode.UserCancelled:
// Don't show error for user cancellation
break;
case ErrorCode.NetworkError:
Alert.alert('Network Error', 'Please check your connection');
break;
case ErrorCode.ItemUnavailable:
Alert.alert(
'Item Unavailable',
'This item is not available for purchase',
);
break;
default:
Alert.alert('Purchase Failed', error.message);
}
},
});
Best Practices
-
Always check
connectedbefore making IAP calls:useEffect(() => {
if (connected) {
fetchProducts({skus: productIds, type: 'in-app'});
}
}, [connected, fetchProducts]); -
Handle loading states:
const [loading, setLoading] = useState(false);
const buyProduct = async (productId: string) => {
setLoading(true);
try {
await requestPurchase({
request: {
apple: {sku: productId},
google: {skus: [productId]},
},
type: 'in-app',
});
} finally {
setLoading(false);
}
}; -
Implement proper error handling:
const handleError = (error: PurchaseError) => {
// Log for debugging
console.error('IAP Error:', error);
// Show user-friendly message
if (error.code !== ErrorCode.UserCancelled) {
Alert.alert('Purchase Failed', error.message);
}
};
Promoted Products (iOS Only)
Handle App Store promoted products when users tap on them in the App Store:
const PromotedProductExample = () => {
const {promotedProductIOS, requestPurchaseOnPromotedProductIOS} = useIAP({
onPromotedProductIOS: (product) => {
console.log('Promoted product detected:', product);
},
onPurchaseSuccess: (purchase) => {
// Recommended: handle success via callback
},
onPurchaseError: (error) => {
// Recommended: handle errors via callback
},
});
useEffect(() => {
if (promotedProductIOS) {
handlePromotedProduct();
}
}, [promotedProductIOS]);
const handlePromotedProduct = async () => {
try {
// Show your custom purchase UI
const confirmed = await showPurchaseConfirmation(promotedProductIOS);
if (confirmed) {
// Complete the promoted purchase
await requestPurchaseOnPromotedProductIOS();
}
} catch (error) {
console.error('Error handling promoted product:', error);
}
};
const showPurchaseConfirmation = async (product: any) => {
return new Promise((resolve) => {
Alert.alert(
'Purchase Product',
`Would you like to purchase ${product.localizedTitle} for ${product.price}?`,
[
{text: 'Cancel', onPress: () => resolve(false), style: 'cancel'},
{text: 'Buy', onPress: () => resolve(true)},
],
);
});
};
return <View>{/* Your regular store UI */}</View>;
};
