Purchase Flow
Source Code: purchase_flow_screen.dart
This example demonstrates how to implement a complete in-app purchase flow for consumable and non-consumable products.
Key Features
- Initialize connection to the store
- Fetch available products
- Handle purchase requests
- Listen to purchase events
- Finish transactions properly
Implementation Overview
1. Set Up Listeners
StreamSubscription<Purchase>? _purchaseUpdatedSubscription;
StreamSubscription<PurchaseError>? _purchaseErrorSubscription;
void initState() {
super.initState();
_setupListeners();
_initConnection();
}
void _setupListeners() {
_purchaseUpdatedSubscription = iap.purchaseUpdatedListener.listen(
(purchase) {
debugPrint('Purchase: ${purchase.productId}');
_handlePurchase(purchase);
},
);
_purchaseErrorSubscription = iap.purchaseErrorListener.listen(
(error) {
debugPrint('Error: ${error.code} - ${error.message}');
_handleError(error);
},
);
}
void dispose() {
_purchaseUpdatedSubscription?.cancel();
_purchaseErrorSubscription?.cancel();
super.dispose();
}
2. Initialize Connection
Future<void> _initConnection() async {
await iap.initConnection();
debugPrint('IAP connection initialized');
// Load products after connection
await _loadProducts();
}
3. Fetch Products
final result = await iap.fetchProducts(
skus: ['product_id_1', 'product_id_2'],
type: ProductQueryType.InApp,
);
if (result is FetchProductsResultProducts) {
final products = result.value ?? [];
debugPrint('Loaded ${products.length} products');
}
4. Request Purchase
await iap.requestPurchase(
RequestPurchaseProps.inApp((
ios: RequestPurchaseIosProps(
sku: 'product_id',
quantity: 1,
),
android: RequestPurchaseAndroidProps(
skus: ['product_id'],
),
useAlternativeBilling: null,
)),
);
5. Handle Purchase Updates
Future<void> _handlePurchase(Purchase purchase) async {
// 1. Verify purchase on server (recommended)
final isValid = await verifyPurchaseOnServer(purchase);
if (!isValid) return;
// 2. Deliver content to user
await deliverContent(purchase.productId);
// 3. Finish transaction
await iap.finishTransaction(
purchase: purchase,
isConsumable: true, // Set based on product type
);
}
6. Handle Purchase Errors
void _handleError(PurchaseError error) {
// Don't show error for user cancellation
if (error.code == ErrorCode.UserCancelled) {
return;
}
// Show error message to user
showErrorMessage('Purchase failed: ${error.message}');
}
Purchase Validation
Future<void> _handlePurchase(Purchase purchase) async {
// IMPORTANT: Server-side receipt validation should be performed here
// Send the receipt to your backend server for validation
final isValid = await validateReceiptOnServer(purchase.purchaseToken);
if (!isValid) {
showMessage('Receipt validation failed');
return;
}
// After successful server validation, finish the transaction
await iap.finishTransaction(
purchase: purchase,
isConsumable: true, // For consumable products
);
}
Best Practices
- Always set up listeners before initConnection - Prevents missing purchase events
- Verify purchases server-side - Validate receipts with your backend before granting access
- Finish transactions properly - Call
finishTransaction()
after successful validation - Handle user cancellation gracefully - Check for
ErrorCode.UserCancelled
- Clean up subscriptions - Cancel stream subscriptions in
dispose()