PAYATHON 2026

flutter_inapp_purchase 支付报 ASDErrorDomain Code=825

支付老李

结论

ASDErrorDomain Code=825 不是 flutter_inapp_purchase 定义的业务错误,而是 iOS App Store/StoreKit 返回的内部错误。No transactions in response 表示支付请求已经发出,但商店响应中没有可交付的 SKPaymentTransaction,插件只能将其映射为:

SKPaymentTransactionStateFailed
code: E_UNKNOWN

问题通常出在沙盒账号、商品配置、App Store Connect 协议状态或苹果沙盒服务。error callback 不能视为支付成功,需要先检查商店配置和沙盒环境。

优先排查步骤

1. 确认商品能够正常查询

发起购买前,先调用 getProducts。如果查询结果为空或不包含目标商品,就不要继续调用 requestPurchase。

import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';

Future<void> loadProducts() async {
  const productIds = <String>[
    'com.example.app.product1',
  ];

  final products =
      await FlutterInappPurchase.instance.getProducts(productIds);

  for (final product in products) {
    print(
      'IAP product: id=${product.productId}, '
      'title=${product.title}, price=${product.localizedPrice}',
    );
  }

  if (!products.any((item) => item.productId == productIds.first)) {
    throw StateError('App Store 未返回目标商品,停止发起支付');
  }
}

如果查不到商品,重点检查以下内容:

  • Bundle ID 是否与 App Store Connect 中的 App 完全一致。
  • Product ID 是否逐字符一致,包括大小写。
  • In-App Purchase 商品是否已完整创建,价格和销售地区等信息是否有效。
  • 商品是否处于可测试状态,是否缺少必要的元数据。
  • Xcode 工程是否启用了 In-App Purchase capability。
  • 当前安装的 App 是否使用与 App Store Connect 对应的签名和 Bundle ID。

2. 检查 App Store Connect 协议状态

进入 App Store Connect,检查付费应用协议、税务信息和收款信息是否还有待处理项目。协议尚未生效或账户资料不完整时,沙盒购买也可能收到异常响应。

但 Code=825 并不只对应某一种协议问题。协议状态只是常见检查项,不能仅凭这个错误码判断具体缺少了哪一项。

3. 重建沙盒测试环境

沙盒账号或设备上的商店会话异常,也可能造成 No transactions in response。可以依次执行以下操作:

  1. 退出当前 Sandbox Apple Account。
  2. 删除测试 App。
  3. 重启设备。
  4. 重新安装开发包或 TestFlight 包。
  5. 新建一个 Sandbox Tester 并用它测试。
  6. 在系统弹出的购买界面中登录沙盒账号。

不同 iOS 版本的沙盒账号入口可能位于“设置 → 开发者 → Sandbox Apple Account”,也可能位于 App Store 的相关设置中。不要用正式 Apple ID 代替 Sandbox Tester。

还要确认设备满足以下条件:

  • 系统日期和时区正确。
  • 网络可以正常访问 App Store。
  • 没有代理、VPN、DNS 过滤或企业网络策略干扰 App Store 请求。
  • “屏幕使用时间”没有禁止 App 内购买。

如果多个沙盒账号和多台设备同时失败,也要考虑苹果沙盒服务暂时异常。遇到这种情况,稍后再试通常比在客户端连续重试更有效。

正确初始化并监听交易

应在查询商品和发起支付之前注册交易监听,否则可能漏掉 StoreKit 回调。

import 'dart:async';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';

StreamSubscription? _purchaseUpdatedSubscription;
StreamSubscription? _purchaseErrorSubscription;

Future<void> initializeIap() async {
  await FlutterInappPurchase.instance.initialize();

  _purchaseUpdatedSubscription =
      FlutterInappPurchase.purchaseUpdated.listen((purchase) async {
    print(
      'purchase-updated: '
      'productId=${purchase.productId}, '
      'transactionId=${purchase.transactionId}, '
      'transactionStateIOS=${purchase.transactionStateIOS}',
    );

    // 先把收据发送到服务端,通过 Apple 验证后再发放权益。
    final verified = await verifyPurchaseOnServer(purchase);

    if (!verified) {
      print('Receipt verification failed');
      return;
    }

    await deliverEntitlement(purchase.productId);

    // 收据已保存、权益已正确处理后再结束交易。
    await FlutterInappPurchase.instance.finishTransaction(purchase);
  });

  _purchaseErrorSubscription =
      FlutterInappPurchase.purchaseError.listen((error) {
    print(
      'purchase-error: '
      'responseCode=${error.responseCode}, '
      'code=${error.code}, '
      'message=${error.message}, '
      'debugMessage=${error.debugMessage}',
    );

    // 这里只记录并提示失败,不能发放商品。
  });
}

Future<void> buy(String productId) async {
  try {
    await FlutterInappPurchase.instance.requestPurchase(productId);
  } catch (error, stackTrace) {
    print('requestPurchase failed: $error');
    print(stackTrace);
  }
}

Future<bool> verifyPurchaseOnServer(dynamic purchase) async {
  // 将 transactionReceipt 等凭据提交到服务端验证。
  return false;
}

Future<void> deliverEntitlement(String? productId) async {
  // 根据业务逻辑发放权益,并保证重复调用不会重复发放。
}

Future<void> disposeIap() async {
  await _purchaseUpdatedSubscription?.cancel();
  await _purchaseErrorSubscription?.cancel();
  await FlutterInappPurchase.instance.finalize();
}

不同小版本访问这些流的方式可能稍有不同。如果使用 5.2.0 时 IDE 提示 purchaseUpdated 或 purchaseError 不是静态成员,应按照该版本的声明,从 FlutterInappPurchase.instance 获取。交易处理原则不受影响。

检查未完成交易

StoreKit 交易必须在业务处理完成后调用 finishTransaction。如果此前购买成功后没有结束交易,StoreKit 可能会在应用下次启动时重新投递。

处理时遵循以下原则:

  • purchased:验证收据并发放权益,随后结束交易。
  • restored:恢复权益并确认无误,随后结束交易。
  • failed:记录具体错误,不发放权益;底层应结束失败交易。
  • 不要刚收到回调就结束成功交易,否则应用崩溃或网络中断时可能失去发货机会。
  • 发货逻辑必须使用 transactionId 等信息保证幂等,避免重复发放。

反复调用 requestPurchase 无法清理旧交易,还可能让测试状态更难判断。

如何缩小问题范围

可以根据排查结果判断问题所在:

  • getProducts 返回空:优先检查 Product ID、Bundle ID、商品状态和 App Store Connect 配置。
  • 商品可以查询,但所有沙盒账号都报 Code=825:检查协议状态、网络环境和苹果沙盒服务。
  • 只有一个账号失败:很可能是 Sandbox Tester 状态异常,可以换新账号验证。
  • 只有一台设备失败:清理沙盒登录状态,重装 App,并检查设备限制和网络。
  • 已在支付面板确认购买,但仍进入 SKPaymentTransactionStateFailed:按失败处理,不能仅凭用户看到扣款界面就发放权益。
  • 收到 purchaseUpdated,但服务端验证失败:这是收据验证问题,与当前 Code=825 不在同一个处理阶段。

如果检查配置、更换 Sandbox Tester、重装应用并跨设备测试后仍能稳定复现,请保存发生时间、时区、Bundle ID、Product ID、iOS 版本和完整设备日志,再向 Apple Developer Support 提交问题。ASDErrorDomain 是苹果的内部错误域,flutter_inapp_purchase 无法在客户端修复 825,也不能将其转换为成功交易。

备注:内容仅供参考。