Skip to main content
Version: 7.0

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 identifiers
  • type - Optional ProductQueryType.InApp or ProductQueryType.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 (use RequestPurchaseProps.inApp() or RequestPurchaseProps.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 finish
  • isConsumable - 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 (if isConsumable: true) or acknowledgePurchase (if isConsumable: false)

Purchase History

getAvailablePurchases()

Gets available purchases with optional filtering.

Future<List<Purchase>> getAvailablePurchases({
bool? onlyIncludeActiveItemsIOS,
bool? alsoPublishToEventListenerIOS,
}) async

Parameters:

  • onlyIncludeActiveItemsIOS - When true (default), excludes expired subscriptions (iOS only)
  • alsoPublishToEventListenerIOS - When true, replays purchases through purchaseUpdatedListener (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