Flutter Android端in_app_purchase调用购买时弹出空白底部弹窗问题
Flutter内购调用
buyProduct()弹出空白弹窗问题 在Android平台开发Flutter应用时,使用in_app_purchase: ^3.1.8插件集成内购功能,采用测试SKU ID进行测试。调用buyProduct()方法触发支付时,仅弹出空白底部弹窗,无法正常显示Google支付详情页面(预期显示包含支付选项的标准Google支付界面)。
相关实现代码
import 'dart:async'; import 'dart:developer'; import 'dart:io'; import 'package:get/get.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import '../../../storage/sharde_preference.dart'; import '../../../utils/const.dart'; //import for GooglePlayPurchaseDetails import 'package:in_app_purchase_android/in_app_purchase_android.dart'; class LockScreenController extends GetxController{ final String categoryName = Get.arguments; final InAppPurchase inAppPurchase = InAppPurchase.instance; late StreamSubscription<dynamic> subscription; List<String> productId = <String>[ ANDROID_PURCHASE_SKU, ]; // Is the API available on the device RxBool isApiAvailable = true.obs; RxBool isProVersion = false.obs; // Products for sale RxList<ProductDetails> products = <ProductDetails>[].obs; // Past purchases RxList<PurchaseDetails> purchases = <PurchaseDetails>[].obs; @override void onInit() { final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream; subscription = purchaseUpdated.listen((List<PurchaseDetails> purchaseDetailsList) { listenToPurchaseUpdate(purchaseDetailsList); },onDone: (){ subscription.cancel(); },onError: (error){ log(error.toString(), name: "ERROR"); } ); initialize(); super.onInit(); } //buy product void buyProduct(ProductDetails productDetails) async { // Log product details to verify they are correct. log("Product ID: ${productDetails.id}, Title: ${productDetails.title}, Price: ${productDetails.price}"); final PurchaseParam param = PurchaseParam(productDetails: productDetails); InAppPurchase.instance.buyNonConsumable(purchaseParam: param,); } initialize() async { // Check availability of In App Purchases isApiAvailable.value = await inAppPurchase.isAvailable(); if(isApiAvailable.value){ await getProducts(); if(Platform.isAndroid){ // await getPastPurchases(); // verifyPurchases(); } } } //get all products Future<void> getProducts () async { // Set<String> ids = Set.from({ANDROID_PURCHASE_SKU}); ProductDetailsResponse response = await inAppPurchase.queryProductDetails(productId.toSet()); products.addAll(response.productDetails); log(" description:${products.first.description} id: ${products.first.id} title: ${products.first.title} price: ${products.first.price}", name: "PRODUCTS"); } //get past purchases Future getPastPurchases() async { await inAppPurchase.restorePurchases( applicationUserName: Platform.packageConfig ); } //product is purchased or not PurchaseDetails hasPurchased(String productID) { return purchases.firstWhere((purchase) => purchase.productID == productID); } //to verify purchases void verifyPurchases () { for (var product in products){ PurchaseDetails purchaseDetails = hasPurchased(product.id); if(purchaseDetails != null && purchaseDetails.status == PurchaseStatus.purchased){ log("_________PURCHASED", name: 'PRODUCT STATUS'); SharedPreference().saveBoolean(PREFKEY_ISPURCHASE, true); isProVersion = true.obs; update(); }else{ log('_________NOT PURCHASED', name: "PRODUCT STATUS"); SharedPreference().saveBoolean(PREFKEY_ISPURCHASE, false); isProVersion = false.obs; update(); } } } //listen on purchases update Future<void> listenToPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) async { for(PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.pending) { //print("Pending"); } else { if (purchaseDetails.status == PurchaseStatus.purchased) { SharedPreference().saveBoolean(PREFKEY_ISPURCHASE, true); isProVersion = true.obs; verifyPurchases(); update(); } else if (purchaseDetails.status == PurchaseStatus.error) { if (purchaseDetails.error?.message == PurchaseStatus.purchased.name) { SharedPreference().saveBoolean(PREFKEY_ISPURCHASE, true); isProVersion = true.obs; update(); } } if (Platform.isAndroid) { final InAppPurchaseAndroidPlatformAddition androidAddition = inAppPurchase.getPlatformAddition< InAppPurchaseAndroidPlatformAddition>(); await androidAddition.consumePurchase(purchaseDetails); } if(purchaseDetails.pendingCompletePurchase){ await inAppPurchase.completePurchase(purchaseDetails); } } } } @override void dispose() { // TODO: implement dispose super.dispose(); subscription.cancel(); } }
排查与解决方案
检查测试SKU有效性
确保使用Google官方测试SKU(如android.test.purchased),自定义测试SKU需在Google Play控制台创建并关联应用,且应用处于内部/封闭测试状态,测试账号已加入测试列表。验证商品查询结果
从日志确认getProducts()返回的ProductDetails包含完整信息(价格、标题、描述)。若信息缺失,需排查SKU ID错误、应用签名不一致或控制台配置问题。Android平台配置检查
- 在
AndroidManifest.xml添加内购权限:<uses-permission android:name="com.android.vending.BILLING" /> - 确保应用签名证书与Google Play控制台配置一致(测试环境需用上传到控制台的签名,而非debug签名)。
- 在
修正支付流程逻辑
- 非消耗品不应调用
consumePurchase,该方法仅适用于消耗品,非消耗品完成购买后只需调用completePurchase。 hasPurchased方法使用firstWhere会在无购买记录时抛出异常,改为firstWhereOrNull(需导入collection包)避免崩溃:PurchaseDetails? hasPurchased(String productID) { return purchases.firstWhereOrNull((purchase) => purchase.productID == productID); }- 修正
listenToPurchaseUpdate中的错误处理逻辑,添加支付失败的用户提示。
- 非消耗品不应调用
测试环境要求
使用安装Google Play商店的真实Android设备,登录已加入测试列表的Google账号,避免使用模拟器测试内购。
内容的提问来源于stack exchange,提问作者Payal Patel _d
相关产品推荐
相关产品推荐

