PAYATHON 2026

苹果手机使用Safari进行支付宝H5支付跳转失败

支付阿杰

结论

Safari 和 iOS 版 Chrome 都提示“系统繁忙”,但夸克可以正常跳转,这通常不代表支付宝系统真的繁忙,更可能是支付请求或跳转链路在不同浏览器中的处理方式不一致。

常见原因有:

  • 商户页面根据 User-Agent 走了不同的支付逻辑。
  • 支付接口返回了 HTML 表单,但页面没有正确提交。
  • 支付请求由 AJAX、异步回调或非用户点击事件触发,因而受到浏览器限制。
  • 中间页面依赖 Cookie、跨站跟踪、弹窗或多次重定向,被 Safari 的隐私策略拦截。
  • 支付地址经过二次编码、参数丢失,或者 alipays:// URL Scheme 拼接错误。
  • 商户后端收到支付宝的错误响应后,统一显示为“系统繁忙”。

Safari 和 Chrome 在 iOS 上出现相同问题并不意外,因为两者在多数 iOS 环境中使用相同或相近的系统 Web 能力。夸克能够成功,只能说明它在用户代理、Cookie、跳转处理或拉起支付宝的方式上有所不同,不能据此认定 Safari 不支持支付宝 H5 支付。

先确认错误来自哪里

不要只看前端显示的“系统繁忙”,还要记录支付宝接口返回的这些信息:

  • code
  • msg
  • sub_code
  • sub_msg
  • 支付订单号
  • 商户订单号 out_trade_no
  • 请求时间
  • 当前浏览器的 User-Agent
  • 最终生成的支付 URL 或表单,但日志中不要记录签名、用户身份信息等敏感数据

如果“系统繁忙”只是商户系统的兜底文案,应先查看 sub_code、sub_msg 和服务端异常日志。缺少这些信息时,无法判断问题来自浏览器限制、订单参数错误,还是支付宝接口拒绝了请求。

推荐的支付流程

支付宝手机网站支付应由服务端调用 alipay.trade.wap.pay,生成支付宝提供的支付表单或跳转内容,再由浏览器完成页面级提交。

具体流程如下:

  1. 用户点击“立即支付”。
  2. 前端向商户服务端申请支付。
  3. 服务端创建唯一的 out_trade_no,并调用 alipay.trade.wap.pay。
  4. 服务端将支付宝 SDK 生成的完整 HTML 表单返回给浏览器。
  5. 浏览器提交表单,跳转到支付宝收银台。
  6. 支付结果以服务端异步通知为准,不能以浏览器返回页面为准。

不要在前端计算支付宝签名,也不要自行解析或重新拼接支付宝返回的支付地址。

服务端示例

下面是 Java SDK 的流程示例。具体类名和配置方式应与项目实际使用的支付宝 SDK 版本一致:

AlipayClient alipayClient = new DefaultAlipayClient(
    gatewayUrl,
    appId,
    merchantPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);

AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount(totalAmount);
model.setSubject(subject);
model.setProductCode("QUICK_WAP_WAY");
model.setQuitUrl(quitUrl);

request.setBizModel(model);

AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    // 记录 code、msg、subCode、subMsg,避免只返回“系统繁忙”
    throw new IllegalStateException(
        "支付宝下单失败: code=" + response.getCode()
        + ", msg=" + response.getMsg()
        + ", subCode=" + response.getSubCode()
        + ", subMsg=" + response.getSubMsg()
    );
}

// response.getBody() 通常是需要浏览器提交的 HTML 表单
String paymentForm = response.getBody();

服务端可以直接返回 HTML,由浏览器完成页面导航:

response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(paymentForm);

相比先通过 JSON 返回数据,再在前端拼装支付参数,直接输出 SDK 生成的表单更不容易出现编码、签名参数顺序或转义错误。

前端触发方式

支付动作最好直接由用户点击触发,并通过普通页面跳转或表单提交完成:

<form method="post" action="/payment/alipay/wap">
  <button type="submit">立即支付</button>
</form>

如果业务流程要求先异步创建订单,应避免在异步回调中使用 window.open() 打开支付页。Safari 可能把这种操作视为非用户触发的弹窗。

一种处理方式是在点击事件中同步打开空白页面,等订单创建成功后再修改页面地址:

