Flutter集成in_app_purchase订阅,iOS调用_buyProduct时崩溃求助
Flutter in_app_purchase iOS端调用_buyProduct崩溃问题解决
问题描述
我在Flutter应用中集成应用内订阅功能,使用in_app_purchase包开发,Android端功能运行正常,但iOS端每次调用_buyProduct方法时应用会崩溃,不过支付表单会正常在主屏幕弹出。
相关代码
Future<void> _initialize() async { // 检查内购可用性 _available = await _iap.isAvailable(); if (_available) { await _getProducts(); await _getPastPurchases(); _verifyPurchase(); // 监听新购买 _subscription = _iap.purchaseStream.listen((data) { // 创建临时列表存储新购买记录 List<PurchaseDetails> newPurchases = List.from(data); // 迭代完成后更新UI setState(() { _purchases.addAll(newPurchases); _verifyPurchase(); }); }); } } // 获取所有可售商品 Future<void> _getProducts() async { Set<String> ids = _productIDs.toSet(); ProductDetailsResponse response = await _iap.queryProductDetails(ids); setState(() { _products = response.productDetails; }); } // 获取历史购买记录 Future<void> _getPastPurchases() async { // 监听购买更新 _iap.purchaseStream.listen((purchases) { List<PurchaseDetails> newPurchases = []; // 遍历购买记录,必要时过滤iOS购买记录 for (PurchaseDetails purchase in purchases) { if (Platform.isIOS) { _iap.completePurchase(purchase); } newPurchases.add(purchase); // 添加到临时列表 } // 迭代完成后更新_purchases列表 setState(() { _purchases.addAll(newPurchases); }); }); } // 返回特定商品ID的购买记录 PurchaseDetails? _hasPurchased(List<String> productIDs) { try { return _purchases .firstWhere((purchase) => productIDs.contains(purchase.productID)); } catch (e) { return null; } } // 自定义业务逻辑:验证购买状态 void _verifyPurchase() { PurchaseDetails? purchase = _hasPurchased(_productIDs); if (purchase != null && purchase.status == PurchaseStatus.purchased) { updateSubscriptionTypeData(_users, purchase.productID); } else { updateSubscriptionTypeData(_users, ''); } } // 购买商品 void _buyProduct(ProductDetails prod) { final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod); _iap.buyNonConsumable(purchaseParam: purchaseParam); // 用于订阅 }
崩溃日志
3 StoreKit 0x7ff8175f6c7d -[SKPaymentQueue finishTransaction:] + 361 4 in_app_purchase_storekit 0x10eacdd5d -[FIAPaymentQueueHandler finishTransaction:] + 93 (FIAPaymentQueueHandler.m:151) 5 in_app_purchase_storekit 0x10eadfd83 InAppPurchasePlugin.finishTransactionFinishMap(_:error:) + 2659 (InAppPurchasePlugin.swift:248) 6 in_app_purchase_storekit 0x10eae00a2 @objc InAppPurchasePlugin.finishTransactionFinishMap(_:error:) + 98 7 in_app_purchase_storekit 0x10ead9926 __SetUpInAppPurchaseAPI_block_invoke.456 + 150 (messages.g.m:705)
问题原因
从崩溃日志和代码来看,核心问题是重复监听purchaseStream并多次调用completePurchase:
_initialize和_getPastPurchases中都注册了purchaseStream的监听器,导致同一个交易事件被多次处理;- iOS的StoreKit不允许对同一个交易重复调用
finishTransaction(对应Flutter中的completePurchase),重复调用会触发崩溃; _getPastPurchases错误地通过监听流来获取历史购买,正确的方式应该是调用queryPastPurchases方法。
解决步骤
1. 修复历史购买记录获取逻辑
替换_getPastPurchases的实现,使用官方提供的queryPastPurchases方法获取历史购买,而非监听流:
Future<void> _getPastPurchases() async { QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases(); setState(() { _purchases.addAll(response.pastPurchases); _verifyPurchase(); }); }
2. 统一处理购买流,避免重复调用completePurchase
修改_initialize中的purchaseStream监听逻辑,确保每个交易只调用一次completePurchase,且在验证购买成功后执行:
_subscription = _iap.purchaseStream.listen((data) { for (var purchase in data) { switch (purchase.status) { case PurchaseStatus.purchased: case PurchaseStatus.restored: // 先执行购买验证逻辑 _verifyPurchase(); // 仅在iOS端调用completePurchase,且确保只调用一次 if (Platform.isIOS && !purchase.isPending) { _iap.completePurchase(purchase); } break; case PurchaseStatus.error: // 处理购买错误逻辑 break; case PurchaseStatus.pending: // 处理待确认的购买(比如需要家长验证) break; } // 更新本地购买列表,避免重复添加 setState(() { if (!_purchases.any((p) => p.purchaseID == purchase.purchaseID)) { _purchases.add(purchase); } }); } });
3. 组件销毁时取消订阅
在dispose方法中取消_subscription,避免内存泄漏和无效的流监听:
@override void dispose() { _subscription?.cancel(); super.dispose(); }
额外注意事项
- 确保
completePurchase仅在购买状态为purchased或restored且验证通过后调用; - iOS端测试内购需要使用沙箱账号,且确保App Store Connect中已正确配置订阅商品;
- 购买验证逻辑建议加入服务器端验证,避免客户端本地验证被篡改。
内容的提问来源于stack exchange,提问作者deandrehaijiel
相关产品推荐
相关产品推荐

