Skip to content

Software Development at Program Tom LTD

Place for coding, programming, development and software in general.

Menu
  • Blog
  • PDF Booklets
  • Dev Utils & Content
  • Java Spring Boot Or Web Apps
  • English
    • български
    • English
    • Español
    • Português
    • हिन्दी
    • Русский
    • Deutsch
    • Français
    • Italiano
    • العربية
  • About Us
Menu
Flutter Pay

Sell Digital Products with Flutter

Posted on March 25, 2025 by Toma Velev

At one moment in time you need to make money, so why not Sell Digital Products in Flutter apps published to the stores?

This is just a variation of a previous article: https://programtom.com/dev/2024/02/20/payment-logic-the-last-software-you-need-to-code/ More on making money from Software here: https://www.linkedin.com/pulse/ways-make-money-from-software-development-toma-velev/

Flutter Pay

First I’ve tried the pay package. There are a lot of links, but not so many step by step guides how to integrate it. There are set of ideas what is needed on iOS, on Android, but not how to approach it from Flutter. Here are some links that you may find useful:

  • https://pub.dev/packages/pay
  • https://developers.google.com/pay/api/android/overview
  • https://developer.apple.com/documentation/passkit/setting-up-apple-pay
  • https://pay.google.com/business/console/

The breaking point for me to try another package and options was the hardcoded in String – JSON, that should represent some configuration. https://github.com/google-pay/flutter-plugin/blob/main/pay/example/lib/payment_configurations.dart#L27. You may find a way to get it, but, I’ve already moved on and made it work with another

Flutter in-app purchases

What I manage to succeed with is in-app purchases https://pub.dev/packages/in_app_purchase/install

Here’s a step-by-step guide to integrating payments into your Flutter app using the official in_app_purchase package. This guide will help you set up in-app purchases for a small digital product priced at $1.


Step 1: Setup Your Store (Google Play & App Store)

Before coding, you need to configure your product in Google Play Console or App Store Connect.

For Google Play Store (Android)

  1. Create a Google Play Developer Account if you don’t have one.
  2. Set up a merchant account in Google Play Console.
  3. Create an app in Google Play Console and upload a release version.
  4. Go to “Monetization Setup” → Click “In-app Products”.
  5. Add a new product:
    • Product ID: digital_product_1
    • Price: $1.00
    • Type: Consumable or Non-Consumable (for digital content)
    • Save and activate it.

A little note for Android (tested so far) – you may need to upload a build that has activated the configuration for in-app purchases.

For Apple App Store (iOS)

  1. Enroll in the Apple Developer Program.
  2. Create an app in App Store Connect.
  3. Go to “Features” → Select “In-App Purchases”.
  4. Create a new product:
    • Product ID: digital_product_1
    • Type: Consumable or Non-Consumable.
    • Price: $1.00.
    • Save and submit for review.

Step 2: Add Dependencies

Add in_app_purchase to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  in_app_purchase: ^3.1.10  # Check for latest version

Run:

flutter pub get

Step 3: Configure Android and iOS

For Android

  1. Open android/app/src/main/AndroidManifest.xml and add:
<uses-permission android:name="com.android.vending.BILLING"/>
  1. Enable Google Play Billing in android/app/build.gradle:
dependencies {
    implementation 'com.android.billingclient:billing:6.0.1'
}

For iOS

  1. Open ios/Runner/Info.plist and add:
<key>SKAdNetworkItems</key>
<array>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>cstr6suwn9.skadnetwork</string>
    </dict>
</array>
  1. Enable in-app purchases in Xcode:
    • Open Xcode.
    • Go to Signing & Capabilities.
    • Add In-App Purchases.

Step 4: Implement Payment Logic in Flutter

Create a new Dart file: payment_service.dart and implement the logic:

import 'package:in_app_purchase/in_app_purchase.dart';

class PaymentService {
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;
  List<ProductDetails> _products = [];
  final String _productId = 'digital_product_1';

  // Load available products
  Future<void> loadProducts() async {
    final bool available = await _inAppPurchase.isAvailable();
    if (!available) return;

    final ProductDetailsResponse response =
        await _inAppPurchase.queryProductDetails({_productId});

    if (response.notFoundIDs.isNotEmpty) {
      print("Product not found: $_productId");
      return;
    }

    _products = response.productDetails;
  }

  // Purchase the product
  Future<void> buyProduct() async {
    if (_products.isEmpty) return;

    final PurchaseParam purchaseParam =
        PurchaseParam(productDetails: _products.first);
    
    await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
  }

  // Handle purchase updates
  void listenToPurchases() {
    _inAppPurchase.purchaseStream.listen((purchases) {
      for (final purchase in purchases) {
        if (purchase.status == PurchaseStatus.purchased) {
          print("Purchase successful!");
          // Unlock digital content here
        } else if (purchase.status == PurchaseStatus.error) {
          print("Purchase error: ${purchase.error}");
        }
      }
    });
  }
}

