How to successfully complete an in app purchase using flutter on android?

2,875

The solution is to not call the completePurchase(...) method for consumable purchases. By default the library consumes the purchase for you which implicitly acts as a call to completePurchase(...).

Background

The call to InAppPurchaseConnection.instance.buyConsumable(...) has an optional boolean parameter autoConsume which is always true. This means that, on android, the purchase is consumed right before the callback to the purchaseUpdatedStream. The documentation of the completePurchase method says the following:

The [consumePurchase] acts as an implicit [completePurchase] on Android

Code to fix the problem

Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
  for (var p in purchaseDetailsList) {

    // Code to validate the payment

    if (!p.pendingCompletePurchase) continue;
    if (_isConsumable(p.productID)) continue; // Determine if the item is consumable. If so do not consume it

    var result = await InAppPurchaseConnection.instance.completePurchase(p);

    if (result.responseCode != BillingResponse.ok) {
      print("result: ${result.responseCode} (${result.debugMessage})");
    }
  }
}
Share:
2,875
Bennik2000
Author by

Bennik2000

Updated on December 26, 2022

Comments

  • Bennik2000
    Bennik2000 over 1 year

    What I want

    I want to integrate Google Play in app purchases into my flutter app, the product I want to sell is a consumable. Right now I am using the in_app_purchase: (0.3.4+16) package.

    What I did

    • Create the product in the google play developer console
    • Set up the project according to the documentation of the in_app_purchase package
    • Implemented some logic to buy a consumable item (see below)
    • Built an alpha release and uploaded it to the alpha test track in order to test it
    • Created a new google account on my developer phone, registered it as tester and downloaded the alpha version
    • Purchased with a "test card, always approves"
    • Purchased with my PayPal account

    Expected and actual result

    I expect the payment to work and all api calls to return ok.

    When I initiate a purchase on my phone the purchase flow starts and I can select the desired payment method. After I accept the payment the _listenToPurchaseUpdated(...) method is called, as expected. However, the call to InAppPurchaseConnection.instance.completePurchase(p) returns a BillingResponse.developerError and I get the following debug messages:

    W/BillingHelper: Couldn't find purchase lists, trying to find single data.
    I/flutter: result: BillingResponse.developerError (Purchase is in an invalid state.)
    

    This error comes with the "test card, always approves" and also when I start a real transaction using PayPal. For the PayPal purchase I got a confirmation Email, that the transaction was successful. In the documentation it says:

    Warning! Failure to call this method and get a successful response within 3 days of the purchase will result a refund on Android.

    Summarized question

    How can I get the call to InAppPurchaseConnection.instance.completePurchase(p) to return a successful result?

    The purchase implementation

    The code to setup in app purchases is implemented as shown in the documentation:

    InAppPurchaseConnection.enablePendingPurchases();
    
    Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchaseConnection.instance.purchaseUpdatedStream;
    
    _subscription = purchaseUpdated.listen(_listenToPurchaseUpdated, onDone: () {
        _subscription.cancel();
    }, onError: (error) {
      // handle error here.
    });
    
    ...
    
    Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
      for (var p in purchaseDetailsList) {
       
        // Code to validate the payment
    
        if (!p.pendingCompletePurchase) continue;
    
        var result = await InAppPurchaseConnection.instance.completePurchase(p);
    
        if (result.responseCode != BillingResponse.ok) {
          print("result: ${result.responseCode} (${result.debugMessage})");
        }
      }
    }
    

    To buy a consumable I have this method which queries the product details and calls buyConsumable(...)

    Future<bool> _buyConsumableById(String id) async {
      final ProductDetailsResponse response = await InAppPurchaseConnection
          .instance
          .queryProductDetails([id].toSet());
    
      if (response.notFoundIDs.isNotEmpty || response.productDetails.isEmpty) {
        return false;
      }
    
      List<ProductDetails> productDetails = response.productDetails;
    
      final PurchaseParam purchaseParam = PurchaseParam(
        productDetails: productDetails[0],
      );
    
      return await InAppPurchaseConnection.instance.buyConsumable(
        purchaseParam: purchaseParam,
      );
    }