我正在尝试使用 in_app_purchase 包在我的 Flutter 应用程序中实现订阅系统。我在文档中发现的只是我需要旧的购买来处理订阅:
final PurchaseDetails oldPurchaseDetails = ...;
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
changeSubscriptionParam: ChangeSubscriptionParam(
oldPurchaseDetails: oldPurchaseDetails,
prorationMode: ProrationMode.immediateWithTimeProration));
InAppPurchaseConnection.instance
.buyNonConsumable(purchaseParam: purchaseParam);
我已经有了产品详细信息。 第一次如何订阅?
第一次订阅不需要旧的订阅,参数
changeSubscriptionParam
是可选的,可以传递null。当您要更改订阅时,您需要在 changeSubscriptionParam
的此参数中传递旧订阅
范围。这仅适用于 Android,获取旧订阅的简单方法是(在我的情况下只有两个可能的签名):
Future<GooglePlayPurchaseDetails> _getOld() async {
if (Platform.isAndroid) {
final InAppPurchaseAndroidPlatformAddition androidAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
QueryPurchaseDetailsResponse oldPurchaseDetailsQuery =
await androidAddition.queryPastPurchases();
oldPurchaseDetailsQuery.pastPurchases.forEach((element) {
oldPurchaseDetails = element;
});
}
return oldPurchaseDetails;
}
2023 年我使用 in_app_purchase:^3.1.11 它最适合应用内购买 flutter 包。
我与提供者状态管理
一起做我只是为应用程序内购买的完整解决方案制作了简单的提供程序类。 我还提供了我所做的正确信息。
class SubscriptionProvider extends ChangeNotifier {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
//! Private variables
bool _available = false;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
StreamSubscription<List<PurchaseDetails>>? _subscription;
bool _isLoading = false;
// final StreamController<StreamSubscription<List<PurchaseDetails>>> _processCompletionController = StreamController<StreamSubscription<List<PurchaseDetails>>>();
final StreamController<Stream<List<PurchaseDetails>>> _processCompletionController = StreamController<Stream<List<PurchaseDetails>>>();
//! Provider Variables
bool get isLoading => _isLoading;
Stream<Stream<List<PurchaseDetails>>> get subscription => _processCompletionController.stream;
bool get isAvailable => _available;
List<ProductDetails> get productDetails => _products;
List<PurchaseDetails> get purchaseDetails => _purchases;
Future<bool> get isActive async => await _isSubscriptionActive();
// This function will get details of available products are in store
Future<void> purchaseInit() async {
try {
_purchaseStream();
_isLoading = true;
notifyListeners();
_available = await _inAppPurchase.isAvailable();
debugPrint('_available : $_available');
notifyListeners();
final productIDs = userSubscribePlans.map<String>((e) => e['purchaseID']).toSet();
debugPrint('productIDs : $productIDs');
List<ProductDetails> products = await _getProducts(productIDs);
_products = products;
_isSubscriptionActive();
_isLoading = false;
notifyListeners();
} catch (e) {
_isLoading = false;
notifyListeners();
debugPrint('catchError : $e');
}
}
StreamSubscription<List<PurchaseDetails>> _purchaseStream() {
final Stream<List<PurchaseDetails>> purchaseUpdate = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdate.listen(
(purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
_purchases = purchaseDetailsList;
notifyListeners();
},
onDone: () {
_subscription?.cancel();
Fluttertoast.showToast(msg: "Done");
},
onError: (_) {
_subscription?.cancel();
Fluttertoast.showToast(msg: "Error : $_");
},
);
notifyListeners();
//If you want to used outside from provider
// one used here(inside) on outside both both side
_processCompletionController.sink.add(purchaseUpdate);
return _subscription!;
}
Future<List<ProductDetails>> _getProducts(Set<String> productIDs) async {
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(productIDs);
debugPrint('_getProductsError : ${response.error}');
debugPrint('response : ${response.productDetails}');
return response.productDetails;
}
Future<bool> buyProduct({required ProductDetails product}) async {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
final bool isSuccess = await _inAppPurchase.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: false, // False for android True for IOS
);
debugPrint('buyProduct : isSuccess => $isSuccess');
return isSuccess;
}
Future restorePlan() async {
debugPrint('restorePlan');
await _inAppPurchase.restorePurchases();
final response = await _inAppPurchase.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>().queryPastPurchases();
if (response.pastPurchases.isEmpty) {
Fluttertoast.showToast(msg: "You haven't any restore plan");
}
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
final BuildContext context = CurrentContext.currentContext;
purchaseDetailsList.forEach(
(PurchaseDetails purchaseDetails) async {
debugPrint('_status : ${purchaseDetails.status}');
debugPrint('_Error : ${purchaseDetails.error}');
switch (purchaseDetails.status) {
case PurchaseStatus.canceled:
{
Fluttertoast.showToast(msg: "You cancelled payment");
}
break;
case PurchaseStatus.pending:
{
Fluttertoast.showToast(msg: "Your subscription plan is pending");
}
break;
case PurchaseStatus.purchased:
{
Fluttertoast.showToast(msg: "You purchase plan successfully");
pushAndReplace(context, const DashboardScreen(isActive: true));
}
break;
case PurchaseStatus.restored:
{
// Restore purchases, if applicable.
final response = await _inAppPurchase.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>().queryPastPurchases();
if (response.pastPurchases.isNotEmpty) {
await _inAppPurchase.completePurchase(purchaseDetails);
//StoreData locally
final encode = SubscriptionsInfo.toJson(purchaseDetails);
sharedPref.subscriptionInfo = encodeSubscriptionInfo(SubscriptionsInfo.fromJson(encode));
debugPrint('RestoreInStorage ==> ${sharedPref.subscriptionInfo}');
debugPrint('mounted -> ${context.mounted}');
if (context.mounted) {
pushAndReplace(context, const DashboardScreen(isActive: true));
}
}
}
break;
case PurchaseStatus.error:
{
debugPrint('----Error : ${purchaseDetails.error}');
debugPrint('----msg : ${purchaseDetails.error?.message}');
if (purchaseDetails.error!.message == "BillingResponse.itemAlreadyOwned") {
Fluttertoast.showToast(msg: "You already purchased this plan");
Future.delayed(const Duration(seconds: 2, milliseconds: 200), () => Fluttertoast.showToast(msg: "Try to restore your plan"));
} else if (purchaseDetails.error!.message == "BillingResponse.developerError") {
Fluttertoast.showToast(msg: purchaseDetails.error?.details);
} else if (purchaseDetails.error!.message == "BillingResponse.itemUnavailable") {
Fluttertoast.showToast(msg: "Selected plan is unavailable");
}
}
break;
default:
{
Fluttertoast.showToast(msg: "Something went wrong! please try again");
}
break;
}
debugPrint('====> purchaseDetails.pendingCompletePurchase : ${purchaseDetails.pendingCompletePurchase}');
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
//StoreData locally
final encode = SubscriptionsInfo.toJson(purchaseDetails);
sharedPref.subscriptionInfo = encodeSubscriptionInfo(SubscriptionsInfo.fromJson(encode));
}
},
);
}
Future<bool> _isSubscriptionActive() async {
final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final QueryPurchaseDetailsResponse response = await androidAddition.queryPastPurchases();
debugPrint('isSubscriptionActive Error : ${response.error?.message}');
//! if get an error no plan is active we send false
if (response.error != null) return false;
debugPrint('isSubscriptionActive responseList : ${response.pastPurchases}');
//! if list is empty that mean no plan is active
//! there for we return false
if (response.pastPurchases.isEmpty) {
return false;
} else {
response.pastPurchases.forEach(
(_) {
debugPrint('status =====> ${_.status}');
debugPrint('pendingCompletePurchase =====> ${_.pendingCompletePurchase}');
debugPrint('productID =====> ${_.productID}');
debugPrint('transactionDate =====> ${_.transactionDate}');
debugPrint('transactionDate Parsed =====> ${DateTime.fromMillisecondsSinceEpoch(int.parse(_.transactionDate!))}');
debugPrint('purchaseID ====> ${_.purchaseID}');
},
);
return true;
}
}
}
使用方法
使用前声明提供者
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ContactProvider()),
ChangeNotifierProvider(create: (_) => MasterContactProvider()),
ChangeNotifierProvider(create: (_) => SubscriptionProvider()),
],
child: const Beginning(),
);