Step 5: Use PaymentService in Your UI

Modify your main.dart or a screen file to use PaymentService:

import 'package:flutter/material.dart';
import 'payment_service.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final PaymentService _paymentService = PaymentService();

  @override
  void initState() {
    super.initState();
    _paymentService.loadProducts();
    _paymentService.listenToPurchases();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Buy Digital Product")),
        body: Center(
          child: ElevatedButton(
            onPressed: () => _paymentService.buyProduct(),
            child: Text("Buy for \$1"),
          ),
        ),
      ),
    );
  }
}

Step 6: Test Your Payment

On Android

  1. Create a test user in Google Play Console under “License Testing”.
  2. Upload an internal test track build.
  3. Sign in with the test user and make a purchase (Google won’t charge).

On iOS

  1. Create a Sandbox Tester in App Store Connect.
  2. Sign in to your iOS device with the Sandbox Apple ID.
  3. Run the app and test the purchase.

Step 7: Publish Your App

  • Ensure your app meets store guidelines.
  • Submit your app for review.

That’s it! 🚀 Now your Flutter app supports in-app purchases for a $1 digital product.

How to differentiate between Test and Production

To differentiate between the test environment (sandbox) and production in Flutter’s in_app_purchase, you need to check the PurchaseDetails object’s verificationData.serverVerificationData or use debugging conditions.


1. Check serverVerificationData for Sandbox vs. Production

Each purchase has a verificationData property that contains information about the environment.

Example Code:

void listenToPurchases() {
  _inAppPurchase.purchaseStream.listen((purchases) {
    for (final purchase in purchases) {
      if (purchase.status == PurchaseStatus.purchased) {
        if (isTestEnvironment(purchase)) {
          print("Purchase is in TEST environment (Sandbox).");
        } else {
          print("Purchase is in PRODUCTION.");
        }
      }
    }
  });
}

bool isTestEnvironment(PurchaseDetails purchase) {
  final verificationData = purchase.verificationData.serverVerificationData;
  
  // Google Play sandbox check
  if (verificationData.contains("sandbox")) {
    return true;
  }

  // Apple sandbox check (sandbox receipts have a specific format)
  if (purchase.verificationData.source == "ios" &&
      verificationData.contains("Sandbox")) {
    return true;
  }

  return false;
}

2. Use Debug Mode for Development Testing

During development, you can use kDebugMode from flutter/foundation.dart to differentiate:

import 'package:flutter/foundation.dart';

bool isTestMode() {
  return kDebugMode;  // True in debug mode, false in production
}

void printEnvironment() {
  if (isTestMode()) {
    print("Running in TEST mode (Development).");
  } else {
    print("Running in PRODUCTION mode.");
  }
}

3. Using Platform-Specific Environment Checks

For Android, Google Play’s test purchases don’t charge the user, but the response will indicate “test” purchases.

For iOS, when running on a real device with a sandbox tester account, the purchase environment is always sandbox.


4. Manual Configuration with Build Variants (Optional)

You can also define an environment variable in your app’s config and check it:

  • Android: Use build flavors in build.gradle
  • iOS: Use Scheme configurations in Xcode
  • Flutter: Use Dart define:
flutter run --dart-define=ENV=TEST

Then, check in your code:

const bool isProduction = String.fromEnvironment("ENV") == "PRODUCTION";

Conclusion

  • ✅ Use purchase.verificationData.serverVerificationData to check for sandbox vs. production.
  • ✅ Use kDebugMode for differentiating test and production environments.
  • ✅ Use flutter run --dart-define=ENV=TEST for manual control.

This ensures your app behaves correctly based on the environment! 🚀

  How the backend to check the purchase

Any meaningful app will have the advanced feature – more functionality – as part of the backend. Your backend is isolated module – from the mobile app and google play where the payment happends.

You can verify purchases on your backend using Google Play and Apple App Store APIs. This ensures the purchase is legitimate and prevents fraud. Here’s how:


1. Why Verify Purchases on Backend?

✅ Prevents fake purchases (hackers modifying app responses).
Ensures the purchase is valid before granting digital goods.✅
✅ Allows tracking purchases and handling refunds.


2. Google Play (Android) – Verify Purchase on Backend

Google provides the Google Play Developer API for verifying purchases.

🔹 Step 1: Enable Google Play API

  1. Go to Google Play Console.
  2. Navigate to API Access → Link your project to Google Cloud Console.
  3. Create OAuth credentials and get a service account JSON key.

🔹 Step 2: Use Google Play API to Verify Purchase

  • API Endpoint:
    GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{purchaseToken}
    
  • Example Request (using Python or Node.js):
import requests
import json

ACCESS_TOKEN = "YOUR_OAUTH_ACCESS_TOKEN"
PACKAGE_NAME = "com.example.app"
PRODUCT_ID = "digital_product_1"
PURCHASE_TOKEN = "USER_PURCHASE_TOKEN"

