Core Methods
Essential methods for implementing in-app purchases with flutter_inapp_purchase v7.0. All methods follow the OpenIAP specification and support both iOS and Android platforms.
All methods are available through the singleton instance:
final iap = FlutterInappPurchase.instance;
Event Streams
purchaseUpdatedListener
Stream that emits successful purchases.
Stream<Purchase> get purchaseUpdatedListener
Example:
StreamSubscription? _purchaseUpdatedSubscription;
_purchaseUpdatedSubscription = iap.purchaseUpdatedListener.listen(
(purchase) {
debugPrint('Purchase received: ${purchase.productId}');
_handlePurchase(purchase);
},
);
// Don't forget to cancel in dispose
void dispose() {
_purchaseUpdatedSubscription?.cancel();
super.dispose();
}
purchaseErrorListener
Stream that emits purchase errors.
Stream<PurchaseError> get purchaseErrorListener
Example:
StreamSubscription? _purchaseErrorSubscription;
_purchaseErrorSubscription = iap.purchaseErrorListener.listen(
(error) {
debugPrint('Purchase error: ${error.code} - ${error.message}');
_handleError(error);
},
);
Connection Management
initConnection()
Initializes the connection to the platform store.
Future<bool> initConnection({
AlternativeBillingModeAndroid? alternativeBillingModeAndroid,
}) async
Parameters:
alternativeBillingModeAndroid
- Android alternative billing mode (optional)
Returns: true
if initialization successful
Example:
try {
await iap.initConnection();
debugPrint('IAP connection initialized');
} catch (e) {
debugPrint('Failed to initialize IAP: $e');
}
Platform Differences:
- iOS: Connects to StoreKit 2 (iOS 15+) or StoreKit 1 (fallback)
- Android: Connects to Google Play Billing Library v6+
endConnection()
Ends the connection to the platform store.
Future<bool> endConnection() async
Returns: true
if connection ended successfully
Example:
await iap.endConnection();
Product Loading
fetchProducts()
Loads product information from the store.
Future<FetchProductsResult> fetchProducts({
required List<String> skus,
ProductQueryType? type,
}) async
Parameters:
skus
- List of product identifierstype
- OptionalProductQueryType.InApp
orProductQueryType.Subs
Returns: FetchProductsResult
- union type containing either products or subscriptions
Example:
final result = await iap.fetchProducts(
skus: ['product_1', 'premium_upgrade'],
type: ProductQueryType.InApp,
);
if (result is FetchProductsResultProducts) {
for (final product in result.products) {
if (product is ProductIOS) {
debugPrint('iOS Product: ${product.displayName} - ${product.displayPrice}');
} else if (product is ProductAndroid) {
debugPrint('Android Product: ${product.title}');
}
}
}
Purchase Processing
requestPurchase()
Initiates a purchase request.
Future<RequestPurchaseResult> requestPurchase(
RequestPurchaseProps params,
) async
Parameters:
params
- Purchase request props (useRequestPurchaseProps.inApp()
orRequestPurchaseProps.subs()
)
Example (In-App Purchase):
await iap.requestPurchase(
RequestPurchaseProps.inApp((
ios: RequestPurchaseIosProps(sku: 'product_id'),
android: RequestPurchaseAndroidProps(skus: ['product_id']),
)),
);
Example (Subscription):
await iap.requestPurchase(
RequestPurchaseProps.subs(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(sku: 'subscription_id'),
android: RequestPurchaseAndroidProps(skus: ['subscription_id']),
),
),
);
Important: Results are delivered via purchaseUpdatedListener
and purchaseErrorListener
, not as a return value.
requestPurchaseWithBuilder()
Builder-style purchase request (alternative API).
Future<RequestPurchaseResult> requestPurchaseWithBuilder({
required RequestPurchaseBuilder Function(RequestPurchaseBuilder) build,
}) async
Example:
await iap.requestPurchaseWithBuilder(
build: (builder) => builder
..type = ProductType.InApp
..withIOS((ios) => ios..sku = 'product_id')
..withAndroid((android) => android..skus = ['product_id']),
);
Transaction Management
finishTransaction()
Completes a transaction after successful purchase processing.
Future<String?> finishTransaction({
required Purchase purchase,
bool isConsumable = false,
}) async
Parameters:
purchase
- The purchase to finishisConsumable
- Whether the product is consumable (consumes on Android, finishes on iOS)
Example:
iap.purchaseUpdatedListener.listen((purchase) async {
// Verify purchase on server
final isValid = await verifyPurchaseOnServer(purchase);
if (!isValid) return;
// Deliver content
await deliverContent(purchase.productId);
// Finish transaction
await iap.finishTransaction(
purchase: purchase,
isConsumable: true, // For consumable products
);
});
Platform Behavior:
- iOS: Calls
finishTransaction
on the transaction - Android: Calls
consumePurchase
(ifisConsumable: true
) oracknowledgePurchase
(ifisConsumable: false
)
Purchase History
getAvailablePurchases()
Gets available purchases with optional filtering.
Future<List<Purchase>> getAvailablePurchases({
bool? onlyIncludeActiveItemsIOS,
bool? alsoPublishToEventListenerIOS,
}) async
Parameters:
onlyIncludeActiveItemsIOS
- Whentrue
(default), excludes expired subscriptions (iOS only)alsoPublishToEventListenerIOS
- Whentrue
, replays purchases throughpurchaseUpdatedListener
(iOS only)
Returns: List of available purchases
Example:
// Get only active purchases (default)
final purchases = await iap.getAvailablePurchases();
// Include expired subscriptions (iOS)
final allPurchases = await iap.getAvailablePurchases(
onlyIncludeActiveItemsIOS: false,
);
restorePurchases()
Restores previous purchases.
Future<List<Purchase>> restorePurchases() async
Returns: List of restored purchases
Example:
final restored = await iap.restorePurchases();
debugPrint('Restored ${restored.length} purchases');
for (final purchase in restored) {
await deliverContent(purchase.productId);
}
Subscription Management
getActiveSubscriptions()
Gets lightweight subscription status (recommended for quick checks).
Future<List<ActiveSubscription>> getActiveSubscriptions(
List<String> skus,
) async
Parameters:
skus
- List of subscription product IDs to check
Returns: List of ActiveSubscription
objects (lightweight)
Example:
final subscriptions = await iap.getActiveSubscriptions([
'monthly_sub',
'yearly_sub',
]);
for (final sub in subscriptions) {
debugPrint('${sub.productId}: active=${sub.isActive}');
}
Use Case: Quick subscription status checks without full purchase details.
hasActiveSubscriptions()
Checks if user has any active subscriptions.
Future<bool> hasActiveSubscriptions(List<String> skus) async
Parameters:
skus
- List of subscription product IDs to check
Returns: true
if any subscription is active
Example:
final hasActive = await iap.hasActiveSubscriptions([
'monthly_sub',
'yearly_sub',
]);
if (hasActive) {
// Show premium content
}
Platform-Specific Methods
iOS-Specific Methods
presentCodeRedemptionSheetIOS()
Presents the App Store code redemption sheet.
Future<void> presentCodeRedemptionSheetIOS() async
Example:
if (Platform.isIOS) {
await iap.presentCodeRedemptionSheetIOS();
}
Requirements: iOS 14.0+
showManageSubscriptionsIOS()
Shows the subscription management interface.
Future<void> showManageSubscriptionsIOS({
String? productId,
}) async
Example:
if (Platform.isIOS) {
await iap.showManageSubscriptionsIOS(
productId: 'subscription_id', // Optional
);
}
isEligibleForIntroOfferIOS()
Checks if user is eligible for introductory offer.
Future<bool> isEligibleForIntroOfferIOS(String productId) async
Example:
if (Platform.isIOS) {
final eligible = await iap.isEligibleForIntroOfferIOS('subscription_id');
debugPrint('Eligible for intro offer: $eligible');
}
subscriptionStatusIOS()
Gets detailed subscription status (iOS only).
Future<List<SubscriptionStatusIOS>> subscriptionStatusIOS(
List<String> productIds,
) async
Example:
if (Platform.isIOS) {
final statuses = await iap.subscriptionStatusIOS(['subscription_id']);
for (final status in statuses) {
debugPrint('Status: ${status.state}');
}
}
Android-Specific Methods
deepLinkToSubscriptions()
Opens the Google Play subscription management page.
Future<void> deepLinkToSubscriptions({
DeepLinkOptions? options,
}) async
Example:
if (Platform.isAndroid) {
await iap.deepLinkToSubscriptions(
options: DeepLinkOptions(productId: 'subscription_id'),
);
}
acknowledgePurchaseAndroid()
Acknowledges a purchase on Android.
Future<void> acknowledgePurchaseAndroid({
required String purchaseToken,
}) async
Example:
if (Platform.isAndroid) {
await iap.acknowledgePurchaseAndroid(
purchaseToken: purchase.purchaseToken,
);
}
Note: Use finishTransaction()
for cross-platform compatibility.
consumePurchaseAndroid()
Consumes a purchase on Android.
Future<void> consumePurchaseAndroid({
required String purchaseToken,
}) async
Example:
if (Platform.isAndroid) {
await iap.consumePurchaseAndroid(
purchaseToken: purchase.purchaseToken,
);
}
Note: Use finishTransaction(isConsumable: true)
for cross-platform compatibility.
Best Practices
1. Always Set Up Listeners First
void initState() {
super.initState();
_setupIAP();
}
Future<void> _setupIAP() async {
// Set up listeners BEFORE initConnection
_purchaseUpdatedSubscription = iap.purchaseUpdatedListener.listen(
(purchase) => _handlePurchase(purchase),
);
_purchaseErrorSubscription = iap.purchaseErrorListener.listen(
(error) => _handleError(error),
);
// Then initialize
await iap.initConnection();
}
2. Handle Purchase Results in Listeners
// ❌ Wrong: Expecting result from requestPurchase
final result = await iap.requestPurchase(...); // Returns immediately
// ✅ Correct: Handle in listener
iap.purchaseUpdatedListener.listen((purchase) {
// Purchase succeeded
_handlePurchase(purchase);
});
iap.purchaseErrorListener.listen((error) {
// Purchase failed
_handleError(error);
});
3. Always Verify Purchases Server-Side
Future<void> _handlePurchase(Purchase purchase) async {
// 1. Verify on server
final isValid = await verifyPurchaseOnServer(purchase);
if (!isValid) {
debugPrint('Invalid purchase');
return;
}
// 2. Deliver content
await deliverContent(purchase.productId);
// 3. Finish transaction
await iap.finishTransaction(
purchase: purchase,
isConsumable: false,
);
}
4. Cancel Subscriptions in Dispose
void dispose() {
_purchaseUpdatedSubscription?.cancel();
_purchaseErrorSubscription?.cancel();
super.dispose();
}
Alternative Billing Methods
iOS External Purchase Methods
presentExternalPurchaseLinkIOS()
Open an external purchase link in Safari to redirect users to your website for purchase. Requires iOS 16.0+.
Future<ExternalPurchaseLinkResultIOS> presentExternalPurchaseLinkIOS(String url)
Parameters:
url
(String): The external purchase URL to open
Returns: Future<ExternalPurchaseLinkResultIOS>
class ExternalPurchaseLinkResultIOS {
final String? error;
final bool success;
}
Example:
final result = await FlutterInappPurchase.instance
.presentExternalPurchaseLinkIOS('https://your-site.com/checkout');
if (result.error != null) {
print('Failed to open link: ${result.error}');
} else if (result.success) {
print('User redirected to external purchase website');
}
Platform: iOS 16.0+
Requirements:
- Requires Apple approval and proper provisioning profile with external purchase entitlements
- URLs must be configured in your app's Info.plist
- Deep linking recommended to return users to your app after purchase
Important Notes:
- Purchase listeners will NOT fire when using external URLs
- You must handle purchase validation on your backend
- Implement deep linking to return users to your app
See also:
Android Alternative Billing Methods
checkAlternativeBillingAvailabilityAndroid()
Check if alternative billing is available for the current user. This must be called before showing the alternative billing dialog.
Future<bool> checkAlternativeBillingAvailabilityAndroid()
Returns: Future<bool>
Example:
final isAvailable = await FlutterInappPurchase.instance
.checkAlternativeBillingAvailabilityAndroid();
if (isAvailable) {
print('Alternative billing is available');
} else {
print('Alternative billing not available for this user');
}
Platform: Android
Requirements:
- Must initialize connection with alternative billing mode
- User must be eligible for alternative billing (determined by Google)
See also: Google Play Alternative Billing documentation
showAlternativeBillingDialogAndroid()
Show Google's required information dialog to inform users about alternative billing. This must be called after checking availability and before processing payment.
Future<bool> showAlternativeBillingDialogAndroid()
Returns: Future<bool>
- Returns true
if user accepted, false
if declined
Example:
final userAccepted = await FlutterInappPurchase.instance
.showAlternativeBillingDialogAndroid();
if (userAccepted) {
print('User accepted alternative billing');
// Proceed with your payment flow
} else {
print('User declined alternative billing');
}
Platform: Android
Note: This dialog is required by Google Play's alternative billing policy. You must show this before redirecting users to your payment system.
createAlternativeBillingTokenAndroid()
Generate a reporting token after successfully processing payment through your payment system. This token must be reported to Google Play within 24 hours.
Future<String?> createAlternativeBillingTokenAndroid()
Returns: Future<String?>
- Returns the token or null
if creation failed
Example:
// After successfully processing payment in your system
final token = await FlutterInappPurchase.instance
.createAlternativeBillingTokenAndroid();
if (token != null) {
print('Token created: $token');
// Send this token to your backend to report to Google
await reportTokenToGooglePlay(token);
} else {
print('Failed to create token');
}
Platform: Android
Important:
- Token must be reported to Google Play backend within 24 hours
- Requires server-side integration with Google Play Developer API
- Failure to report will result in refund and possible account suspension
Alternative Billing Configuration
// Initialize with alternative billing mode
await FlutterInappPurchase.instance.initConnection(
alternativeBillingModeAndroid: AlternativeBillingModeAndroid.UserChoice,
// or AlternativeBillingModeAndroid.AlternativeOnly
);
// To change mode, reinitialize
await FlutterInappPurchase.instance.endConnection();
await FlutterInappPurchase.instance.initConnection(
alternativeBillingModeAndroid: AlternativeBillingModeAndroid.AlternativeOnly,
);
Billing Modes:
enum AlternativeBillingModeAndroid {
None, // Default - no alternative billing
UserChoice, // Users choose between Google Play or your payment system
AlternativeOnly, // Only your payment system is available
}
Complete Flow Example:
Future<void> purchaseWithAlternativeBilling(String productId) async {
// Step 1: Check availability
final isAvailable = await FlutterInappPurchase.instance
.checkAlternativeBillingAvailabilityAndroid();
if (!isAvailable) {
throw Exception('Alternative billing not available');
}
// Step 2: Show required dialog
final userAccepted = await FlutterInappPurchase.instance
.showAlternativeBillingDialogAndroid();
if (!userAccepted) {
throw Exception('User declined alternative billing');
}
// Step 3: Process payment in your system
final paymentResult = await processPaymentInYourSystem(productId);
if (!paymentResult.success) {
throw Exception('Payment failed');
}
// Step 4: Create reporting token
final token = await FlutterInappPurchase.instance
.createAlternativeBillingTokenAndroid();
if (token == null) {
throw Exception('Failed to create token');
}
// Step 5: Report to Google (must be done within 24 hours)
await reportToGooglePlayBackend(token, productId, paymentResult);
print('Alternative billing completed successfully');
}
See also:
See Also
- Types - Type definitions
- Error Codes - Error handling
- Purchase Lifecycle - Complete purchase flow
- Subscription Guide - Subscription management
- Alternative Billing Guide - Alternative billing implementation