Purchases
Complete guide to implementing in-app purchases with flutter_inapp_purchase v8.1+.
Purchase Flow
- Initialize Connection - Connect to the store
- Setup Listeners - Handle purchase updates and errors
- Load Products - Fetch available products
- Request Purchase - Initiate purchase
- Deliver Content - Provide purchased content
- Finish Transaction - Complete the transaction
Initialize Connection
final iap = FlutterInappPurchase.instance;
await iap.initConnection();
Setup Purchase Listeners
StreamSubscription<Purchase>? _purchaseUpdatedSubscription;
StreamSubscription<PurchaseError>? _purchaseErrorSubscription;
void setupListeners() {
_purchaseUpdatedSubscription = iap.purchaseUpdatedListener.listen(
(purchase) {
debugPrint('Purchase received: ${purchase.productId}');
_handlePurchase(purchase);
},
);
_purchaseErrorSubscription = iap.purchaseErrorListener.listen(
(error) {
debugPrint('Purchase error: ${error.message}');
_handleError(error);
},
);
}
void dispose() {
_purchaseUpdatedSubscription?.cancel();
_purchaseErrorSubscription?.cancel();
super.dispose();
}
Load Products
final products = await iap.fetchProducts(
skus: ['product_id_1', 'product_id_2'],
type: ProductQueryType.inApp,
);
for (final product in products) {
print('${product.title}: ${product.displayPrice}');
}
Request Purchase
Using the builder pattern (recommended):
await iap.requestPurchaseWithBuilder(
build: (builder) {
builder.ios.sku = 'product_id';
builder.ios.appAccountToken = userId; // Must be UUID format
builder.android.skus = ['product_id'];
builder.android.obfuscatedAccountIdAndroid = userId;
builder.type = ProductQueryType.InApp;
},
);
⚠️ Important: The
appAccountTokenmust be a valid UUID format (e.g.,550e8400-e29b-41d4-a716-446655440000). If a non-UUID value is provided, Apple will silently returnnullfor this field in the purchase response. See OpenIAP Request Types for details.
Or using props directly:
await iap.requestPurchase(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(
sku: 'product_id',
appAccountToken: userId, // Must be UUID format
),
google: RequestPurchaseAndroidProps(
skus: ['product_id'],
obfuscatedAccountIdAndroid: userId,
),
useAlternativeBilling: null,
)),
);
With Attribution Data (iOS 15+)
await iap.requestPurchaseWithBuilder(
build: (builder) {
builder.ios.sku = 'product_id';
builder.ios.advancedCommerceData = 'campaign_token';
builder.type = ProductQueryType.InApp;
},
);
Handle Purchase
Future<void> _handlePurchase(Purchase purchase) async {
// 1. Validate purchase on your server (required for production)
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, // or false for non-consumables/subscriptions
);
}
Product Types
Consumable Products
Products that can be purchased multiple times (coins, gems):
await iap.finishTransaction(
purchase: purchase,
isConsumable: true, // Consumes on Android, finishes on iOS
);
Non-Consumable Products
One-time purchases (premium features, ad removal):
await iap.finishTransaction(
purchase: purchase,
isConsumable: false, // Acknowledges on Android, finishes on iOS
);
Subscriptions
Recurring purchases - see Subscription Offers
Restore Purchases
// Restore previous purchases
await iap.restorePurchases();
// Get available purchases
final purchases = await iap.getAvailablePurchases();
for (final purchase in purchases) {
await deliverContent(purchase.productId);
}
Best Practices
- Always set up listeners first before making purchase requests
- Validate purchases server-side for security
- Use correct
isConsumableflag - it handles consume/acknowledge automatically - Handle errors gracefully - see Error Handling
- Test thoroughly in sandbox environments
Complete Example
See working implementations in Examples.
Next Steps
- Purchase Lifecycle - Understand the full lifecycle
- Subscription Offers - Handle subscriptions
- Error Handling - Handle purchase errors