url = f"https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{PACKAGE_NAME}/purchases/products/{PRODUCT_ID}/tokens/{PURCHASE_TOKEN}"

headers = {
    "Authorization": f"Bearer {ACCESS_TOKEN}",
    "Accept": "application/json"
}

response = requests.get(url, headers=headers)
data = response.json()

if data.get("purchaseState") == 0:  # 0 = Purchased, 1 = Canceled, 2 = Pending
    print("✅ Purchase is valid!")
else:
    print("❌ Purchase is invalid or refunded!")

👉 purchaseState Values:

  • 0 → Purchased (valid)
  • 1 → Canceled (invalid)
  • 2 → Pending (awaiting user action)

3. Apple App Store (iOS) – Verify Purchase on Backend

Apple provides the App Store Server API for verifying receipts.

🔹 Step 1: Get Receipt Data

In your Flutter app, get the receipt:

import 'package:in_app_purchase/in_app_purchase.dart';

void getReceipt(PurchaseDetails purchase) {
  print("Receipt Data: ${purchase.verificationData.serverVerificationData}");
}

🔹 Step 2: Send Receipt to Your Backend

Your backend needs to verify this receipt with Apple.

  • API Endpoint (Production)
    POST https://buy.itunes.apple.com/verifyReceipt
    
  • API Endpoint (Sandbox)
    POST https://sandbox.itunes.apple.com/verifyReceipt
    
  • Example Request (Node.js):
const axios = require('axios');

const receiptData = "USER_RECEIPT_FROM_APP";
const requestBody = {
  "receipt-data": receiptData,
  "password": "YOUR_SHARED_SECRET"
};

axios.post("https://buy.itunes.apple.com/verifyReceipt", requestBody)
  .then(response => {
    if (response.data.status === 0) {
      console.log("✅ Purchase is valid!");
    } else {
      console.log("❌ Invalid purchase!");
    }
  })
  .catch(error => console.error(error));

👉 status Values:

  • 0 → Valid purchase
  • Any other value → Invalid or refunded purchase

4. Flutter: Send Purchase Data to Your Backend

Modify your Flutter app to send purchase data to your backend.

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> verifyPurchaseOnBackend(PurchaseDetails purchase) async {
  final String backendUrl = "https://yourserver.com/verify_purchase";
  
  final response = await http.post(
    Uri.parse(backendUrl),
    headers: {"Content-Type": "application/json"},
    body: jsonEncode({
      "platform": purchase.verificationData.source, // "android" or "ios"
      "purchaseToken": purchase.verificationData.serverVerificationData,
    }),
  );

  if (response.statusCode == 200) {
    print("✅ Purchase is verified by backend!");
  } else {
    print("❌ Purchase verification failed.");
  }
}

5. When to Call Backend for Verification?

✅ After purchase success (PurchaseStatus.purchased).
Before unlocking digital content.✅
✅ Periodically (for subscriptions) to check validity.


6. Summary

Platform API Endpoint What to Send
Google GET /purchases/products/{productId}/tokens/{purchaseToken} Purchase Token
Apple POST /verifyReceipt Receipt Data & Shared Secret

✅ Always verify purchases on your backend to prevent fraud and ensure real transactions. 🚀

  • Join iOS Beta Testing Explained
  • Firebase App Distribution Setup
  • iOS App Lifetime Unverified
  • Flutter Bottom Border
  • Get Flutter current time zone

Categories

  • Apps (25)
  • ChatGPT (24)
  • Choosing a Framework (38)
  • Flutter (279)
  • Graphical User Interface (14)
  • Marketing (118)
  • Software Development (288)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (4)
  • Uncategorized (14)
  • Vaadin (15)

Tags

Algorithms (9) crypto (29) flutterdev (39) General (86) Java (7) QR & Bar Codes (3) Software Dev Choices (33) Spring Boot (1) standards (1) Theme (3) User Authentication & Authorization (9) User Experience (10) Utilities (19) WordPress (11)

Product categories

  • All Technologies (86)
    • Flutter Apps (26)
    • GPT (4)
    • Java (38)
    • Native Android (3)
    • PHP (9)
    • Spring (Boot) / Quarkus (35)
    • Utils (15)
    • Vaadin 24+ (27)
    • Vaadin 8 (1)
  • Apps (18)
    • Employees DB (1)
    • Notes (6)
    • Personal Budget (1)
    • Recipes Book (1)
    • Stuff Organizer (1)
    • To-Do (2)
  • PDF Books (3)
  • Source Code Generators (8)

Recent Posts

  • Join iOS Beta Testing Explained
  • Firebase App Distribution Setup
  • iOS App Lifetime Unverified
  • Flutter Bottom Border
  • Get Flutter current time zone

Post Categories

  • Apps (25)
  • ChatGPT (24)
  • Choosing a Framework (38)
  • Flutter (279)
  • Graphical User Interface (14)
  • Marketing (118)
  • Software Development (288)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (4)
  • Uncategorized (14)
  • Vaadin (15)