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

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:

  1. _initialize和_getPastPurchases中都注册了purchaseStream的监听器,导致同一个交易事件被多次处理;
  2. iOS的StoreKit不允许对同一个交易重复调用finishTransaction(对应Flutter中的completePurchase),重复调用会触发崩溃;
  3. _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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:00:59