In-App Purchase in Flutter

Ronak Jain
9 min readMar 5, 2024

--

Unlocking Revenue Streams: with multiple purchase plans for both the platforms Android and iOS.

Hey there! 👋 I’m Ronak, a seasoned Flutter developer with extensive experience across multiple platforms including Android, iOS, Windows, and Mac. Over the years, I’ve had the opportunity to dive deep into the world of mobile app development, exploring various technologies and strategies to create engaging user experiences.
Recently, I embarked on a journey to explore the realm of in-app purchases, and I’m excited to share my insights with you! In this blog post, I’ll walk you through the process of integrating in-app purchases into your Flutter app from scratch. Whether you’re a beginner looking to monetize your app or a seasoned developer seeking to enhance your revenue streams, I’ve got you covered.
So, let’s dive in and unlock the potential of in-app purchases together! 🚀

Step 1: Setting Up Developer Accounts

Before integrating in-app purchases into your Flutter app, you must have accounts on Google Play Developer Console & App Store Connect. These platforms will be used for managing your in-app products and purchases.
Google Play Store: Sign up for a Google Play Developer account. You’ll need to pay a one-time registration fee

Google Play documentation

Apple App Store: Enroll in the Apple Developer Program. This involves an annual fee.

App Store documentation

Once your developer accounts are set up and activated, you’ll gain access to the developer consoles where you can manage your apps and in-app purchases.

Step 2: Creating Products

After setting up your developer accounts, you’ll need to create products for your in-app purchases:

Google Play Console: Navigate to your app’s dashboard and select “Monetize” > “In-app products” > “Add new product”. Follow the prompts to create your in-app products, such as subscriptions, consumables, or one-time purchases.

App Store Connect: In your app’s dashboard, go to “App Store” > “App Store Connect” > “My Apps” > Your app > “Features” > “In-App Purchases”. Click the “+” button to create new in-app purchases, specifying the type and details of each product.

To facilitate in-app purchases within your Flutter app, you’ll need to define unique product IDs for each available product or subscription plan. These product IDs serve as identifiers to differentiate between different types of purchases and enable your app to retrieve the corresponding product details from the app store.

Here are examples of product IDs for various subscription plans:

Yearly Plan Product ID: com.example.yearly
Monthly Plan Product ID: com.example.monthly

You can create different product IDs for each subscription plan or product offered within your app, ensuring clarity and consistency in your app’s purchasing process. These product IDs should be defined in your app’s code and used accordingly when initiating purchases or querying product details.

Step 3: Integrating In-App Purchases into Flutter

Now, let’s dive into the code setup within your Flutter app:

Adding dependencies

The initial step is to add all the mandatory dependencies to your Flutter project.

dependencies:
flutter:
sdk: flutter
in_app_purchase: ^3.1.13
get: ^4.6.5

“Plugin Integration: Utilize a Flutter plugin for in-app purchases. A commonly used option is the in_app_purchase plugin. While I have personally employed GetX for state management, feel free to choose any state management solution that suits your needs.”

class InAppPurchaseUtils extends GetxController {
// Private constructor
InAppPurchaseUtils._();

// Singleton instance
static final InAppPurchaseUtils _instance = InAppPurchaseUtils._();

// Getter to access the instance
static InAppPurchaseUtils get inAppPurchaseUtilsInstance => _instance;

// Create a private variable
final InAppPurchase _iap = InAppPurchase.instance;

// Add your methods related to in-app purchases here
...
// Example method
void purchaseProduct(String productId) {
// Implementation for purchasing a product
}
}

By making InAppPurchaseUtils a singleton, you ensure that only one instance of the class exists throughout the application, preventing the creation of multiple instances.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
// Create an instance of InAppPurchaseUtils
final InAppPurchaseUtils inAppPurchaseUtils = InAppPurchaseUtils.instance;

@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Your App Name',
home: Scaffold(
appBar: AppBar(
title: Text('Your App'),
),
body: Center(
child: Text('In-app purchase related content here'),
),
),
initialBinding: BindingsBuilder(() {
Get.put<InAppPurchaseUtils>(inAppPurchaseUtils);
}),
);
}
}

Initialization: Initialize the plugin in your app’s main function or during app startup.

