简体   繁体   中英

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM