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.inapporPurchaseType.subs)
Request Structure
RequestPurchaseProps (v8.2.0+ Recommended)
Use the factory constructors for type-safe purchase requests:
// For in-app products
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(sku: 'product_id'),
google: RequestPurchaseAndroidProps(skus: ['product_id']),
))
// For subscriptions
RequestPurchaseProps.subs((
apple: RequestPurchaseIosProps(sku: 'subscription_id'),
google: RequestPurchaseAndroidProps(skus: ['subscription_id']),
))
RequestPurchase (Legacy)
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 (must be UUID format)
final Map<String, dynamic>? withOffer; // Promotional offer
final String? advancedCommerceData; // Attribution data (iOS 15+)
}
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 (Recommended v8.2.0+)
// Simple product purchase using RequestPurchaseProps
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(sku: 'com.example.premium'),
google: RequestPurchaseAndroidProps(skus: ['com.example.premium']),
)),
);
Purchase with User Identifier
// Purchase with user account token for restoration
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(
sku: 'com.example.premium',
appAccountToken: userId, // Must be UUID format (e.g., '550e8400-e29b-41d4-a716-446655440000')
),
google: RequestPurchaseAndroidProps(
skus: ['com.example.premium'],
obfuscatedAccountIdAndroid: userId,
),
)),
);
Subscription with Promotional Offer (iOS)
// iOS subscription with promotional offer
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps.subs((
apple: RequestPurchaseIosProps(
sku: 'com.example.monthly',
withOffer: PaymentDiscount(
identifier: 'promo_50_off',
keyIdentifier: 'ABCDEF123456',
nonce: generateNonce(),
signature: generateSignature(), // Server-generated
timestamp: DateTime.now().millisecondsSinceEpoch,
),
),
google: RequestPurchaseAndroidProps(skus: ['com.example.monthly']),
)),
);
Subscription Upgrade/Downgrade (Android)
// Android subscription change with proration
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps.subs((
apple: RequestPurchaseIosProps(sku: 'com.example.yearly'),
google: RequestPurchaseAndroidProps(
skus: ['com.example.yearly'],
purchaseToken: currentSubscriptionToken,
prorationMode: AndroidProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE,
),
)),
);
Purchase with Attribution Data (iOS 15+)
// iOS purchase with advanced commerce data for attribution tracking
await FlutterInappPurchase.instance.requestPurchaseWithBuilder(
build: (builder) {
builder.ios.sku = 'com.example.premium';
builder.ios.advancedCommerceData = 'campaign_summer_2025';
builder.type = ProductQueryType.InApp;
},
);
The advancedCommerceData field uses StoreKit 2's Product.PurchaseOption.custom API to pass campaign tokens, affiliate IDs, or other attribution data during purchases.
Complete Implementation
class PurchaseService {
final _iap = FlutterInappPurchase.instance;
Future<void> purchaseProduct(String productId, {bool isSubscription = false}) async {
try {
final userId = await _getUserId();
// Initiate purchase using RequestPurchaseProps (v8.2.0+)
if (isSubscription) {
await _iap.requestPurchase(
RequestPurchaseProps.subs((
apple: RequestPurchaseIosProps(
sku: productId,
appAccountToken: userId, // Must be UUID format
),
google: RequestPurchaseAndroidProps(
skus: [productId],
obfuscatedAccountIdAndroid: userId,
),
)),
);
} else {
await _iap.requestPurchase(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(
sku: productId,
appAccountToken: userId, // Must be UUID format
),
google: RequestPurchaseAndroidProps(
skus: [productId],
obfuscatedAccountIdAndroid: userId,
),
)),
);
}
// Purchase result will be received via purchaseUpdatedListener stream
} on PurchaseError catch (e) {
_handlePurchaseError(e);
} catch (e) {
print('Unexpected error: $e');
}
}
void _handlePurchaseError(PurchaseError error) {
switch (error.code) {
case ErrorCode.UserCancelled:
print('User cancelled the purchase');
break;
case ErrorCode.AlreadyOwned:
print('Product already owned');
break;
case ErrorCode.ServiceError:
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.instance.purchaseUpdatedListener.listen((purchase) {
print('Purchase successful: ${purchase.productId}');
// Handle purchase state (v8.2.0+: unified across platforms)
switch (purchase.purchaseState) {
case PurchaseState.Purchased:
// Verify the purchase
_verifyPurchase(purchase);
// Deliver the content
_deliverContent(purchase.productId);
// Finish the transaction
_finishTransaction(purchase);
break;
case PurchaseState.Pending:
print('Purchase pending');
break;
case PurchaseState.Unknown:
print('Unknown purchase state');
break;
}
});
// Listen to purchase errors
FlutterInappPurchase.instance.purchaseErrorListener.listen((error) {
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(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(sku: productId),
google: RequestPurchaseAndroidProps(skus: [productId]),
)),
);
} catch (e) {
// Handle errors
if (e is PurchaseError) {
switch (e.code) {
case ErrorCode.NotInitialized:
// Reinitialize connection
await _iap.initConnection();
break;
case ErrorCode.ItemUnavailable:
// Product not available
showError('Product not available');
break;
default:
showError(e.message);
}
}
} finally {
_isPurchasing = false;
}
}
Using the Builder DSL
requestPurchaseWithBuilder exposes a fluent builder for composing
platform-specific purchase parameters without manual object construction.
await FlutterInappPurchase.instance.requestPurchaseWithBuilder(
build: (RequestPurchaseBuilder r) => r
..type = ProductType.Subs
..withIOS((RequestPurchaseIosBuilder i) => i..sku = 'your.sku1')
..withAndroid(
(RequestPurchaseAndroidBuilder a) => a..skus = ['your.sku1'],
),
);
Use the builder when you:
- Need to configure different options per platform.
- Prefer a DSL-style API with chained modifiers.
- Want safeguards against unsupported
ProductType.Allusage—the builder only allows in-app or subscription types.
Related Methods
fetchProducts()- Fetch products before purchasingfinishTransaction()- Complete the purchaserequestPurchase()- 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
advancedCommerceDatarequires iOS 15+ and StoreKit 2
Android
- Supports multiple SKUs but typically uses one
- Proration modes only apply to subscriptions
- Requires acknowledgment within 3 days
- Use
googlefield for request parameters (v8.2.0+)
