PAYATHON 2026

HyperPay 信用卡支付在 iOS live environment 中失败

支付小周

结论

这个错误通常与信用卡或 OTP 页面无关。更常见的原因是:服务端创建 checkoutId 的环境,与 iOS SDK 查询支付会话的环境不一致。

例如:

  • 服务端通过 HyperPay live 接口创建 checkoutId;
  • Android SDK 使用 live 模式,可以继续支付;
  • iOS SDK 仍处于 test/sandbox 模式,因而会到测试环境查询 live checkoutId;
  • 测试环境找不到该会话,于是返回:
No payment session found for the requested id

此外,checkoutId 已过期、被重复使用,或者 iOS 收到的 ID 并非刚创建的 ID,也可能导致同样的错误。只有先解决会话查询问题,支付流程才会进入 3-D Secure/OTP 验证。

重点检查项

1. 确认 iOS SDK 使用 live 模式

检查 Flutter 插件的 iOS 原生实现。HyperPay iOS SDK 初始化支付 Provider 时,需要明确指定 live 模式。不同 SDK 或插件版本的类名可能有所区别,常见写法如下:

let provider = OPPaymentProvider(mode: .live)

有些版本可能使用:

let provider = OPPaymentProvider(mode: OPPProviderMode.live)

如果代码使用 .test,或者插件默认采用测试模式,需要将其改为 .live。

只修改 Dart 层的环境变量可能不够。部分 Flutter HyperPay 插件会在 Android 和 iOS 端分别初始化原生 SDK。Android 已切换到 live,并不表示 iOS 也会自动使用 live。

2. 保证 checkoutId 与 SDK 环境一致

checkoutId 应由后端创建。Flutter 客户端不应直接携带 HyperPay access token 请求支付接口。

调用逻辑可以写成:

final checkoutId = await backend.createCheckout(
  amount: '100.00',
  currency: 'SAR',
);

await hyperPay.pay(
  checkoutId: checkoutId,
  paymentBrand: 'VISA',
);

后端创建 checkout 时,以下配置必须来自同一环境:

  • 请求地址;
  • access token;
  • entityId;
  • 商户账户及支付品牌配置;
  • iOS SDK 的 Provider mode。

示意代码:

const isLive = process.env.HYPERPAY_ENV === 'live';

const checkoutBaseUrl = isLive
  ? process.env.HYPERPAY_LIVE_BASE_URL
  : process.env.HYPERPAY_TEST_BASE_URL;

const response = await fetch(`${checkoutBaseUrl}/v1/checkouts`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HYPERPAY_ACCESS_TOKEN}`,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    entityId: process.env.HYPERPAY_ENTITY_ID,
    amount: '100.00',
    currency: 'SAR',
    paymentType: 'DB',
  }),
});

const data = await response.json();
return data.id;

实际使用的生产域名、请求参数和认证方式,应以商户所在区域及当前 HyperPay 接入文档为准。不要直接沿用测试环境的 URL 或凭据。

3. 每次支付都创建新的 checkoutId

不要缓存、复用或持久化旧的 checkoutId。用户点击支付时,应由后端即时创建新会话:

Future<void> startPayment() async {
  final checkoutId = await paymentApi.createCheckout();

  if (checkoutId.isEmpty) {
    throw StateError('Backend did not return a checkoutId');
  }

  await hyperPay.pay(
    checkoutId: checkoutId,
    paymentBrand: 'VISA',
  );
}

以下情况都可能触发相同的错误:

  • checkoutId 已超过有效期;
  • 使用同一个 checkoutId 重复发起支付;
  • Flutter 状态中仍保留上一次支付的 ID;
  • 创建 checkout 后等待时间过长;
  • iOS 传递参数时取错字段或传入空值。

错误信息中的 “more than 30min ago” 是网关提供的排查提示。具体有效期取决于当前接入配置和官方文档,但实际处理时,仍应在每次支付前创建新的 ID。

4. 检查 iOS 与 Android 实际收到的值

可以记录以下信息,但不要输出 token、完整卡号等敏感内容:

debugPrint('paymentEnv=live');
debugPrint('checkoutId=$checkoutId');
debugPrint('platform=${Platform.operatingSystem}');

检查以下问题:

  • iOS 收到的 checkoutId 是否为空或被截断;
  • iOS 是否误读了测试环境配置;
  • Android 和 iOS 是否请求同一个后端;
  • Release、Debug、Scheme 或 Flavor 是否加载了不同配置;
  • 服务端是否成功创建 checkout,而不是误将响应中的其他字段作为 id。

生产环境不宜长期记录完整的 checkoutId。排查时可以只保留首尾几位,或改用请求追踪 ID。

OTP 页面还需要检查的配置

OTP 页面通常属于 3-D Secure 验证流程。SDK 成功找到 checkout 会话并提交银行卡支付后,如果发卡行要求验证,才会显示该页面。当前错误发生在 OTP 流程之前,直接打开 OTP 页面无法解决问题。

会话环境修复后,如果 iOS 仍然无法从验证页面返回应用,再检查:

  • shopperResultUrl 是否正确;
  • iOS URL Scheme 或 Universal Link 是否已经配置;
  • Info.plist 中的回调 Scheme 是否与支付请求一致;
  • AppDelegate 或 SceneDelegate 是否将回调 URL 交给 HyperPay SDK;
  • live 商户账户是否已启用相应的支付品牌和 3-D Secure;
  • iOS App 的 bundle identifier、回调地址等是否符合商户后台配置。

不同版本的 HyperPay iOS SDK 可能采用不同的回调处理方式。应按照当前插件依赖的 SDK 文档进行配置,不要直接套用其他版本的 AppDelegate 示例。

推荐的排查顺序

  1. 在 iOS 原生代码中确认 Provider mode 为 .live。
  2. 确认服务端使用 live URL、live token 和 live entityId 创建 checkout。
  3. 清除客户端缓存,并在每次支付前重新获取 checkoutId。
  4. 比较 Android 和 iOS 实际访问的后端地址及环境标识。
  5. 确认 iOS Release Scheme 没有加载测试配置。
  6. 会话错误消失后,再检查 3-D Secure 回调和 URL Scheme。

如果 Android 使用同一后端返回的全新 checkoutId 可以完成支付,而 iOS 收到该 ID 后立即报 No payment session found,应优先检查 iOS 是否仍连接到 test 环境。

备注:内容仅供参考。