Featured Image
Software Development

Flutter in-app purchase tutorial: monetize your application

In-app purchases have become advantageous for new monetization strategies as they accelerate growth, prompting new revenue streams and opening doors to multiple opportunities. Whether you are getting down to creating a new game, an entertainment platform, or a productivity app, Flutter will make your task easy by supporting seamless implementation of in-app purchases. 

In this blog, we’ll detail the process of integration of in-app purchases into your Flutter app and help you with step-by-step guidance to get you started.

Prerequisites

Before beginning the implementation process, keep a checklist of below prerequisites set up:

Flutter SDK and IDE

  • Flutter should be installed on your system [download it from the official Flutter website
  • There should be a suitable IDE for Flutter development in place, like Visual Studio Code or Android Studio.

Accounts on Play Console & App Store Connect

  • You must have accounts on Google Play Developer Console & App Store Connect (Apple’s paid membership). These platforms will be used for managing your in-app products and purchases.
  • For more information, visit the official pages for Google Play’s Billing System & Apple In-app Purchase

Setup Play Console to test purchases in Android

  • It’s mandatory to finish the entire setup of Google Play Console to test in-app purchases in Android.
  • Understand and go through the steps and guidelines for Google Play’s Billing
  • Make sure that only the account owner of the Play Console can access the Payments Profile & API access under Setup in Play Console.
  • You need to complete the Payments Profile, and it will ask for details based on your country.
  • In the test environment, only license testers can be able to make a purchase or buy a subscription. To manage license testers, go to Play Console > Setup > License Testing.
  • In the test environment, in-app purchase subscriptions will renew every 5 minutes.

Setup App Store Connect to test purchases on iOS

  • Understand and go through the steps and guidelines for Apple In-app Purchase.
  • Make sure that the Paid Apps agreement is signed and active.
  • Also ensure that you provide all the metadata like Localization and Review information for each product and subscription listing and their status should read Ready to Submit.

Also read: Learn how to upgrade your Flutter version to 3.13

Adding dependencies

The initial step is to add all the mandatory dependencies to your Flutter project. Once that is done, open your project’s pubspec.yaml file. Here are the below-mentioned dependencies:

dependencies:
  flutter:
    sdk: flutter
  in_app_purchase: ^2.0.0

Save the file and run the flutter pub get. This step will fetch the new dependencies.

Setting up billing client

For enabling in-app purchases, the billing client setup needs to be completed first. A new class will be needed to manage the billing client initialization, hence create that subsequently. For instance, you can create a BillingService class.

Once you have the BillingService class, explore inside it and initialize the billing client using the below code:

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';

class BillingService {
  BillingService._();
  static BillingService get instance => _instance;
  static final BillingService _instance = BillingService._();

  final InAppPurchase _iap = InAppPurchase.instance;

  Future<void> initialize() async {
    if(!(await _iap.isAvailable())) return;
    if (Platform.isIOS) {
      final iosPlatformAddition = _iap
          .getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
      await iosPlatformAddition.setDelegate(PaymentQueueDelegate());
    }
  }

  Future<void> dispose() async {
    if (Platform.isIOS) {
      final iosPlatformAddition = _inAppPurchase
          .getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
      await iosPlatformAddition.setDelegate(null);
    }
  }
}

Fetching products

To enable fetching of the available in-app products from the store, you need to create a method in your BillingService class. An example is fetchProducts(). You can make use of the following code snippet:

Future<List<ProductDetails>> fetchProducts(List<String> productIds) async {
  Set<String> ids = Set.from(productIds);
  ProductDetailsResponse response =
      await _iap.queryProductDetails(ids);

  if (response.notFoundIDs.isNotEmpty) {
    // Handle not found product IDs
  }

  return response.productDetails;
}

Make sure to replace productIds with the actual IDs of your in-app products.

Making a purchase

Incorporation of purchase flow is necessary after setting the fetching of products. For this, you need to create a method in the BillingService class, such as makePurchase(). This will help you handle the purchase flow. Use the below code:

Future<PurchaseDetails> makePurchase(ProductDetails product) async {
  final purchaseParam = PurchaseParam(productDetails: product);
  final purchaseDetails =
      await _iap.buyNonConsumable(purchaseParam: purchaseParam);

  if (purchaseDetails.status == PurchaseStatus.error) {
    // Handle purchase error
  }

  return purchaseDetails;
}

Handling consumable purchases (optional)

If your app contains consumable in-app products, such as in-game currencies or subscription based currencies, then those consumptions need to be handled properly. As soon as a consumable product is purchased, and a user consumes it then the user’s inventory, account, or balance needs to be updated immediately. You need to modify the makePurchase() method as shown below:

Future<PurchaseDetails> makePurchase(ProductDetails product) async {
  final purchaseParam = PurchaseParam(productDetails: product);

  final purchaseDetails = _isConsumable(product.id) // checks if the product is consumable or not
      ? await _iap.buyConsumable(purchaseParam: purchaseParam);
      : await _iap.buyNonConsumable(purchaseParam: purchaseParam);

  if (purchaseDetails.status == PurchaseStatus.error) {
    // Handle purchase error
  }

  return purchaseDetails;
}

Also read: Enabling Authentication and Navigation in Flutter Web with go_router

Verifying the purchase

When a purchase is made, it is critical that the purchase details are updated with the store to enable the purchase order before delivering the products. For this, send the verification data to your backend so that the user’s purchase can be properly verified. Enable a function here by creating a method in your BillingService class, such as verifyPurchase(). You can use the following code: 

Future<bool> verifyPurchase(PurchaseDetails purchaseDetails) {
  // Provide the [purchaseDetails.verificationData] to your backend to verify.
  final response = await _httpClient.post(
    Uri.https(
      host, // your base url host
      path, // your verify purchase endpoint
    ),
    body: purchaseDetails.verificationData,
  );

  if (response[isSuccess] as bool? ?? false) {
    return result[isVerified] as bool;
  } else {
    // handle error
    return false;
  }
}

Listening to purchase updates

You can listen to the purchase updates via purchaseStream. Add a handler method in your BillingService class and set up the listener in your widget. Use the following code:

// inside BillingService class
Future<void> handlePurchaseUpdates(
  List<PurchaseDetails> purchaseDetailsList,
) async {
  for (final purchaseDetails in purchaseDetailsList) {
    purchaseStatus = purchaseDetails.status;
    switch (purchaseDetails.status) {
      case PurchaseStatus.pending:
        // handle pending case and update the UI
        continue;
      case PurchaseStatus.error:
        // handle error case and update the UI
        break;
      case PurchaseStatus.canceled:
        // handle canceled case and update the UI
        break;
      case PurchaseStatus.purchased:
      case PurchaseStatus.restored:
        if (await verifyPurchase(purchaseDetails)) {
          // you can deliver the product if the purchase has been verified
          deliverProduct(purchaseDetails);
        } else {
          // handle the invalid purchases
          handleInvalidPurchase(purchaseDetails);
        }
        break;
    }
    if (purchaseDetails.pendingCompletePurchase) {
      await _iap.completePurchase(purchaseDetails);
    }
  }
}

// inside your widget
late final StreamSubscription<List<PurchaseDetails>> _purchasesSubscription;

@override
void initState() {
  super.initState();
  _purchasesSubscription = InAppPurchase.instance.purchaseStream.listen(
    BillingService.instance.handlePurchaseUpdates,
    onError: (error) {
      // handle error
    },
  );
}

Implementing the UI

By following the above-mentioned steps, you will have your core logic ready. Now you can initiate the integration of the in-app purchase flow into your app’s UI. For this function, create a purchase button.  You can use makePurchase() command method. Whenever this button is pressed, the response will be handled accordingly.

Also read: Simplifying Paginated List fetching in Flutter with the Generic BLoC Pattern

Testing in-app purchases

For a smooth and engaging user experience, testing in-app purchases is crucial. In the current section, we’ll deep dive into the necessary tips and strategies that are helpful for seamless navigation of the testing phase.

Setting up your testing environment

For a coherent testing of in-app purchases, you will first have to create a controlled testing environment that has the ability to reflect the replication of real user experience without any actual payments being made. Here’s how you can achieve this:

Use test accounts: You can use either Google Play Console or App Store Connect to create test accounts. These portals will enable test purchases without any actual payment processing. Ensure proper utilization to avoid any unexpected payments.

Sandbox mode: This mode offers a simulation of purchases without impacting actual transactions. For iOS, it is essential that your app’s operation is being conducted in the App Store Connect Sandbox environment

Testing on real devices: The final step is to always test your product on real devices. Since you can simulate real-world conditions with precision on these devices, you will be able to gather insights that can help you make data-driven decisions. Emulators are not always able to mimic the behavior of actual devices.

Simulating purchases

One of the most fundamental steps for effective functionality of in-app purchase flow is simulating purchases. Below mentioned guidelines will help in ensuring efficacy:

  1. Non-consumable products: You will have to simulate a purchase by selecting the test account for non-consumable products like any premium features of the app or especially for ad removal. Initiate the purchase process after that and conduct a verification process to ensure that the user has gained access to the purchased feature, content, etc. or not.
  1. Consumable products: In comparison to non-consumable products, simulation of consumable purchases requires careful and precise management.  This is most relevant for features like in-game currency. You have to develop the app’s functionality to ensure prompt and correct updates in the user’s balance or inventory after a purchase is made and any subsequent purchases after that. 
  1. Subscription Testing: If your app also caters to subscription models you should test various scenarios, including initial subscription purchases, renewals, cancellations, and changing subscription levels or phases. You must conduct thorough testing to verify that the subscription lifecycle is functioning seamlessly.

Also read: Complete Guide to Integrating Augmented Reality in Flutter

Conclusion

Implementation of in-app purchases in Flutter apps is an empowering method to monetize your applications. The detailed steps explained in this blog will simplify the integration of in-app purchases into your Flutter project. You can also start generating revenue once the implementation process is complete. It is important that you also need to resolve error cases aptly and properly test the purchase flow to enable a seamless user experience.
If you need any support or assistance with the implementation of in-app purchases in Flutter apps, you can get in touch with our expert Flutter app development team at Aubergine Solutions. Connect with us today to make way for new revenue opportunities.

author
Nayan Babariya
A Senior Flutter Engineer with specialization in the Flutter framework for building cross-platform mobile applications. I have a strong understanding of the Flutter ecosystem and am able to use its various features and tools to build high-quality, scalable, and maintainable apps.