Flutter [in_app_purchase] Get all plans inside subscription
Asked Answered
B

3

12

I'm using the in_app_purchase package, but I only can get one plan inside the subscriptions

I have 3 subscriptions:

Basic subscription
Premium subscription
Enterprise subscription

And inside each subscription, I want to have 2 plans:

Month plan
Year plan

I always get the plan that has the "backward compatibility"("This will be the baseline returned by the deprecated Google Play Billing Library method querySkuDetailsAsync()") enabled.

Is any way to get all plans, or do I have to have 6 subscriptions with only 1 plan in each one?

Edit:

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
import 'package:motorline_home/widgets/materials/appbar/appbar_title_widget.dart';
import 'package:motorline_home/widgets/materials/pop_button_widget.dart';
import 'package:rxdart/subjects.dart';

class SubscriptionPage extends StatefulWidget {

  const SubscriptionPage({
    Key? key,
  }) : super(key: key);

  @override
  State<SubscriptionPage> createState() => _SubscriptionPageState();
}

class _SubscriptionPageState extends State<SubscriptionPage> {
  // In app subscriptions
  InAppPurchase _inAppPurchase = InAppPurchase.instance;
  late StreamSubscription<List<PurchaseDetails>> _inAppPurchaseSubscription;
  StreamController<List<ProductDetails>> _streamGooglePlaySubscriptions =
      BehaviorSubject();
  final List<String> _subscriptionsIDs = [
    "basic",
    "premium",
    "enterprise",
  ];

  @override
  void initState() {
    super.initState();

    // In app purchase subscription
    _inAppPurchaseSubscription =
        _inAppPurchase.purchaseStream.listen((purchaseDetailsList) {
      _listenToPurchaseUpdated(purchaseDetailsList);
    }, onDone: () {
      print("In app purchase onDone");
      _inAppPurchaseSubscription.cancel();
    }, onError: (error) {
      print("In app purchase error: ${error.toString()}");
      // handle error here.
      _inAppPurchaseSubscription.cancel();
    });
    // Initialize in app purchase
    _initializeInAppPurchase();
  }

  @override
  void dispose() {
    if (Platform.isIOS) {
      final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
      _inAppPurchase
          .getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
      iosPlatformAddition.setDelegate(null);
    }

    // Cancel in app purchase listener
    _inAppPurchaseSubscription.cancel();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: AppBarTitleWidget(
          title: FlutterI18n.translate(context, "subscriptions"),
        ),
        leading: PopButtonWidget(),
      ),
      // Body
      body: Container(),
    );
  }

  void _initializeInAppPurchase() async {
    print("Initializing in app purchase");
    bool available = await _inAppPurchase.isAvailable();
    print("In app purchase initialized: $available");

    if (available) {
      if (Platform.isIOS) {
        final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
        _inAppPurchase
            .getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
        await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
      }

      // Get subscriptions
      List<ProductDetails> subscriptions = await _getSubscriptions(
        productIds:
          _subscriptionsIDs.toSet(),
      );
      // Sort by price
      subscriptions.sort((a, b) => a.rawPrice.compareTo(b.rawPrice));

      // Add subscriptions to stream
      _streamGooglePlaySubscriptions.add(subscriptions);

      // DEBUG: Print subscriptions
      print("In app purchase subscription subscriptions: ${subscriptions}");
      for (var subscription in subscriptions) {
        print("In app purchase plan: ${subscription.id}: ${subscription.rawPrice}");
        print("In app purchase description: ${subscription.description}");
        // HOW GET ALL PLANS IN EACH SUBSCRIPTION ID?
      }

      await InAppPurchase.instance.restorePurchases();
    }
  }

  // In app purchase updates
  void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
    purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
      // If purchase is pending
      if (purchaseDetails.status == PurchaseStatus.pending) {
        print("In app purchase pending...");
        // Show pending ui
      } else {
        if (purchaseDetails.status == PurchaseStatus.canceled) {
          print("In app purchase cancelled");
        }
        // If purchase failed
        if (purchaseDetails.status == PurchaseStatus.error) {
          print("In app purchase error");
          // Show error
        } else if (purchaseDetails.status == PurchaseStatus.purchased ||
            purchaseDetails.status == PurchaseStatus.restored) {
          print("In app purchase restored or purchased");
        }

        if (purchaseDetails.pendingCompletePurchase) {
          debugPrint("In app purchase complete purchased");
          debugPrint(
              "In app purchase purchase id : ${purchaseDetails.purchaseID}");
          debugPrint(
              "In app purchase server data : ${purchaseDetails.verificationData.serverVerificationData}");
          debugPrint(
              "In app purchase local data  : ${purchaseDetails.verificationData.localVerificationData}");
          // Verify purchase on backend

          try {
            // VALIDADE PURCHASE IN BACKEND
          } catch (error) {
            debugPrint("In app purchase error: ${error.toString()}");
          }
        }
      }
    });
  }

  // Get subscription
  Future<List<ProductDetails>> _getSubscriptions(
      {required Set<String> productIds}) async {
    ProductDetailsResponse response =
        await _inAppPurchase.queryProductDetails(productIds);

    return response.productDetails;
  }
}

