In-app purchases on Flutter/Android with in_app_purchase - Google Play
I'm going to show you a bit of the code that works for me and that you can implement to make a payment through Google Play.
In this case, as you can see, the emulator doesn't allow payments through Google Play:
Internal Testing on Google Play (Android) and Steps for Payment Testing
I'm doing this on a real device. Note that you can make the purchase here, but if you're using an emulator—as in this case—it shows you a nice message like the one you see on the screen, saying that you can't access Google Play.
Installation of the dependency
The first thing you need to do is install the in_app_purchase dependency, which is what we use to make payments through Google Play. You can also configure it for Apple Pay—I think that's what it's called—although I haven't done it yet. That's a different topic:
pubspec.yaml
in_app_purchase:
I have an array (a list) with the products. You need to have those products defined somewhere, either by fetching them from an API or—as in my case—by having them fixed here. Since my product can have coupons, discounts, etc., I don't want to keep changing the configuration on the platform:
final List<String> _productsId = [
'',
'curso_libro_1_dolar',
'curso_libro_2_dolar',
***
'curso_libro_29_dolar',
'curso_libro_30_dolar',
];
Here's a list of products I sell, up to $30. Obviously, they must have their equivalent created on Google Play:
https://play.google.com/console/u/<user>/developers/
Remember to create them in the "Select App" → "Products" → "In-App Products" section. From there, you can start creating products like crazy, as if there were no tomorrow.
Process to make payment through Google Play
First, we call initialize. The first step here is to check if the product exists. If it doesn't exist, there's nothing to do, and I simply cancel the operation:
Future<void> _initialize() async {
//*** We verify that the array does not go out of range
if (_productsId.length <= priceKey) {
showToastMessage(context, LocaleKeys.productNotAvailable.tr());
return;
}
***
}
Next, I check if purchases are available. This is done with InAppPurchase.instance.isAvailable. If not, there's nothing to do.
//*** Check if Google Play
/*
The Google Play Store is installed and running,
the account has access to purchases,
the device can connect to Google billing.
*/
final bool isAvailable = await InAppPurchase.instance.isAvailable();
if (!isAvailable) {
showToastMessage(context, LocaleKeys.googlePlayNoAvailable.tr());
return;
}
After that, if the store is available, we search for the product. If we don't find it, we can't continue. This could be due to a mistake on my part or something similar. To search for the product, we use QueryProductDetails. There are more methods, but this is the one I need. If it's not found, it returns a notFoundID, and then I display an error and stop the process:
//*** Search for the product to sell
Set<String> kIds = {_productsId[priceKey]}; // para produccion
final ProductDetailsResponse response = await InAppPurchase.instance
.queryProductDetails(kIds);
// the product does not exist, don't create it in Google Play?
if (response.notFoundIDs.isNotEmpty) {
showToastMessage(context, LocaleKeys.googlePlayNoProduct.tr());
return;
}
Here I set up a listener to know what's happening. Many events occur, but the important ones are usually the success (done) or error events.
//*** Listen for events only once
// Used to detect changes to the purchase, in the app,
// To assign the product to the user when it's complete
InAppPurchase.instance.purchaseStream.listen(
_listenToPurchaseUpdated,
onDone: () {
// _buyProduct(response.productDetails.first);
},
onError: (error) {
showToastMessage(context, "${LocaleKeys.anErrorOccurred.tr()}: $error");
},
);
For example, the error occurs when you try to purchase a product you already own, which is why you receive the error message. Once all these errors and listeners have been sorted, I finally call the process to make the purchase:
// sell the product
_buyProduct(response.productDetails.first);
***
//*** Displays the payment screen
void _buyProduct(ProductDetails productDetails) {
final purchaseParam = PurchaseParam(productDetails: productDetails);
// buyNonConsumable It is a product that does not expire
_iap.buyConsumable(purchaseParam: purchaseParam);
// _iap.buyNonConsumable(purchaseParam: purchaseParam);
}
Product type: consumables and non-consumables
For the product the user wants to buy, you have two options:
- Non-consumable product: If you do something like what I do—tabulated products—it doesn't work well. For example, if I have two $10 books and the user buys one, when they try to buy the other, they'll be told they already have it. That creates a blockage.
- Consumable product: This one doesn't apply as much to my case, but it's what I ended up using. It's most useful for things like stars or coins in games. Non-consumable products would be real things like books, shoes, a house, etc.
_iap.buyConsumable(purchaseParam: purchaseParam); // buyConsumable es un producto que expira
_iap.buyNonConsumable(purchaseParam: purchaseParam); // buyNonConsumable es un producto que no expira
I agree to receive announcements of interest about this Blog.
I'll explain how you can create an app that makes payments through Google Play, since you can't use other APIs, such as PayPal and Stripe.
- Andrés Cruz