Skip to main content
Version: 14.1 (Current)

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:

📱 PurchaseFlow.tsx

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

  1. Always use event listeners - Purchase results come through listeners, not return values
  2. Validate receipts server-side - Never trust client-side validation alone
  3. Call finishTransaction - Required to complete the transaction
  4. Handle all error cases - Including user cancellation
  5. Provide user feedback - Show loading states and results
  6. Store transaction records - Keep purchase history in your backend

Complete Working Example

See the full implementation: PurchaseFlow.tsx

See Also