Flutter Android端非消耗型内购购买状态验证异常排查
问题:Flutter Android端非消耗型内购购买状态检查失败
问题背景
iOS端已成功实现非消耗型内购功能,但Android端出现购买状态验证问题:
- 产品ID为
kaidoku的内购商品配置完成,测试账号设置正常 purchase()函数执行成功,二次调用时提示"You already own this item",说明购买流程无问题- 启动应用调用
check()函数时,无法进入purchaseStream的监听回调,无法获取购买状态
调试输出
I/flutter (12879): Got into check() function I/flutter (12879): Got out of listening
简化的check()函数
Future<void> check() async { final inapp = InAppPurchase.instance; print("Got into check() function"); _subscription = inapp.purchaseStream.listen((data) async { print("Got into listening data"); }, onDone: () {}, onError: (error) {}); print("Got out of listening"); }
完整InApp类代码
import 'dart:async'; import 'package:flutter/foundation.dart'; import 'globals.dart' as globals; import 'package:in_app_purchase/in_app_purchase.dart'; String productId = 'kaidoku'; class InApp { final Set<String> _productIds = <String>{productId}; final inapp = InAppPurchase.instance; late StreamSubscription _subscription; Future<void> check() async { final completer = Completer<void>(); globals.purchaseCancel = false; globals.purchaseError = false; if (kDebugMode) { print("Got into check() function"); } _subscription = inapp.purchaseStream.listen((data) async { if (kDebugMode) { print("Got into listening data"); } if (data.isNotEmpty) { PurchaseDetails purchaseDetails = data.firstWhere((purchase) => purchase.productID == productId); switch (purchaseDetails.status) { case PurchaseStatus.purchased: if (purchaseDetails.pendingCompletePurchase) { await inapp.completePurchase(purchaseDetails); } globals.purchased = true; if (!completer.isCompleted) { completer.complete(); } break; case PurchaseStatus.error: globals.purchaseError = true; globals.purchaseErrorMessage = purchaseDetails.error.toString(); if (!completer.isCompleted) { completer.complete(); } break; case PurchaseStatus.pending: // Purchase is pending, waiting for update break; case PurchaseStatus.restored: if (purchaseDetails.pendingCompletePurchase) { await inapp.completePurchase(purchaseDetails); } globals.purchased = true; if (!completer.isCompleted) { completer.complete(); } break; case PurchaseStatus.canceled: globals.purchaseCancel = true; await inapp.completePurchase(purchaseDetails); if (!completer.isCompleted) { completer.complete(); } break; } } }, onDone: () { globals.purchaseCancel = true; if (!completer.isCompleted) { completer.complete(); } _subscription.cancel(); }, onError: (error) { globals.purchaseError = true; globals.purchaseErrorMessage = 'Network connection error.'; if (!completer.isCompleted) { completer.complete(); } }); if (kDebugMode) { print("Got out of listening"); } return completer.future; } /// purchase Future<void> purchase() async { if (globals.purchased) { return; } //user will now buy the product List<ProductDetails> availableProducts = await _getAvailableProducts(); if (availableProducts.isNotEmpty) { ProductDetails productDetails = availableProducts .firstWhere((productDetail) => productDetail.id == productId); final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); await inapp.isAvailable(); inapp.buyNonConsumable(purchaseParam: purchaseParam); } } /// Restore purchase Future<void> restore() async { inapp.restorePurchases(); } /// get products Future<List<ProductDetails>> _getAvailableProducts() async { await inapp.isAvailable(); ProductDetailsResponse productDetailQueryResponse = await inapp.queryProductDetails(_productIds); return Future.value(productDetailQueryResponse.error == null ? productDetailQueryResponse.productDetails : <ProductDetails>[]); } }
原因分析
purchaseStream触发逻辑差异:iOS端会自动同步已购状态到流中,但Android端需要显式调用restorePurchases()才会触发流推送已购数据check()函数缺少主动触发操作:仅订阅流但未触发恢复购买,流没有事件可推送,因此不会进入回调- 流订阅时机问题:如果先触发恢复再订阅流,会错过恢复操作产生的事件
解决建议
1. 修改check()函数,先订阅流再触发恢复购买
确保流处于监听状态,不会错过恢复操作的事件,同时增加内购服务可用性检查:
Future<void> check() async { final completer = Completer<void>(); globals.purchaseCancel = false; globals.purchaseError = false; // 先检查内购服务是否可用 bool isAvailable = await inapp.isAvailable(); if (!isAvailable) { globals.purchaseError = true; globals.purchaseErrorMessage = "In-app purchase not available"; completer.complete(); return completer.future; } if (kDebugMode) { print("Got into check() function"); } // 先订阅流 _subscription = inapp.purchaseStream.listen((data) async { if (kDebugMode) { print("Got into listening data"); } if (data.isNotEmpty) { PurchaseDetails purchaseDetails = data.firstWhere((purchase) => purchase.productID == productId); switch (purchaseDetails.status) { case PurchaseStatus.purchased: case PurchaseStatus.restored: if (purchaseDetails.pendingCompletePurchase) { await inapp.completePurchase(purchaseDetails); } globals.purchased = true; if (!completer.isCompleted) { completer.complete(); } break; case PurchaseStatus.error: globals.purchaseError = true; globals.purchaseErrorMessage = purchaseDetails.error.toString(); if (!completer.isCompleted) { completer.complete(); } break; case PurchaseStatus.pending: break; case PurchaseStatus.canceled: globals.purchaseCancel = true; if (!completer.isCompleted) { completer.complete(); } break; } } }, onDone: () { if (!completer.isCompleted) { completer.complete(); } _subscription.cancel(); }, onError: (error) { globals.purchaseError = true; globals.purchaseErrorMessage = 'Network connection error.'; if (!completer.isCompleted) { completer.complete(); } }); if (kDebugMode) { print("Got out of listening"); } // 触发恢复购买,让流推送已购数据 inapp.restorePurchases(); return completer.future; }
2. 确保completePurchase执行彻底
购买或恢复成功后,必须调用completePurchase标记交易完成,否则Android端可能会重复触发交易事件,导致状态异常
3. 处理流的广播特性(可选)
将purchaseStream转换为广播流,避免多次订阅丢失事件:
final purchaseStream = InAppPurchase.instance.purchaseStream.asBroadcastStream();
内容的提问来源于stack exchange,提问作者seki
相关产品推荐
相关产品推荐

