Flutter in_app_purchase iOS部分设备无法恢复内购及兑换码问题求助
大家好,我们团队开发了一款支持iOS和Android的Flutter应用,通过非消耗型内购提供额外内容,使用的是Flutter官方的in_app_purchase包。目前Android端运行一切正常,iOS在TestFlight测试环境也没有问题,但上线后有部分用户反馈兑换promo code时遇到了异常:
- 部分用户需要连续兑换两次promo code才能成功恢复内购
- 还有部分用户多次兑换后,依然无法恢复已购买的内容
我们已经在一台iOS设备上复现了该问题:
用同一个Apple ID,这台设备无法恢复已兑换的内购(兑换记录在App Store账号的购买历史中清晰可见),但切换到另一台iOS设备登录同一账号,内购恢复却完全正常。
关键代码实现
我们的内购核心逻辑封装在IAPStore组件中,以下是initState、restorePurchases和purchaseListener的核心代码:
class IAPStore extends StatefulWidget { const IAPStore({super.key}); @override IAPStoreState createState() => IAPStoreState(); } class IAPStoreState extends State<IAPStore> { late InAppPurchase inAppPurchase; Map<String, ProductDetails> products = {}; @override void initState() { super.initState(); inAppPurchase = InAppPurchase.instance; inAppPurchase.purchaseStream.listen( purchaseListener, onError: (error) { print('Purchase stream error: $error'); showErrorDialog(); }, cancelOnError: true, ); queryProductInformation().then((value) { if (value == null) { print('value in queryProductInformation is null!'); updateProcessing(false); return; } setState(() { for (var details in value.productDetails) { products[details.id] = details; } }); updateProcessing(false); }); } Future<void> restorePurchases() async { updateProcessing(true); await inAppPurchase.restorePurchases(); } void purchaseListener(List<PurchaseDetails> purchaseDetailsList) async { print( 'Called purchaseListener (purchaseDetailsList: ${purchaseDetailsList.length})'); if (purchaseDetailsList.isEmpty) { print('Purchase details list is empty!'); updateProcessing(false); return; } for (var purchaseDetails in purchaseDetailsList) { switch (purchaseDetails.status) { case PurchaseStatus.purchased: print('Purchase successful: ${purchaseDetails.productID}'); completePurchase(purchaseDetails.productID); break; case PurchaseStatus.canceled: print('Purchase was canceled'); updateProcessing(false); break; case PurchaseStatus.restored: print('Purchase restored'); completePurchase(purchaseDetails.productID); break; case PurchaseStatus.pending: print('Purchase pending'); break; case PurchaseStatus.error: print('Purchase error'); showErrorDialog(); break; } print('Calling completePurchase...'); await inAppPurchase.completePurchase(purchaseDetails); } } }
(注:queryProductInformation、updateProcessing、completePurchase、showErrorDialog为我们封装的辅助方法,核心逻辑未在上述代码中体现)
设备日志对比
我们对比了同一账号在正常设备和异常设备上的日志,发现了关键差异:
日志1 - 正常恢复内购的设备
14:48:17.032895+0200 Runner flutter: Found id: de.BUNDLEID.01, title: TITLE 1, price: €29.99 14:48:17.032922+0200 Runner flutter: Found id: de.BUNDLEID.bundle, title: TITLE Gesamtpaket, price: €59.99 14:48:17.032975+0200 Runner flutter: Found id: de.BUNDLEID.02, title: TITLE 2, price: €29.99 14:48:17.033001+0200 Runner flutter: Found id: de.BUNDLEID.extension, title: TITLE Plus, price: €9.99 14:48:20.656702+0200 storekitd [70D5C079]: Found 2 products in receipt with ID de.BUNDLEID.bundle 14:48:20.667793+0200 Runner flutter: Called purchaseListener (purchaseDetailsList: 1) 14:48:20.667838+0200 Runner flutter: Purchase restored ... products are found and listed correctly
从日志可以看到,storekit成功识别收据中的产品,
purchaseListener收到了包含1条记录的列表,随后正常处理恢复逻辑。
日志2 - 无法恢复内购的设备
14:23:26.150484+0200 Runner flutter: Found id: de.BUNDLEID.bundle, title: TITLE Gesamtpaket, price: €59.99 14:23:26.150513+0200 Runner flutter: Found id: de.BUNDLEID.02, title: TITLE 2, price: €29.99 14:23:26.150619+0200 Runner flutter: Found id: de.BUDNLEID.extension, title: TITLE Plus, price: €9.99 14:23:26.150657+0200 Runner flutter: Found id: de.BUNDLEID.01, title: TITLE 1, price: €29.99 14:23:27.125353+0200 dasd com.apple.icloud.searchpartyd.ProductInfoManager:C25423:[ (name: Thundering Herd Policy, policyWeight: 14:23:27.376336+0200 storekitd [Client] (Runner) Initialized with server Production bundle ID de.BUNDLEID.bundle and request bundl 14:23:27.390026+0200 storekitd AMSURRequestEncoder: (7BA6012D] Encoding request for URL: https://mzstorekit.itunes.apple.com/inApps/ 14:23:27.984831+0200 storekitd [7BA6012D]: Found 2 products in receipt with ID de.BUNDLEID.bundle 14:23:27.990235+0200 Runner flutter: Called purchaseListener (purchaseDetailsList: 0) 14:23:27.990271+0200 Runner flutter: Purchase details list is empty!
异常设备的日志中,storekit同样找到了收据中的产品,但
purchaseListener却收到了空的列表,导致无法触发后续的恢复逻辑。
我们现在的困惑点在于:同一Apple ID在不同iOS设备上的内购恢复行为不一致,而且异常设备的storekit明明已经识别到收据中的产品,但in_app_purchase包的回调却返回空列表。
有没有朋友遇到过类似的问题?或者能从我们的代码实现或日志中发现潜在问题?麻烦帮忙分析一下,非常感谢!
内容来源于stack exchange

