useIAP Hook
The useIAP
hook is the main interface for interacting with in-app purchases in Expo IAP. It provides a comprehensive API for managing purchases, subscriptions, and error handling.
Import
import {useIAP} from 'expo-iap';
Basic Usage
const {
connected,
products,
subscriptions,
currentPurchase,
currentPurchaseError,
getProducts,
getSubscriptions,
requestPurchase,
validateReceipt,
} = useIAP({
onPurchaseSuccess: (purchase) => {
console.log('Purchase successful:', purchase);
},
onPurchaseError: (error) => {
console.error('Purchase failed:', error);
},
onSyncError: (error) => {
console.warn('Sync error:', 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;
onSyncError?: (error: Error) => void;
autoFinishTransactions?: boolean; // Default: true
}
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.E_USER_CANCELLED) {
Alert.alert('Purchase Failed', error.message);
}
};
onSyncError
- Type:
(error: Error) => void
- Description: Called when there's an error syncing with the store
- Example:
onSyncError: (error) => {
console.warn('Store sync error:', 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
getProducts(['product.id']);
}
products
- Type:
Product[]
- Description: Array of available products
- Example:
products.map((product) => <ProductItem key={product.id} product={product} />);
subscriptions
- Type:
SubscriptionProduct[]
- Description: Array of available subscription products
- Example:
subscriptions.map((subscription) => (
<SubscriptionItem key={subscription.id} subscription={subscription} />
));
currentPurchase
- Type:
Purchase | null
- Description: Currently active purchase (if any)
- Example:
useEffect(() => {
if (currentPurchase) {
processPurchase(currentPurchase);
}
}, [currentPurchase]);
currentPurchaseError
- Type:
PurchaseError | null
- Description: Current purchase error (if any)
- Example:
useEffect(() => {
if (currentPurchaseError) {
handlePurchaseError(currentPurchaseError);
}
}, [currentPurchaseError]);
Methods
getProducts
- Type:
(productIds: string[]) => Promise<Product[]>
- Description: Fetch products from the store
- Parameters:
productIds
: Array of product IDs to fetch
- Returns: Promise resolving to array of products
- Example:
const fetchProducts = async () => {
try {
const products = await getProducts([
'com.app.premium',
'com.app.coins_100',
]);
console.log('Fetched products:', products);
} catch (error) {
console.error('Failed to fetch products:', error);
}
};
getSubscriptions
- Type:
(subscriptionIds: string[]) => Promise<SubscriptionProduct[]>
- Description: Fetch subscription products from the store
- Parameters:
subscriptionIds
: Array of subscription IDs to fetch
- Returns: Promise resolving to array of subscription products
- Example:
const fetchSubscriptions = async () => {
try {
const subs = await getSubscriptions([
'com.app.premium_monthly',
'com.app.premium_yearly',
]);
console.log('Fetched subscriptions:', subs);
} catch (error) {
console.error('Failed to fetch subscriptions:', error);
}
};
requestPurchase
- Type:
(request: RequestPurchaseProps) => Promise<void>
- Description: Initiate a purchase request
- Parameters:
request
: Purchase request configuration
- Example:
const buyProduct = async (productId: string) => {
try {
await requestPurchase({
request: {sku: productId},
});
} catch (error) {
console.error('Purchase request failed:', error);
}
};
validateReceipt
- Type:
(productId: string, params?: ValidationParams) => Promise<ValidationResult>
- Description: Validate a purchase receipt
- Parameters:
productId
: ID of the product to validateparams
: Optional validation parameters (Android)
- Returns: Promise resolving to validation result
- Example:
const validatePurchase = async (productId: string) => {
try {
const result = await validateReceipt(productId);
if (result.isValid) {
console.log('Receipt is valid');
// Grant access to content
}
} catch (error) {
console.error('Validation failed:', 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) => {
if (product.platform === 'ios') {
requestPurchase({
request: {sku: product.id},
});
}
};
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) => {
if (product.platform === 'android') {
requestPurchase({
request: {skus: [product.id]},
});
}
};
return (
<View>
{products
.filter((p) => p.platform === 'android')
.map((product) => (
<Button
key={product.id}
title={`${product.title} - ${product.oneTimePurchaseOfferDetails?.formattedPrice}`}
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.E_USER_CANCELLED:
// Don't show error for user cancellation
break;
case ErrorCode.E_NETWORK_ERROR:
Alert.alert('Network Error', 'Please check your connection');
break;
case ErrorCode.E_ITEM_UNAVAILABLE:
Alert.alert(
'Item Unavailable',
'This item is not available for purchase',
);
break;
default:
Alert.alert('Purchase Failed', error.message);
}
},
});
Best Practices
-
Always check
connected
before making IAP calls:useEffect(() => {
if (connected) {
getProducts(productIds);
}
}, [connected]); -
Handle loading states:
const [loading, setLoading] = useState(false);
const buyProduct = async (productId: string) => {
setLoading(true);
try {
await requestPurchase({request: {sku: productId}});
} 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.E_USER_CANCELLED) {
Alert.alert('Purchase Failed', error.message);
}
}; -
Cache products to avoid repeated fetches:
const [productsLoaded, setProductsLoaded] = useState(false);
useEffect(() => {
if (connected && !productsLoaded) {
getProducts(productIds).then(() => {
setProductsLoaded(true);
});
}
}, [connected, productsLoaded]);