/// Example implementation of the
/// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc).
///
/// The payment queue delegate can be implementated to provide information
/// needed to complete transactions.
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
  @override
  bool shouldContinueTransaction(
      SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
    return true;
  }

  @override
  bool shouldShowPriceConsent() {
    return false;
  }
}
Benoit answered 9/6, 2022 at 13:26 Comment(2)
Hi Renato, if you would provide us your code sample we could have a look at the issue but with only what you wrote we can't help you with that and can only give you the answer. It is possible.Epagoge
@Epagoge Code added to question. My question is if after getting subscriptions with "queryProductDetails" I can get all plans inside each subscription.Benoit
E
1

What you like to do is not possible. You need to create for each subscription a new plan, you can't say a Premium Subscription does have a yearly and a monthly plan.

Epagoge answered 13/6, 2022 at 13:41 Comment(6)
Why is it not possible? What is the point in having "base plans" then?Gerah
This is what I found, too. But the reason for having different base plans is exactly the variation of timeframes like monthly, yearly etc.Diploid
Hi, someone gets the answer? I understand that have multiple base plans inside just one subscription is the correct way, but I cant finda API in flutter for get the information about these base plansPtah
@Ptah You don't need to search there for Flutter API this is a Apple/Google API Restriction.Epagoge
if I create for each subscription a new plan with extra logic, how does it behave when the user wants to "upgrade". Should he buy the new subscription? What happens then? Will it be active at the end of the billing period or will it be canceled?Kinnon
@Kinnon I'm sorry, but this far I haven't tested the Subscription package.Epagoge
E
2

This is not possible at the moment since it's not supported by official plugin (and we don't know when it will be):

https://github.com/flutter/flutter/issues/110909

As they mentioned, solution is one plan per subscription with the extra logic necessary in your app (e.g. to detect if subscription with ID PLUS-year is an upgrade of PLUS-month)

Ewart answered 4/12, 2022 at 12:5 Comment(0)
A
2

This is now possible in the in_app_purchase library thanks to this PR: https://github.com/flutter/packages/commit/dc5ff42b1b9d634567f1967229e59ffb93a300af.

Now, if you have a subscription with id: my_subscription that has two base plans. Simply calling queryProductDetails(["my_subscription"]) will return two items of type GooglePlayProductDetails: both base plans.

What I'm still confused about and would like to know is how do we know which is which? I can't seem to find the info in GooglePlayProductDetails that delineates between the two base plans.

Antiproton answered 21/5, 2024 at 20:36 Comment(2)
same problem here, any advise?Hereby
I just did a hacky workaround where I checked which one had a higher priceAntiproton
E
1

What you like to do is not possible. You need to create for each subscription a new plan, you can't say a Premium Subscription does have a yearly and a monthly plan.

Epagoge answered 13/6, 2022 at 13:41 Comment(6)
Why is it not possible? What is the point in having "base plans" then?Gerah
This is what I found, too. But the reason for having different base plans is exactly the variation of timeframes like monthly, yearly etc.Diploid
Hi, someone gets the answer? I understand that have multiple base plans inside just one subscription is the correct way, but I cant finda API in flutter for get the information about these base plansPtah
@Ptah You don't need to search there for Flutter API this is a Apple/Google API Restriction.Epagoge
if I create for each subscription a new plan with extra logic, how does it behave when the user wants to "upgrade". Should he buy the new subscription? What happens then? Will it be active at the end of the billing period or will it be canceled?Kinnon
@Kinnon I'm sorry, but this far I haven't tested the Subscription package.Epagoge

© 2022 - 2025 — McMap. All rights reserved.