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

Flutter in_app_purchase插件iOS恢复购买先识别为新购买的问题咨询

iOS端恢复内购出现重复状态的原因及解决方案

原因分析

iOS App Store的内购恢复机制和Android存在本质差异:当调用restorePurchases()时,系统会先将已购买的非消耗品交易以PurchaseStatus.purchased状态推送给观察者,随后再将这些交易更新为PurchaseStatus.restored状态。这是iOS平台的原生行为,in_app_purchase插件只是如实转发了系统的状态变化事件,因此会出现先收到purchased再收到restored的情况。

同时你的代码存在两个放大问题的细节:

  • 在处理purchased状态时调用了completePurchase(),这会让iOS系统误判该交易为主动完成的购买,干扰后续恢复状态的推送逻辑。
  • 未区分主动购买和恢复操作的事件流,导致两种场景的状态被无差别处理,引发重复逻辑执行。

解决方案

1. 增加恢复状态标记,区分事件场景

在类中添加布尔变量标记当前是否处于恢复流程,调用restorePurchases()时设为true,恢复完成后重置为false,以此跳过恢复流程中的purchased状态处理:

bool _isRestoring = false;

// 恢复购买的方法
Future<void> restorePurchases() async {
  _isRestoring = true;
  await iap.restorePurchases();
  // 恢复完成后重置标记
  _isRestoring = false;
}

Future<void> listenForPurchases() async {
  print("Called listenForPurchases method");
  iap.purchaseStream.listen((List<PurchaseDetails> purchases) async {
    print("Listened");
    bool purchaseStatus = await purchaseProducts().handlePurchase(purchases);
    if (purchaseStatus == true) {
      for (var purchase in purchases) {
        print("User has ${purchase.status.toString()} ${purchase.productID}");
        if (purchase.status == PurchaseStatus.purchased) {
          // 仅在非恢复流程时处理主动购买逻辑
          if (!_isRestoring) {
            print("NEW Purchase was made");
            // 执行主动购买的业务逻辑
            await iap.completePurchase(purchase);
          }
        } else if (purchase.status == PurchaseStatus.restored) {
          print("Restore was made");
          // 执行恢复购买的业务逻辑
          // iOS恢复的交易无需调用completePurchase,系统会自动处理
        }
      }
    }
  });
}

2. 避免在恢复流程中调用completePurchase

iOS平台的恢复交易由系统自动完成收尾操作,只有用户主动发起的购买才需要手动调用completePurchase()确认交易完成。

3. 过滤重复交易事件

通过PurchaseDetails的transactionID过滤已处理过的交易,避免因状态重复推送导致逻辑重复执行:

final Set<String> _processedTransactionIds = {};

Future<void> listenForPurchases() async {
  print("Called listenForPurchases method");
  iap.purchaseStream.listen((List<PurchaseDetails> purchases) async {
    print("Listened");
    bool purchaseStatus = await purchaseProducts().handlePurchase(purchases);
    if (purchaseStatus == true) {
      for (var purchase in purchases) {
        // 跳过已处理的交易
        if (_processedTransactionIds.contains(purchase.transactionID)) continue;
        
        print("User has ${purchase.status.toString()} ${purchase.productID}");
        if (purchase.status == PurchaseStatus.purchased) {
          if (!_isRestoring) {
            print("NEW Purchase was made");
            // 业务逻辑
            await iap.completePurchase(purchase);
            _processedTransactionIds.add(purchase.transactionID!);
          }
        } else if (purchase.status == PurchaseStatus.restored) {
          print("Restore was made");
          // 业务逻辑
          _processedTransactionIds.add(purchase.transactionID!);
        }
      }
    }
  });
}

内容的提问来源于stack exchange,提问作者jscodes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 08:00:14