### Initialize and Manage In-App Purchases in Flutter Source: https://pub.dev/packages/in_app_purchase/example This example demonstrates setting up the InAppPurchase instance, listening to the purchase stream, and querying product details. It includes platform-specific configuration for iOS and handles product availability checks. ```dart // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'consumable_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(_MyApp()); } // Auto-consume must be true on iOS. // To try without auto-consume on another platform, change `true` to `false` here. // ignore: no_literal_bool_comparisons final bool _kAutoConsume = Platform.isIOS || true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver'; const String _kGoldSubscriptionId = 'subscription_gold'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchase _inAppPurchase = InAppPurchase.instance; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _inAppPurchase.purchaseStream; _subscription = purchaseUpdated.listen( (List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }, ); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _inAppPurchase.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (Platform.isIOS) { final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = _inAppPurchase .getPlatformAddition(); await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); } final ProductDetailsResponse productDetailResponse = await _inAppPurchase.queryProductDetails( _kProductIds.toSet(), ); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } ``` -------------------------------- ### Initialize and Configure StoreKit Plugin Source: https://pub.dev/packages/in_app_purchase_storekit/example Register the StoreKit platform and set up the purchase stream listener during app initialization. ```dart // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/material.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/store_kit_2_wrappers.dart'; import 'consumable_store.dart'; import 'example_payment_queue_delegate.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // When using the StoreKit plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseStoreKitPlatform.registerPlatform(); runApp(_MyApp()); } const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver'; const String _kGoldSubscriptionId = 'subscription_gold'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchaseStoreKitPlatform _iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; final InAppPurchaseStoreKitPlatformAddition _iapStoreKitPlatformAddition = InAppPurchasePlatformAddition.instance! as InAppPurchaseStoreKitPlatformAddition; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _iapStoreKitPlatform.purchaseStream; _subscription = purchaseUpdated.listen( (List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }, ); // Register the example payment queue delegate _iapStoreKitPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _iapStoreKitPlatform.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } final ProductDetailsResponse productDetailResponse = await _iapStoreKitPlatform .queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } @override void dispose() { _subscription.cancel(); super.dispose(); } ``` -------------------------------- ### Build Main UI Layout Source: https://pub.dev/packages/in_app_purchase_android/example Constructs the main application scaffold including the product list and loading indicators. ```dart @override Widget build(BuildContext context) { final stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), const _FeatureCard(), _buildFetchButtons(), _buildUserChoiceDetailsDisplay(), ], ), ); } else { stack.add(Center(child: Text(_queryProductError!))); } if (_purchasePending) { stack.add( const Stack( children: [ Opacity(opacity: 0.3, child: ModalBarrier(dismissible: false, color: Colors.grey)), Center(child: CircularProgressIndicator()), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('IAP Example')), body: Stack(children: stack), ), ); } ``` -------------------------------- ### Build Product List Source: https://pub.dev/packages/in_app_purchase_ios/example Renders a list of available products for sale and handles purchase initiation logic. Always verify purchase data on a server before trusting it. ```dart Card _buildProductList() { if (_loading) { return Card( child: (ListTile( leading: CircularProgressIndicator(), title: Text('Fetching products...')))); } if (!_isAvailable) { return Card(); } final ListTile productHeader = ListTile(title: Text('Products for Sale')); List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().errorColor)), subtitle: Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. Map purchases = Map.fromEntries(_purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _iapIosPlatform.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); })); productList.addAll(_products.map( (ProductDetails productDetails) { PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text( productDetails.title, ), subtitle: Text( productDetails.description, ), trailing: previousPurchase != null ? IconButton( onPressed: () { _iapIosPlatformAddition.showPriceConsentIfNeeded(); }, icon: Icon(Icons.upgrade)) : TextButton( child: Text(productDetails.price), style: TextButton.styleFrom( backgroundColor: Colors.green[800], primary: Colors.white, ), onPressed: () { PurchaseParam purchaseParam = PurchaseParam( productDetails: productDetails, applicationUserName: null, ); if (productDetails.id == _kConsumableId) { _iapIosPlatform.buyConsumable( purchaseParam: purchaseParam, autoConsume: _kAutoConsume || Platform.isIOS); } else { _iapIosPlatform.buyNonConsumable( purchaseParam: purchaseParam); } }, )); }, )); ``` -------------------------------- ### Initialize and Configure In-App Purchase for iOS Source: https://pub.dev/packages/in_app_purchase_ios/example Registers the iOS platform instance and sets up the payment queue delegate during application startup. ```dart // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:in_app_purchase_ios/in_app_purchase_ios.dart'; import 'package:in_app_purchase_ios_example/example_payment_queue_delegate.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'consumable_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // When using the Android plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseIosPlatform.registerPlatform(); runApp(_MyApp()); } const bool _kAutoConsume = true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver'; const String _kGoldSubscriptionId = 'subscription_gold'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchaseIosPlatform _iapIosPlatform = InAppPurchasePlatform.instance as InAppPurchaseIosPlatform; final InAppPurchaseIosPlatformAddition _iapIosPlatformAddition = InAppPurchasePlatformAddition.instance as InAppPurchaseIosPlatformAddition; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _iapIosPlatform.purchaseStream; _subscription = purchaseUpdated.listen((purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (error) { // handle error here. }); // Register the example payment queue delegate _iapIosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _iapIosPlatform.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } ProductDetailsResponse productDetailResponse = await _iapIosPlatform.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } @override void dispose() { _subscription.cancel(); super.dispose(); } ``` -------------------------------- ### Build Main UI Layout Source: https://pub.dev/packages/in_app_purchase_storekit/example Constructs the primary application view, including connection status, product lists, and a loading overlay. ```dart @override Widget build(BuildContext context) { final stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), _buildCodeRedemptionButton(), _buildRestoreButton(), ], ), ); } else { stack.add(Center(child: Text(_queryProductError!))); } if (_purchasePending) { stack.add( const Stack( children: [ Opacity(opacity: 0.3, child: ModalBarrier(dismissible: false, color: Colors.grey)), Center(child: CircularProgressIndicator()), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('IAP Example')), body: Stack(children: stack), ), ); } ``` -------------------------------- ### Build Product List UI Source: https://pub.dev/packages/in_app_purchase_android/example Renders a product list card, handling loading states, availability checks, and missing product IDs. ```dart Card _buildProductList() { if (_loading) { return const Card( child: ListTile(leading: CircularProgressIndicator(), title: Text('Fetching products...')), ); } if (!_isAvailable) { return const Card(); } const productHeader = ListTile(title: Text('Products for Sale')); final productList = []; if (_notFoundIds.isNotEmpty) { productList.add( ListTile( title: Text( '[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().colorScheme.error), ), subtitle: const Text( 'This app needs special configuration to run. Please see example/README.md for instructions.', ), ), ); } ``` -------------------------------- ### Process Purchases and Build Product List Source: https://pub.dev/packages/in_app_purchase_android/example Maps purchase details to product IDs and constructs a list of UI widgets for product display. Always verify purchase data server-side before trusting it. ```dart final purchases = Map.fromEntries( _purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _inAppPurchasePlatform.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); }), ); productList.addAll( _products.map((ProductDetails productDetails) { final PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text(productDetails.title), subtitle: Text(productDetails.description), trailing: previousPurchase != null ? const SizedBox.shrink() : TextButton( style: TextButton.styleFrom( backgroundColor: Colors.green[800], foregroundColor: Colors.white, ), onPressed: () { // NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to // verify the latest status of you your subscription by using server side receipt validation // and update the UI accordingly. The subscription purchase status shown // inside the app may not be accurate. final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription( productDetails as GooglePlayProductDetails, purchases, ); final purchaseParam = GooglePlayPurchaseParam( productDetails: productDetails, changeSubscriptionParam: oldSubscription != null ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, replacementMode: ReplacementMode.withTimeProration, ) : null, ); if (productDetails.id == _kConsumableId) { _inAppPurchasePlatform.buyConsumable( purchaseParam: purchaseParam, // ignore: avoid_redundant_argument_values autoConsume: _kAutoConsume, ); } else { _inAppPurchasePlatform.buyNonConsumable(purchaseParam: purchaseParam); } }, child: Text(productDetails.price), ), ); }), ); return Card(child: Column(children: [productHeader, const Divider()] + productList)); } ``` -------------------------------- ### Initialize and Query Products in Android In-App Purchase Source: https://pub.dev/packages/in_app_purchase_android/example Registers the Android platform implementation and sets up listeners for purchase and user choice streams. Requires calling InAppPurchaseAndroidPlatform.registerPlatform() before app execution. ```dart // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/material.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'consumable_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // When using the Android plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseAndroidPlatform.registerPlatform(); runApp(_MyApp()); } // To try without auto-consume, change `true` to `false` here. const bool _kAutoConsume = true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver1'; const String _kGoldSubscriptionId = 'subscription_gold1'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchasePlatform _inAppPurchasePlatform = InAppPurchasePlatform.instance; late StreamSubscription> _subscription; late StreamSubscription _userChoiceDetailsStream; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; String _countryCode = ''; String _isAlternativeBillingOnlyAvailableResponseCode = ''; String _showAlternativeBillingOnlyDialogResponseCode = ''; String _alternativeBillingOnlyReportingDetailsToken = ''; String _showInAppMessagesResponseCode = ''; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; final List _userChoiceDetailsList = []; @override void initState() { final Stream> purchaseUpdated = _inAppPurchasePlatform.purchaseStream; _subscription = purchaseUpdated.listen( (List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }, ); initStoreInfo(); final addition = InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition; final Stream userChoiceDetailsUpdated = addition.userChoiceDetailsStream; _userChoiceDetailsStream = userChoiceDetailsUpdated.listen( (GooglePlayUserChoiceDetails details) { deliverUserChoiceDetails(details); }, onDone: () { _userChoiceDetailsStream.cancel(); }, onError: (Object error) { // handle error here. }, ); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _inAppPurchasePlatform.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } final ProductDetailsResponse productDetailResponse = await _inAppPurchasePlatform .queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } ``` -------------------------------- ### Import the package Source: https://pub.dev/packages/in_app_purchase_platform_interface/install Include the library in your Dart files to access the platform interface functionality. ```dart import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; ``` -------------------------------- ### Import the package Source: https://pub.dev/packages/in_app_purchase_storekit/install Include the package in your Dart files to access StoreKit functionality. ```dart import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; ``` -------------------------------- ### Configure Purchase Parameters and Initiate Purchase Source: https://pub.dev/packages/in_app_purchase/example Handles platform-specific purchase parameters for Android and initiates either consumable or non-consumable purchase flows. ```dart if (Platform.isAndroid) { // NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to // verify the latest status of you your subscription by using server side receipt validation // and update the UI accordingly. The subscription purchase status shown // inside the app may not be accurate. final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription( productDetails, purchases, ); purchaseParam = GooglePlayPurchaseParam( productDetails: productDetails, changeSubscriptionParam: (oldSubscription != null) ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, replacementMode: ReplacementMode.withTimeProration, ) : null, ); } else { purchaseParam = PurchaseParam(productDetails: productDetails); } if (productDetails.id == _kConsumableId) { _inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: _kAutoConsume, ); } else { _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam); } ``` -------------------------------- ### Build Alternative Billing Info Buttons Source: https://pub.dev/packages/in_app_purchase_android/example Displays various alternative billing response codes and details in a list format. ```dart Card _buildFetchButtons() { const header = ListTile(title: Text('AlternativeBilling Info')); final List entries = []; entries.add( ListTile( title: Text( 'User Country Code', style: TextStyle(color: ThemeData.light().colorScheme.primary), ), subtitle: Text(_countryCode), ), ); entries.add( ListTile( title: Text( 'isAlternativeBillingOnlyAvailable response code', style: TextStyle(color: ThemeData.light().colorScheme.primary), ), subtitle: Text(_isAlternativeBillingOnlyAvailableResponseCode), ), ); entries.add( ListTile( title: Text( 'showAlternativeBillingOnlyDialog response code', style: TextStyle(color: ThemeData.light().colorScheme.primary), ), subtitle: Text(_showAlternativeBillingOnlyDialogResponseCode), ), ); entries.add( ListTile( title: Text( 'createAlternativeBillingOnlyReportingDetails contents', style: TextStyle(color: ThemeData.light().colorScheme.primary), ), subtitle: Text(_alternativeBillingOnlyReportingDetailsToken), ), ); entries.add( ListTile( title: Text( 'showInAppMessages response code', style: TextStyle(color: ThemeData.light().colorScheme.primary), ), subtitle: Text(_showInAppMessagesResponseCode), ), ); ``` -------------------------------- ### Connect to the store Source: https://pub.dev/packages/in_app_purchase Check if the underlying store is available before attempting transactions. ```dart final bool available = await InAppPurchase.instance.isAvailable(); if (!available) { // The store cannot be reached or accessed. Update the UI accordingly. } ``` -------------------------------- ### Query products for sale Source: https://pub.dev/packages/in_app_purchase Retrieve product details from the store using a set of product IDs. ```dart // Set literals require Dart 2.2. Alternatively, use // `Set _kIds = ['product1', 'product2'].toSet()`. const Set _kIds = {'product1', 'product2'}; final ProductDetailsResponse response = await InAppPurchase.instance.queryProductDetails(_kIds); if (response.notFoundIDs.isNotEmpty) { // Handle the error. } List products = response.productDetails; ``` -------------------------------- ### Build Connection Status Tile Source: https://pub.dev/packages/in_app_purchase_ios/example Displays the current store availability status and provides troubleshooting information if the connection fails. ```dart Card _buildConnectionCheckTile() { if (_loading) { return Card(child: ListTile(title: const Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().errorColor), title: Text( 'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'), ); final List children = [storeHeader]; if (!_isAvailable) { children.addAll([ Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().errorColor)), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), ), ]); } return Card(child: Column(children: children)); } ``` -------------------------------- ### Display Promotional Offers Source: https://pub.dev/packages/in_app_purchase_storekit/example Renders a list of promotional offers for App Store products, checking eligibility for win-back offers. ```dart return Column( children: [ Card(child: Column(children: [productHeader, const Divider(), ...productList])), Card( child: Column( children: [ promoHeader, const Divider(), FutureBuilder>( future: _buildPromoList(), builder: (BuildContext context, AsyncSnapshot> snapshot) { final List? data = snapshot.data; if (data != null) { return Column(children: data); } return const SizedBox.shrink(); }, ), ], ), ), ], ); } Future> _buildPromoList() async { final promoList = []; for (final ProductDetails detail in _products) { if (detail is AppStoreProduct2Details) { final SK2SubscriptionInfo? subscription = detail.sk2Product.subscription; final List offers = subscription?.promotionalOffers ?? []; for (final offer in offers) { if (offer.type == SK2SubscriptionOfferType.winBack) { final bool eligible = await _iapStoreKitPlatform.isWinBackOfferEligible( detail.id, offer.id ?? '', ); if (!eligible) { continue; } } promoList.add(_buildPromoTile(detail, offer)); } } } return promoList; } ListTile _buildPromoTile(ProductDetails productDetails, SK2SubscriptionOffer offer) { return ListTile( title: Text('${productDetails.title} [${offer.type.name}]'), subtitle: Text(productDetails.description), trailing: TextButton( style: TextButton.styleFrom( backgroundColor: Colors.green[800], foregroundColor: Colors.white, ), onPressed: () { final purchaseParam = Sk2PurchaseParam.fromOffer( productDetails: productDetails, offer: offer, signature: SK2SubscriptionOfferSignature( keyID: 'keyID', nonce: '1ac96421-947d-45e9-a0a0-5bb3a6937284', timestamp: DateTime.now().millisecondsSinceEpoch, signature: 'dmFsaWRzaWduYXR1cmU=', ), ); _iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); }, child: Text('${productDetails.currencySymbol} ${offer.price}'), ), ); } ``` -------------------------------- ### Build Connection Status Tile Source: https://pub.dev/packages/in_app_purchase_android/example Displays the current connection status to the payment processor. ```dart Card _buildConnectionCheckTile() { if (_loading) { return const Card(child: ListTile(title: Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon( _isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().colorScheme.error, ), title: Text('The store is ${_isAvailable ? 'available' : 'unavailable'}.'), ); final children = [storeHeader]; if (!_isAvailable) { children.addAll([ const Divider(), ListTile( title: Text( 'Not connected', style: TextStyle(color: ThemeData.light().colorScheme.error), ), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.', ), ), ]); } return Card(child: Column(children: children)); } ``` -------------------------------- ### Add dependency via command line Source: https://pub.dev/packages/in_app_purchase_ios/install Use this command in your terminal to automatically add the package to your project. ```bash $ flutter pub add in_app_purchase_ios ``` -------------------------------- ### Initiate a purchase Source: https://pub.dev/packages/in_app_purchase Distinguish between consumable and non-consumable products before calling the appropriate purchase method. ```dart final ProductDetails productDetails = ... // Saved earlier from queryProductDetails(). final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); if (_isConsumable(productDetails)) { InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam); } else { InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam); } // From here the purchase flow will be handled by the underlying store. // Updates will be delivered to the `InAppPurchase.instance.purchaseStream`. ``` -------------------------------- ### Add dependency via command line Source: https://pub.dev/packages/in_app_purchase/install Use this command to automatically add the package to your pubspec.yaml file. ```bash $ flutter pub add in_app_purchase ``` -------------------------------- ### Display User Choice Details Source: https://pub.dev/packages/in_app_purchase_android/example Builds a card component that iterates through user choice details and displays them in a list format. ```dart Card _buildUserChoiceDetailsDisplay() { const header = ListTile(title: Text('UserChoiceDetails')); final List entries = []; for (final String item in _userChoiceDetailsList) { entries.add( ListTile( title: Text(item, style: TextStyle(color: ThemeData.light().colorScheme.primary)), subtitle: Text(_countryCode), ), ); } return Card(child: Column(children: [header, const Divider(), ...entries])); } ``` -------------------------------- ### Handle Product Details and Restore Purchases Source: https://pub.dev/packages/in_app_purchase_android/example Updates the UI state based on product availability and triggers purchase restoration. ```dart if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } await _inAppPurchasePlatform.restorePurchases(); final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } ``` -------------------------------- ### Implement SKPaymentQueueDelegateWrapper Source: https://pub.dev/packages/in_app_purchase Create a custom delegate to control transaction continuation and suppress the default price consent popup. ```dart // import for SKPaymentQueueDelegateWrapper import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper { @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) { return true; } @override bool shouldShowPriceConsent() { return false; } } ``` -------------------------------- ### Implement Payment Queue Delegate Source: https://pub.dev/packages/in_app_purchase/example Provides an implementation of SKPaymentQueueDelegateWrapper to manage transaction continuation and price consent on iOS. ```dart class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper { @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront, ) { return true; } @override bool shouldShowPriceConsent() { return false; } } ``` -------------------------------- ### Enable StoreKit 1 on iOS and macOS Source: https://pub.dev/packages/in_app_purchase Call this method before registering the platform if your application requires StoreKit 1 instead of the default StoreKit 2. ```dart InAppPurchaseStoreKitPlatform.enableStoreKit1() ``` -------------------------------- ### Build IAP UI Components Source: https://pub.dev/packages/in_app_purchase_ios/example Constructs the main UI stack for the in-app purchase screen, handling loading states, error displays, and purchase progress overlays. ```dart @override Widget build(BuildContext context) { List stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), _buildRestoreButton(), ], ), ); } else { stack.add(Center( child: Text(_queryProductError!), )); } if (_purchasePending) { stack.add( Stack( children: [ Opacity( opacity: 0.3, child: const ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('IAP Example'), ), body: Stack( children: stack, ), ), ); } ``` -------------------------------- ### Import the package Source: https://pub.dev/packages/in_app_purchase_ios/install Include this import statement in your Dart files to access the plugin functionality. ```dart import 'package:in_app_purchase_ios/in_app_purchase_ios.dart'; ``` -------------------------------- ### Check and display BillingClient features Source: https://pub.dev/packages/in_app_purchase_android/example Uses InAppPurchaseAndroidPlatformAddition to verify feature support asynchronously and updates the UI based on the result. ```dart InAppPurchaseAndroidPlatformAddition get addition => InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition; @override Widget build(BuildContext context) { return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const ListTile(title: Text('Available features')), const Divider(), for (final BillingClientFeature feature in BillingClientFeature.values) _buildFeatureWidget(feature), ], ), ); } Widget _buildFeatureWidget(BillingClientFeature feature) { return FutureBuilder( future: addition.isFeatureSupported(feature), builder: (BuildContext context, AsyncSnapshot snapshot) { Color color = Colors.grey; final bool? data = snapshot.data; if (data != null) { color = data ? Colors.green : Colors.red; } return Padding( padding: const EdgeInsets.fromLTRB(16.0, 4.0, 16.0, 4.0), child: Text(_featureToString(feature), style: TextStyle(color: color)), ); }, ); } String _featureToString(BillingClientFeature feature) { return switch (feature) { BillingClientFeature.alternativeBillingOnly => 'alternativeBillingOnly', BillingClientFeature.priceChangeConfirmation => 'priceChangeConfirmation', BillingClientFeature.productDetails => 'productDetails', BillingClientFeature.subscriptions => 'subscriptions', BillingClientFeature.subscriptionsUpdate => 'subscriptionsUpdate', BillingClientFeature.billingConfig => 'billingConfig', BillingClientFeature.externalOffer => 'externalOffer', BillingClientFeature.inAppMessaging => 'inAppMessaging', }; } } ``` -------------------------------- ### Listen to purchase updates Source: https://pub.dev/packages/in_app_purchase Subscribe to the purchase stream in initState to handle incoming purchase events throughout the app lifecycle. ```dart class _MyAppState extends State { StreamSubscription> _subscription; @override void initState() { final Stream purchaseUpdated = InAppPurchase.instance.purchaseStream; _subscription = purchaseUpdated.listen((purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (error) { // handle error here. }); super.initState(); } @override void dispose() { _subscription.cancel(); super.dispose(); } ``` -------------------------------- ### Consume and Deliver Products Source: https://pub.dev/packages/in_app_purchase_storekit/example Methods to manage consumable product state and deliver purchased items after verification. ```dart Future consume(String id) async { await ConsumableStore.consume(id); final List consumables = await ConsumableStore.load(); setState(() { _consumables = consumables; }); } void showPendingUI() { setState(() { _purchasePending = true; }); } Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); final List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } ``` -------------------------------- ### Import the package Source: https://pub.dev/packages/in_app_purchase_android/install Include this import statement in your Dart files to access the package functionality. ```dart import 'package:in_app_purchase_android/in_app_purchase_android.dart'; ``` -------------------------------- ### Deliver Product Logic Source: https://pub.dev/packages/in_app_purchase_ios/example Verifies and delivers products, updating the state based on whether the item is a consumable or a standard purchase. ```dart void deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } ``` -------------------------------- ### Add dependency via terminal Source: https://pub.dev/packages/in_app_purchase_android/install Use this command in your terminal to automatically add the package to your project. ```bash $ flutter pub add in_app_purchase_android ``` -------------------------------- ### Add dependency via terminal Source: https://pub.dev/packages/in_app_purchase_platform_interface/install Use the Flutter CLI to add the package to your project dependencies. ```bash $ flutter pub add in_app_purchase_platform_interface ``` -------------------------------- ### Rebuild Serializers Source: https://pub.dev/packages/in_app_purchase_ios Run these commands after modifying serialized data structures to regenerate the necessary code. ```bash flutter packages pub run build_runner build --delete-conflicting-outputs ``` ```bash flutter packages pub run build_runner watch --delete-conflicting-outputs ``` -------------------------------- ### Build Consumable UI Component Source: https://pub.dev/packages/in_app_purchase_ios/example Renders a card displaying purchased consumables or a loading indicator if data is being fetched. ```dart Card _buildConsumableBox() { if (_loading) { return Card( child: (ListTile( leading: CircularProductIndicator(), title: Text('Fetching consumables...')))); } if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { return Card(); } final ListTile consumableHeader = ListTile(title: Text('Purchased consumables')); final List tokens = _consumables.map((String id) { return GridTile( child: IconButton( icon: Icon( Icons.stars, size: 42.0, color: Colors.orange, ), splashColor: Colors.yellowAccent, onPressed: () => consume(id), ), ); }).toList(); return Card( child: Column(children: [ consumableHeader, Divider(), GridView.count( crossAxisCount: 5, children: tokens, shrinkWrap: true, padding: EdgeInsets.all(16.0), ) ])); } ```