flutter_in_app_purchase_helper 0.1.0
flutter_in_app_purchase_helper: ^0.1.0 copied to clipboard
A Flutter in-app purchase helper that handles product lookup, purchase flows, restores and transaction completion.
Flutter In-App Purchase Helper #
A thin, tested wrapper around in_app_purchase that handles the bookkeeping most integrations get wrong: completing transactions, telling pending and canceled purchases apart from real failures, and surfacing store misconfiguration instead of silently returning an empty product list.
Install #
dependencies:
flutter_in_app_purchase_helper: ^0.1.0
You still need to configure your products in Play Console and App Store Connect, and follow the platform setup in the in_app_purchase README.
Usage #
Initialize #
Create the helper, then initialize it with your product IDs and the callbacks you care about. Only the first four are required.
import 'package:flutter_in_app_purchase_helper/flutter_in_app_purchase_helper.dart';
final FlutterInAppPurchaseHelper _helper = FlutterInAppPurchaseHelper();
@override
void initState() {
super.initState();
_helper.initialize(
productIds: {'monthly_plan', 'yearly_plan'},
onProductsFetched: (products) {
setState(() => _products = products);
},
onPurchaseSuccess: (purchase) {
// Fires for both new purchases and restores.
_unlockContent(purchase);
},
onPurchaseError: (error) {
_showSnackBar(error);
},
// Optional:
onPurchasePending: (purchase) => _showSpinner(),
onPurchaseCanceled: (purchase) => _hideSpinner(),
);
}
Calling initialize more than once is a no-op until you dispose, so a rebuild cannot register duplicate listeners.
Show the products #
onProductsFetched gives you ProductDetails straight from the store, so prices and currencies are already localized. Use product.price rather than formatting rawPrice yourself.
ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return ListTile(
title: Text(product.title),
subtitle: Text(product.description),
trailing: Text(product.price),
onTap: () => setState(() => _selected = product),
);
},
)
Buy #
final sent = await _helper.buyProduct(product, (error) => _showSnackBar(error));
if (!sent) {
// The request never reached the store; stop any spinner you started.
}
buyProduct returns whether the request was sent. The outcome arrives on the callbacks you gave to initialize.
For consumables, pass consumable: true:
await _helper.buyProduct(coinPack, _showSnackBar, consumable: true);
Restore #
The App Store requires a restore entry point for any app selling non-consumables or subscriptions. Restored purchases arrive on onPurchaseSuccess.
TextButton(
onPressed: () => _helper.restorePurchases(onError: _showSnackBar),
child: const Text('Restore purchases'),
)
Dispose #
@override
void dispose() {
_helper.dispose();
super.dispose();
}
Verifying purchases #
onPurchaseSuccess fires on the client's word alone. For anything revenue-bearing, send purchase.verificationData.serverVerificationData to your backend and validate it with Apple or Google before unlocking content.
What the helper handles for you #
| Concern | Behaviour |
|---|---|
| Transaction completion | completePurchase is called on every finished transaction. Skipping this makes iOS re-deliver the transaction on every launch, and makes Google Play auto-refund the purchase after 3 days. |
| Pending purchases | Routed to onPurchasePending, never to onPurchaseError. Never completed, which would throw. |
| Cancellations | Routed to onPurchaseCanceled, never to onPurchaseError. |
| Restores | Routed to onPurchaseSuccess. |
| Unknown product IDs | Reported through onPurchaseError with the offending IDs, instead of an empty list. |
| Slow stores | queryProductDetails is bounded by timeout (30s default). |
| Double initialization | Ignored, so you cannot leak a second stream listener. |
| Disposal | Safe even if initialize was never called or the store was unavailable. |
API #
| Member | Description |
|---|---|
initialize({productIds, onProductsFetched, onPurchaseSuccess, onPurchaseError, onPurchasePending, onPurchaseCanceled, timeout}) |
Checks availability, fetches products, starts listening. |
buyProduct(product, onError, {consumable, autoConsume}) |
Starts a purchase. Returns whether the request was sent. |
restorePurchases({applicationUserName, onError}) |
Restores non-consumables and subscriptions. |
dispose() |
Cancels the purchase stream subscription. |
isAvailable |
Whether the store is available on this device. |
isInitialized |
Whether the helper is currently initialized. |
products |
Products from the most recent fetch. |
purchases |
Most recent purchase per product ID. |
Example #
A complete plan-picker screen lives in example/lib/main.dart.
License #
MIT — see LICENSE.