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)
- Create a Google Play Developer Account if you don’t have one.
- Set up a merchant account in Google Play Console.
- Create an app in Google Play Console and upload a release version.
- Go to “Monetization Setup” → Click “In-app Products”.
- Add a new product:
- Product ID:
digital_product_1
- Price: $1.00
- Type: Consumable or Non-Consumable (for digital content)
- Save and activate it.
- Product ID:
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)
- Enroll in the Apple Developer Program.
- Create an app in App Store Connect.
- Go to “Features” → Select “In-App Purchases”.
- Create a new product:
- Product ID:
digital_product_1
- Type: Consumable or Non-Consumable.
- Price: $1.00.
- Save and submit for review.
- Product ID:
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
- Open
android/app/src/main/AndroidManifest.xml
and add:
<uses-permission android:name="com.android.vending.BILLING"/>
- Enable Google Play Billing in
android/app/build.gradle
:
dependencies {
implementation 'com.android.billingclient:billing:6.0.1'
}
For iOS
- Open
ios/Runner/Info.plist
and add:
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array>
- 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
- Create a test user in Google Play Console under “License Testing”.
- Upload an internal test track build.
- Sign in with the test user and make a purchase (Google won’t charge).
On iOS
- Create a Sandbox Tester in App Store Connect.
- Sign in to your iOS device with the Sandbox Apple ID.
- 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.
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
- Go to Google Play Console.
- Navigate to API Access → Link your project to Google Cloud Console.
- 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 |
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. 🚀