如何在 Flutter 中首次购买应用内购买订阅?

问题描述 投票:0回答:2

我正在尝试使用 in_app_purchase 包在我的 Flutter 应用程序中实现订阅系统。我在文档中发现的只是我需要旧的购买来处理订阅:

final PurchaseDetails oldPurchaseDetails = ...;
PurchaseParam purchaseParam = PurchaseParam(
    productDetails: productDetails,
    changeSubscriptionParam: ChangeSubscriptionParam(
        oldPurchaseDetails: oldPurchaseDetails,
        prorationMode: ProrationMode.immediateWithTimeProration));
InAppPurchaseConnection.instance
    .buyNonConsumable(purchaseParam: purchaseParam);

我已经有了产品详细信息。 第一次如何订阅?

flutter in-app-purchase google-play-console in-app-subscription
2个回答
0
投票

第一次订阅不需要旧的订阅,参数

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;
    }

0
投票

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(),
    );
© www.soinside.com 2019 - 2024. All rights reserved.