requestPurchase()
Initiates a purchase flow for the specified product.
Overview
The requestPurchase()
method starts the platform's native purchase flow for a product. It handles both one-time purchases and subscriptions with platform-specific options.
Signature
Future<void> requestPurchase({
required RequestPurchase request,
required PurchaseType type,
})
Parameters
request
- Platform-specific purchase request parameterstype
- Type of purchase (PurchaseType.inapp
orPurchaseType.subs
)
Request Structure
RequestPurchase
class RequestPurchase {
final RequestPurchaseIOS? ios;
final RequestPurchaseAndroid? android;
}
RequestPurchaseIOS
class RequestPurchaseIOS {
final String sku; // Product ID
final int? quantity; // Quantity (for consumables)
final String? appAccountToken; // User identifier
final Map<String, dynamic>? withOffer; // Promotional offer
}
RequestPurchaseAndroid
class RequestPurchaseAndroid {
final List<String> skus; // Product IDs
final String? obfuscatedAccountIdAndroid; // User identifier
final String? obfuscatedProfileIdAndroid; // Profile identifier
final String? purchaseToken; // For upgrades/downgrades
final int? offerTokenIndex; // Specific offer index
final int? prorationMode; // Subscription proration
}
Usage Examples
Basic Purchase
// Simple product purchase
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: 'com.example.premium'),
android: RequestPurchaseAndroid(skus: ['com.example.premium']),
),
type: PurchaseType.inapp,
);
Purchase with User Identifier
// Purchase with user account token for restoration
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(
sku: 'com.example.premium',
appAccountToken: userId, // Your user ID
),
android: RequestPurchaseAndroid(
skus: ['com.example.premium'],
obfuscatedAccountIdAndroid: userId,
),
),
type: PurchaseType.inapp,
);
Subscription with Promotional Offer (iOS)
// iOS subscription with promotional offer
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(
sku: 'com.example.monthly',
withOffer: {
'identifier': 'promo_50_off',
'keyIdentifier': 'ABCDEF123456',
'nonce': generateNonce(),
'signature': generateSignature(), // Server-generated
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
),
),
type: PurchaseType.subs,
);
Subscription Upgrade/Downgrade (Android)
// Android subscription change with proration
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
android: RequestPurchaseAndroid(
skus: ['com.example.yearly'],
purchaseToken: currentSubscriptionToken,
prorationMode: AndroidProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE,
),
),
type: PurchaseType.subs,
);
Complete Implementation
class PurchaseService {
final _iap = FlutterInappPurchase.instance;
Future<void> purchaseProduct(String productId, {bool isSubscription = false}) async {
try {
// Create platform-specific request
final request = RequestPurchase(
ios: RequestPurchaseIOS(
sku: productId,
appAccountToken: await _getUserId(),
),
android: RequestPurchaseAndroid(
skus: [productId],
obfuscatedAccountIdAndroid: await _getUserId(),
),
);
// Initiate purchase
await _iap.requestPurchase(
request: request,
type: isSubscription ? PurchaseType.subs : PurchaseType.inapp,
);
// Purchase result will be received via purchaseUpdated stream
} on PurchaseError catch (e) {
_handlePurchaseError(e);
} catch (e) {
print('Unexpected error: $e');
}
}
void _handlePurchaseError(PurchaseError error) {
switch (error.code) {
case ErrorCode.E_USER_CANCELLED:
print('User cancelled the purchase');
break;
case ErrorCode.E_PRODUCT_ALREADY_OWNED:
print('Product already owned');
break;
case ErrorCode.E_BILLING_UNAVAILABLE:
print('Billing service unavailable');
break;
default:
print('Purchase error: ${error.message}');
}
}
Future<String?> _getUserId() async {
// Return your user identifier
return 'user123';
}
}
Handling Purchase Results
Purchase results are delivered through streams:
// Listen to successful purchases
FlutterInappPurchase.purchaseUpdated.listen((PurchasedItem? item) {
if (item != null) {
print('Purchase successful: ${item.productId}');
// Verify the purchase
_verifyPurchase(item);
// Deliver the content
_deliverContent(item.productId);
// Finish the transaction
_finishTransaction(item);
}
});
// Listen to purchase errors
FlutterInappPurchase.purchaseError.listen((PurchaseResult? error) {
if (error != null) {
print('Purchase failed: ${error.message}');
}
});
Android Proration Modes
class AndroidProrationMode {
static const int IMMEDIATE_AND_CHARGE_FULL_PRICE = 5;
static const int DEFERRED = 4;
static const int IMMEDIATE_AND_CHARGE_PRORATED_PRICE = 2;
static const int IMMEDIATE_WITHOUT_PRORATION = 3;
static const int IMMEDIATE_WITH_TIME_PRORATION = 1;
static const int UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = 0;
}
Best Practices
- User Identification: Always include a user identifier for purchase restoration
- Error Handling: Implement comprehensive error handling for all failure cases
- Loading State: Show loading indicator during purchase flow
- Double Purchase Prevention: Disable purchase button after click
Error Handling
Future<void> safePurchase(String productId) async {
// Prevent double purchases
if (_isPurchasing) return;
_isPurchasing = true;
try {
await _iap.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: productId),
android: RequestPurchaseAndroid(skus: [productId]),
),
type: PurchaseType.inapp,
);
} catch (e) {
// Handle errors
if (e is PurchaseError) {
switch (e.code) {
case ErrorCode.E_NOT_INITIALIZED:
// Reinitialize connection
await _iap.initConnection();
break;
case ErrorCode.E_ITEM_UNAVAILABLE:
// Product not available
showError('Product not available');
break;
default:
showError(e.message);
}
}
} finally {
_isPurchasing = false;
}
}
Related Methods
getProducts()
- Fetch products before purchasingfinishTransaction()
- Complete the purchaserequestSubscription()
- Legacy subscription method
Platform Notes
iOS
- Requires valid product IDs from App Store Connect
- Promotional offers need server-side signature
- Quantity only works for consumable products
Android
- Supports multiple SKUs but typically uses one
- Proration modes only apply to subscriptions
- Requires acknowledgment within 3 days