You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter in_app_purchase iOS部分设备无法恢复内购及兑换码问题求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:08:06