async function startPayment() {
  const paymentWindow = window.open("", "_blank");

  if (!paymentWindow) {
    window.location.href = "/payment/alipay/wap";
    return;
  }

  try {
    const response = await fetch("/api/payment/order", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      }
    });

    if (!response.ok) {
      throw new Error(`Create order failed: ${response.status}`);
    }

    const result = await response.json();
    paymentWindow.location.href = result.paymentPageUrl;
  } catch (error) {
    paymentWindow.close();
    console.error(error);
    alert("支付订单创建失败,请稍后重试");
  }
}

更稳妥的做法是让当前页面直接进入商户支付入口,再由该入口输出支付宝表单,尽量减少弹窗和跨域跳转。

Safari 兼容性排查

1. 检查浏览器判断代码

如果系统根据 User-Agent 判断是否允许支付,需要确认 Safari 和 iOS Chrome 没有被误判:

const ua = navigator.userAgent;
console.log(ua);

不要只根据字符串中是否包含 Safari 或 Chrome 来判断支付能力。iOS 浏览器的 User-Agent 与桌面端不同,Chrome 通常使用 CriOS 等标识,硬编码判断很容易走错分支。

更合适的方式是使用能力检测,或者让所有普通手机浏览器统一进入标准 WAP 支付流程。

2. 减少中间跳转

检查支付过程是否存在类似的跳转链路:

业务页面
  → 短链接
  → 广告或统计页面
  → 登录页面
  → 商户支付页
  → 支付宝收银台

跳转次数过多时,Cookie、查询参数和 URL Fragment 都可能丢失。商户支付入口最好直接输出支付宝表单。

Safari 对跨站 Cookie 和跟踪行为的限制较严格。支付入口不应只依赖第三方 Cookie 保存订单状态。可以改用商户自己的短期支付令牌,并在服务端完成校验:

https://pay.example.com/alipay/wap?token=一次性随机令牌

令牌需要设置有效期和一次性使用限制,并在服务端绑定订单与当前用户。不要把金额、用户身份或签名密钥直接写入 URL。

4. 检查 URL 编码

不要对整个支付宝支付 URL 重复调用 encodeURIComponent():

// 不建议:可能破坏完整支付地址
location.href = encodeURIComponent(paymentUrl);

// 正确:支付地址由可信服务端生成后直接跳转
location.href = paymentUrl;

如果只需要编码单个查询参数,可以这样处理:

const url = `/payment/alipay/wap?orderId=${encodeURIComponent(orderId)}`;
location.href = url;

5. 检查 HTTPS 和证书

支付页面、回跳地址和通知地址应使用配置正确的 HTTPS。证书链异常、协议过旧、域名不匹配,或者页面混合加载 HTTP 内容,都可能导致 Safari 访问失败。

notify_url 必须能由支付宝服务端直接访问,不能使用局域网地址或 localhost,也不能依赖用户浏览器中的登录 Cookie。

不建议的处理方式

不要因为检测到 Safari,就直接引导用户更换浏览器。这种方式只能暂时绕过问题,不能修复支付链路中的兼容性缺陷。

还应避免以下做法:

  • 在前端保存商户私钥或生成支付签名。
  • 手工拼接支付宝 App 的 URL Scheme。
  • 混淆异步通知和同步回跳。
  • 根据 return_url 页面判断支付一定成功。
  • 将所有异常都显示为同一个“系统繁忙”页面。
  • 再次转义、编码或截取 SDK 返回的表单。
  • 为兼容某个浏览器而关闭订单金额、用户身份或签名校验。

注意事项

支付是否成功,必须以支付宝异步通知和服务端主动查询结果为准。浏览器成功跳转、显示成功页面或拉起支付宝 App,都不代表订单已经支付。

处理异步通知时还需要:

  • 验证支付宝签名。
  • 校验 app_id、商户订单号、金额和收款方信息。
  • 保证订单状态更新具有幂等性。
  • 已支付订单重复收到通知时,不要重复发货。
  • 返回符合支付宝接口要求的响应内容。
  • 不在日志或前端页面中暴露私钥、完整签名和用户敏感信息。

如果服务端日志显示 alipay.trade.wap.pay 已成功生成表单,但只有 Safari 和 iOS Chrome 支付失败,应重点排查用户手势、弹窗、Cookie、重定向链和 User-Agent 分支。如果接口本身返回失败,则应先根据 sub_code 和 sub_msg 修复业务参数或商户配置,而不是继续更换浏览器。

备注:内容仅供参考。