Complete Purchase
This example shows how to properly complete a purchase transaction with receipt validation and finishTransaction.
Complete Example
For a production-ready purchase implementation, please refer to our complete example:
This example demonstrates:
- ✅ Complete purchase flow with event listeners
- ✅ Product loading and display
- ✅ Purchase request handling
- ✅ Transaction completion with finishTransaction
- ✅ Platform-specific receipt handling
- ✅ Error handling and user feedback
- ✅ Purchase result display
Complete Purchase Flow
The critical steps after initiating a purchase are handling the purchase event, validating the receipt, and finishing the transaction.
1. Setup Purchase Listeners
import {
purchaseUpdatedListener,
purchaseErrorListener,
type Purchase,
type NitroPurchaseResult,
} from 'react-native-iap'
useEffect(() => {
// Set up purchase success listener
const updateSubscription = purchaseUpdatedListener((purchase: Purchase) => {
handlePurchaseUpdate(purchase)
})
// Set up purchase error listener
const errorSubscription = purchaseErrorListener(
(error: NitroPurchaseResult) => {
handlePurchaseError(error)
}
)
// Cleanup
return () => {
updateSubscription.remove()
errorSubscription.remove()
}
}, [])
2. Handle Successful Purchase
const handlePurchaseUpdate = async (purchase: Purchase) => {
console.log('✅ Purchase successful:', purchase)
try {
// Step 1: Validate receipt (implement server-side validation)
const isValid = await validateReceiptOnServer(purchase)
if (isValid) {
// Step 2: Grant purchase to user
await grantPurchaseToUser(purchase)
// Step 3: Finish the transaction (required)
await finishTransaction({
purchase,
isConsumable: true, // Set based on your product type
})
console.log('Transaction finished successfully')
Alert.alert('Success', 'Thank you for your purchase!')
} else {
Alert.alert('Error', 'Purchase validation failed')
}
} catch (error) {
console.error('Failed to complete purchase:', error)
Alert.alert('Error', 'Failed to process purchase')
}
}
3. Handle Purchase Errors
const handlePurchaseError = (error: NitroPurchaseResult) => {
console.error('❌ Purchase failed:', error)
if (error.code === 'E_USER_CANCELLED') {
// User cancelled - no action needed
console.log('User cancelled the purchase')
} else {
Alert.alert('Purchase Failed', error.message || 'Unknown error')
}
}
4. Initiate Purchase
const handlePurchase = async (productId: string) => {
try {
// Request purchase - results come through event listeners
await requestPurchase({
request: {
ios: {
sku: productId,
quantity: 1,
},
android: {
skus: [productId],
},
},
type: 'inapp',
})
// Purchase result will be handled by purchaseUpdatedListener
console.log('Purchase request sent - waiting for result')
} catch (error) {
console.error('Purchase request failed:', error)
Alert.alert('Error', 'Failed to initiate purchase')
}
}
Key Implementation Points
Receipt Validation
Always validate receipts server-side before granting purchases:
const validateReceiptOnServer = async (purchase: Purchase) => {
const receipt =
Platform.OS === 'ios' ? purchase.transactionReceipt : purchase.purchaseToken
const response = await fetch('https://your-server.com/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
platform: Platform.OS,
productId: purchase.productId,
receipt: receipt,
transactionId: purchase.transactionId,
}),
})
const result = await response.json()
return result.isValid
}
Platform-Specific Receipt Data
Different platforms provide different receipt formats:
// iOS Receipt
const iosReceipt = purchase.transactionReceipt // Base64 receipt
const purchaseToken = purchase.purchaseToken // JWS representation on iOS (StoreKit 2), purchase token on Android
// Note: jwsRepresentationIOS is deprecated, use purchaseToken instead
// Android Receipt
const androidReceipt = (purchase as any).dataAndroid // Purchase data JSON
const signature = (purchase as any).signatureAndroid // Signature for validation
Transaction Completion
Always call finishTransaction
after processing:
await finishTransaction({
purchase,
isConsumable: true, // true for consumable products
})
- Consumable products: Set
isConsumable: true
(can be purchased multiple times) - Non-consumables & Subscriptions: Set
isConsumable: false
(one-time purchase)
Best Practices
- Always use event listeners - Purchase results come through listeners, not return values
- Validate receipts server-side - Never trust client-side validation alone
- Call finishTransaction - Required to complete the transaction
- Handle all error cases - Including user cancellation
- Provide user feedback - Show loading states and results
- Store transaction records - Keep purchase history in your backend
Complete Working Example
See the full implementation: PurchaseFlow.tsx
See Also
- Basic Store Example - Simple product purchase flow
- Subscription Manager - Subscription-specific implementation
- useIAP Hook - Alternative React Hook approach
- Error Handling - Comprehensive error management