Flutter同一账号跨设备内购异常:重复购买崩溃及循环问题
Flutter跨设备同一账号内购重复购买崩溃&无限循环问题排查与修复
核心问题根源
- 非消耗型/订阅项目的跨设备已购状态未被正确拦截:同一沙盒账号购买后,第二台设备未在购买前校验已购状态,导致用户点击确定时仍发起重复购买请求。
- 购买流的Stream管理混乱:重复订阅、未及时取消订阅,或者未正确处理购买状态回调,引发无限循环或内存泄漏,最终导致崩溃。
- 缺乏购买请求锁机制:多次点击确定按钮触发并发购买请求,导致支付SDK内部状态冲突。
分步修复方案
1. 购买前强制校验已购状态
在用户触发购买操作的第一时间,先查询账号的已购项目列表,若目标商品已购买,直接终止购买流程并提示用户。
示例代码(基于flutter_inapppurchase库):
Future<void> onPurchaseButtonTap(String productId) async { // 查询历史购买记录 final purchaseQueryResult = await InAppPurchase.instance.queryPastPurchases(); if (purchaseQueryResult.error != null) { debugPrint('查询已购项目失败: ${purchaseQueryResult.error!.message}'); return; } // 校验目标商品是否已购买 final hasPurchased = purchaseQueryResult.pastPurchases.any( (purchase) => purchase.productID == productId && purchase.status == PurchaseStatus.purchased ); if (hasPurchased) { // 提示用户已购买,终止流程 showDialog( context: context, builder: (_) => AlertDialog( title: const Text('提示'), content: const Text('该项目已购买,无需重复购买'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定') ) ] ) ); return; } // 未购买则发起购买请求 await _startPurchaseFlow(productId); } Future<void> _startPurchaseFlow(String productId) async { final products = await InAppPurchase.instance.queryProductDetails({productId}); if (products.error != null || products.productDetails.isEmpty) { debugPrint('获取商品详情失败'); return; } final purchaseParam = PurchaseParam(productDetails: products.productDetails.first); await InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam); }
2. 规范Stream订阅管理
避免重复订阅购买状态流,确保页面销毁时取消订阅,防止内存泄漏和回调冲突。
示例代码:
StreamSubscription<List<PurchaseDetails>>? _purchaseStreamSub; @override void initState() { super.initState(); // 仅在页面初始化时订阅一次购买流 _purchaseStreamSub = InAppPurchase.instance.purchaseStream.listen( _handlePurchaseUpdates, onError: (error) => debugPrint('购买流异常: $error') ); } @override void dispose() { // 页面销毁时必须取消订阅 _purchaseStreamSub?.cancel(); super.dispose(); } void _handlePurchaseUpdates(List<PurchaseDetails> purchases) async { for (final purchase in purchases) { switch (purchase.status) { case PurchaseStatus.pending: // 显示加载状态 break; case PurchaseStatus.purchased: // 处理购买成功逻辑(同步本地/服务器状态) await _processSuccessfulPurchase(purchase); break; case PurchaseStatus.error: // 提示用户购买失败 showDialog( context: context, builder: (_) => AlertDialog( title: const Text('购买失败'), content: Text(purchase.error?.message ?? '未知错误'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('确定'))] ) ); break; case PurchaseStatus.restored: // 处理恢复购买逻辑 await _processRestoredPurchase(purchase); break; } // 必须完成购买处理,避免队列阻塞 if (purchase.pendingCompletePurchase) { await InAppPurchase.instance.completePurchase(purchase); } } }
3. 添加购买请求锁
用布尔变量标记当前是否有正在进行的购买请求,拦截重复点击导致的并发请求。
示例代码:
bool _isPurchaseInProgress = false; Future<void> onPurchaseButtonTap(String productId) async { if (_isPurchaseInProgress) { // 已有购买请求在处理,直接返回 return; } _isPurchaseInProgress = true; try { // 执行前面的校验和购买流程 await _checkPurchasedAndStartFlow(productId); } finally { // 无论成功失败,释放锁 _isPurchaseInProgress = false; } }
4. 沙盒环境特殊适配
沙盒环境下支付回调可能存在延迟或重复通知,需额外处理:
- 购买成功后优先向服务器验证凭证,再更新本地状态,确保跨设备状态一致。
- 恢复购买后立即同步本地状态,避免用户因本地状态错误再次触发购买。
内容的提问来源于stack exchange,提问作者Testing Softradix
相关产品推荐
相关产品推荐

