Reputation: 1152
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.
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.
How can I get the call to InAppPurchaseConnection.instance.completePurchase(p)
to return a successful result?
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,
);
}
Upvotes: 4
Views: 7755
Reputation: 1152
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(...)
.
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
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})");
}
}
}
Upvotes: 4