class InAppPurchaseUtils extends GetxController {

....

late StreamSubscription<List<PurchaseDetails>> _purchasesSubscription;

@override
void onInit() {
super.onInit();
initialize();
}

@override
void onClose() {
_purchasesSubscription.cancel();
super.onClose();
}

Future<void> initialize() async {
if(!(await _iap.isAvailable())) return;
///catch all purchase updates
_purchasesSubscription = InAppPurchase.instance.purchaseStream.listen((List<PurchaseDetails> purchaseDetailsList) {
handlePurchaseUpdates(purchaseDetailsList);
},
onDone: () {
_purchasesSubscription.cancel();
},
onError: (error) { },
);
}

void handlePurchaseUpdates(List<PurchaseDetails> purchaseDetailsList) {
// Implement your logic for handling purchase updates here
}
....

}

In the InAppPurchaseUtils class, the core functionality of handling in-app purchases relies on utilizing a stream provided by the in_app_purchase plugin.

StreamSubscription<List<PurchaseDetails>> _purchasesSubscription.

This is a purchase stream that allows the class to listen for updates whenever a purchase is made or updated.

_iap.isAvailable()

This method checks if in-app purchases are available on the current platform.

_purchasesSubscription = InAppPurchase.instance.purchaseStream.listen((List<PurchaseDetails> purchaseDetailsList) {
handlePurchaseUpdates(purchaseDetailsList);
}

Whenever an event occurs within the in-app purchase system, such as a successful purchase or an error during the transaction process, the stream notifies the _purchasesSubscription. Upon receiving these notifications, the class invokes the handlePurchaseUpdates method to appropriately manage and respond to the event.

In essence, the _purchasesSubscription allows the InAppPurchaseUtils class to stay constantly updated with the latest information regarding in-app purchases. This ensures that the application can react promptly to any changes or updates in the purchasing process, providing a seamless user experience.

handlePurchaseUpdates(purchaseDetailsList){
for (int index = 0 ; index < purchaseDetailsList.length; index++) {
var purchaseStatus = purchaseDetailsList[index].status;
switch (purchaseDetailsList[index].status) {
case PurchaseStatus.pending:
print(' purchase is in pending ');
continue;
case PurchaseStatus.error:
print(' purchase error ');
break;
case PurchaseStatus.canceled:
print(' purchase cancel ');
break;
case PurchaseStatus.purchased:
print(' purchased ');
break;
case PurchaseStatus.restored:
print(' purchase restore ');
break;
}

if (purchaseDetailsList[index].pendingCompletePurchase){
await _iap.completePurchase(purchaseDetailsList[index]).then((value){
if(purchaseStatus == PurchaseStatus.purchased){
//on purchase success you can call your logic and your API here.
}
});
}
}
}

This method processes each purchase detail within the provided list, allowing for dynamic handling of various purchase events based on their respective flags.

Additionally, if a purchase detail requires pending completion, the method invokes the

_iap.completePurchase();

method to finalize the purchase. This step is crucial for ensuring the completion of pending purchases and enabling further processing, such as invoking relevant logic or API calls upon successful purchases.

Making a purchase

There are two main types of In-App purchase

  1. Consumable
    Once bought, consumable purchases become depleted as the user uses them. After using all of a consumable purchase, the user must purchase another consumable IAP again to enjoy the same benefits.
    Example: Extra lives in a gaming app
  2. Non-consumable
    Once a user buys a non-consumable purchase, it’s permanently available to them. It does not expire with time or use. Non-consumable purchases are often premium features.
    Example: Additional filters in a photo app.
  Future<void> buyNonConsumableProduct(String productId) async {
try {
Set<String> productIds = {newProductId};

final ProductDetails productDetails =
await _iap.queryProductDetails(productId);
if (productDetails == null) {
// Product not found
return;
}

final PurchaseParam purchaseParam =
PurchaseParam(productDetails: productDetails.first);
await _iap.buyNonConsumable(purchaseParam: purchaseParam);
} catch (e) {
// Handle purchase error
print('Failed to buy plan: $e');
}
}
}

the method calls “_iap.buyNonConsumable” to initiate the purchase of the non-consumable product. This method triggers the purchase flow, allowing users to complete the transaction securely through the app store.

Future<void> buyConsumableProduct(String productId) async {
try {
Set<String> productIds = {newProductId};

final ProductDetails productDetails =
await _iap.queryProductDetails(productId);
if (productDetails == null) {
// Product not found
return;
}

final PurchaseParam purchaseParam =
PurchaseParam(productDetails: productDetails.first);
await _iap.buyConsumable(purchaseParam: purchaseParam,autoConsume: true);
} catch (e) {
// Handle purchase error
print('Failed to buy plan: $e');
}
}
}

the method calls “_iap.buyConsumable” to initiate the purchase of the consumable product. The autoConsume parameter is set to true to enable automatic consumption of the product after purchase, ensuring a seamless user experience.

“productId Parameter” : This parameter represents the unique identifier of the non-consumable product defined in both the Google Play Store and Apple App Store. It serves as a reference to the specific product users intend to purchase.

When creating purchase plans like monthly or weekly subscriptions in the Play Store and App Store, it’s crucial to ensure consistency in the plan names across both platforms. This ensures a seamless purchasing experience for users, regardless of the device they’re using.

Restore Purchases

Restoring purchases typically refers to the process of retrieving a user’s previous purchases, especially in scenarios where the app is reinstalled or the user switches to a new device. This ensures that users can regain access to any content or features they’ve previously purchased without having to make additional payments.

  restorePurchases() async {
try{
await _iap.restorePurchases();
}catch(error){
//you can handle error if restore purchase fails
}
}

Time to Purchase in Just One Click!

Hurray! Now that our in-app purchase setup is complete, it’s time to make purchases with just one click. Let’s see how easy it is :

import 'package:flutter/material.dart';
import 'package:your_app_name/in_app_purchase_utils.dart';

class PurchaseScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Purchase Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
InAppPurchaseUtils.inAppPurchaseUtilsInstance.buyNonConsumableProduct("pass your product id here");
},
child: Text('Purchase Plan'),
),
),
);
}
}

Simply navigate to the PurchaseScreen in your app and tap the “Purchase Plan” button. It’s that easy! With just one click, users can access premium content and features offered by your app.

Test In-App purchases on Android and iOS

Testing in-app purchases in Flutter involves setting up test accounts and adding them to the respective developer consoles (Google Play Console for Android and App Store Connect for iOS).

Here’s a step-by-step guide to test :

Setting up Test Accounts :

  1. Google Play Console (Android):
  • Go to the Google Play Console.
  • Select your app from the dashboard.
  • Navigate to “Monetize” > “Products”.
  • Under “License Testing”, add the Gmail addresses of your test accounts. These accounts will have access to test in-app purchases without being charged.

2. App Store Connect (iOS):

  • Go to App Store Connect.
  • Select “Users and Access”.
  • Under “Sandbox Testers”, add the email addresses of your test accounts. These accounts will be able to make test purchases without being charged.

Testing In-App Purchases in Flutter :

  1. Android:
  • Ensure that your device or emulator is logged in with one of the test accounts added to the Google Play Console.
  • Run your Flutter app on the device or emulator.
  • Perform in-app purchase actions as usual. Google Play will treat these purchases as test transactions and won’t charge the test accounts.

2. iOS:

  • Ensure that your device or simulator is logged in with one of the test accounts added to App Store Connect.
  • Run your Flutter app on the device or simulator.
  • Perform in-app purchase actions as usual. App Store will treat these purchases as test transactions and won’t charge the test accounts.

By following these steps and tips, you can effectively test in-app purchases in your Flutter app and ensure a smooth user experience during the purchasing process.

Step 4: Managing Purchases and Subscriptions

Implement a backend system to validate purchases and manage user entitlements if necessary.
Handle scenarios such as refunds, subscription renewals, and expiration gracefully within your app.
Consider using server-side validation for added security and flexibility

Step 5: Compliance and Review

Ensure your in-app purchase implementation complies with the respective platform’s guidelines and policies.
Test your app thoroughly to identify and fix any issues before submission.
Submit your app for review on the Google Play Store and Apple App Store, adhering to their submission guidelines.

Step 6: Iteration and Optimization

Monitor the performance of your in-app purchases using analytics tools.
Gather user feedback and iterate on your in-app purchase strategy to optimize revenue generation.
Stay informed about updates and changes to the platforms’ in-app purchase systems, adapting your implementation as needed.

By following these steps, you’ll be able to integrate in-app purchases into your Flutter app effectively, offering users a seamless purchasing experience while maximizing revenue potential.

Conclusion

Congratulations on completing the setup of in-app purchases in your Flutter app! With the steps outlined in this guide, you’ve gained the necessary knowledge to monetize your app and offer premium features or content to your users.

We’ve covered everything from creating developer accounts on the Google Play Store and App Store to defining product IDs and implementing in-app purchase code using a singleton class and GetX state management. Additionally, we’ve emphasized the importance of testing to ensure functionality and reliability across platforms and devices.

Now that you’re ready to launch your in-app purchases, we value your feedback. If you have any doubts or questions about the content of this guide or the implementation of in-app purchases, please feel free to ask. Your feedback is invaluable as we strive to provide the best resources and support for your Flutter development journey.

Thank you for choosing us as your guide in exploring the world of in-app purchases. We look forward to hearing from you and wish you continued success in your app development endeavors. Happy coding!

--

--