Warm tip: This article is reproduced from serverfault.com, please click

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

发布于 2020-12-01 10:36:57

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,
  );
}
Questioner
Bennik2000
Viewed
0
Bennik2000 2020-12-02 01:45:00

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