PAYATHON 2026

支付宝扫码支付成功后,苹果手机不执行回调函数

支付阿杰

结论

不要把支付宝客户端中的 JavaScript 回调当作支付成功的最终依据。建议按以下流程处理:

  1. 创建支付订单时设置服务端异步通知地址 notify_url。
  2. 服务端接收支付宝通知,完成验签后更新订单状态。
  3. 苹果端返回页面后,通过订单查询接口或轮询业务服务端获取支付结果。
  4. return_url、JSBridge 回调和前端 alert 只负责页面交互,不能代替异步通知。

安卓端能执行回调而苹果端不能,通常与两端支付宝客户端或 WebView 的页面生命周期、JSBridge 实现和跳转方式不同有关,不能据此判断支付订单是否完成。在缺少具体调用代码和运行环境的情况下,也无法仅凭这一现象确定具体的 iOS 兼容问题。

为什么苹果端可能不执行回调

扫码或唤起支付宝支付后,原来的 H5 页面可能进入后台、跳转到其他页面,甚至被支付宝重新创建。iOS 与 Android 对 WebView、页面挂起和跨应用跳转的处理并不完全相同,可能出现以下情况:

  • 支付完成后没有返回原来的 WebView。
  • 原页面已被销毁,保存在页面内存中的回调函数也随之消失。
  • 返回页面时触发了重新加载,原来的 JavaScript 执行上下文已经不存在。
  • 代码调用了仅适用于特定支付宝容器的 JSBridge API,但页面实际运行在 Safari、普通 WebView 或其他容器中。
  • 支付在回调注册完成前就已发起,或者 JSBridge 尚未初始化。
  • return_url、自定义 URL Scheme 或 Universal Link 没有在 iOS 上正确生效。
  • 页面恢复后使用 alert() 验证流程,但弹窗受到 WebView 页面状态或宿主策略的限制。

所以,没有弹出 alert 只能说明前端代码没有在预期页面中执行,不能用于判断支付宝是否已经完成扣款。

推荐的处理流程

1. 创建订单时传入 notify_url

notify_url 必须是支付宝服务器可以访问的公网 HTTPS 地址,不能使用 localhost、局域网地址,也不能要求登录后才能访问。

{
  "out_trade_no": "ORDER_202609130001",
  "total_amount": "99.00",
  "subject": "商品订单",
  "notify_url": "https://example.com/api/alipay/notify",
  "return_url": "https://example.com/pay/result?orderNo=ORDER_202609130001"
}

具体参数需要通过项目所用的支付宝支付产品和 SDK 设置。不同支付产品使用的下单接口可能不同,但都应由服务端确认最终支付状态。

2. 服务端接收通知并验签

下面的 Java 示例展示了核心处理方式。AlipaySignature.rsaCheckV1 的重载参数可能因支付宝 Java SDK 版本而异,请以项目实际使用的 SDK 接口为准。

@PostMapping("/api/alipay/notify")
@ResponseBody
public String alipayNotify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();

    request.getParameterMap().forEach((key, values) -> {
        if (values != null && values.length > 0) {
            params.put(key, String.join(",", values));
        }
    });

    boolean verified = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );

    if (!verified) {
        return "failure";
    }

    String outTradeNo = params.get("out_trade_no");
    String tradeNo = params.get("trade_no");
    String tradeStatus = params.get("trade_status");
    String totalAmount = params.get("total_amount");

    // 还应核对 app_id、seller_id、订单金额和订单归属,防止串单或伪造数据。
    Order order = orderService.findByOrderNo(outTradeNo);
    if (order == null || !order.getAmount().toPlainString().equals(totalAmount)) {
        return "failure";
    }

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        // 必须做幂等处理,支付宝可能重复发送通知。
        orderService.markPaidIfUnpaid(outTradeNo, tradeNo);
    }

    return "success";
}

验签通过并完成业务处理后,应按照支付宝接口要求返回纯文本 success。如果响应不正确,支付宝可能会再次发送通知。

3. 前端返回后查询自己的服务端

发起支付后,前端可以轮询业务订单状态,但不能根据客户端返回的“成功”直接修改数据库。

async function waitForPayment(orderNo) {
  const maxAttempts = 30;
  const interval = 2000;

  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const response = await fetch(
      `/api/orders/${encodeURIComponent(orderNo)}/payment-status`,
      {
        method: 'GET',
        credentials: 'include',
        cache: 'no-store'
      }
    );

    if (!response.ok) {
      throw new Error(`查询支付状态失败:${response.status}`);
    }

    const result = await response.json();

    if (result.status === 'PAID') {
      location.replace(
        `/pay/success?orderNo=${encodeURIComponent(orderNo)}`
      );
      return;
    }

    if (result.status === 'CLOSED' || result.status === 'FAILED') {
      throw new Error('订单已关闭或支付失败');
    }

    await new Promise(resolve => setTimeout(resolve, interval));
  }

  // 超时不代表支付失败,应显示“支付结果确认中”并允许用户重新查询。
  location.replace(
    `/pay/pending?orderNo=${encodeURIComponent(orderNo)}`
  );
}

4. 页面恢复时再次查询

iOS 返回 H5 页面时,原支付函数的回调不一定会执行。可以监听页面重新变为可见的事件,并主动查询订单状态:

const orderNo = 'ORDER_202609130001';

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') {
    waitForPayment(orderNo).catch(console.error);
  }
});

window.addEventListener('pageshow', () => {
  waitForPayment(orderNo).catch(console.error);
});

两个事件可能同时触发,造成重复轮询。可以增加运行状态锁:

let checkingPayment = false;

async function refreshPaymentStatus(orderNo) {
  if (checkingPayment) return;

  checkingPayment = true;

  try {
    await waitForPayment(orderNo);
  } finally {
    checkingPayment = false;
  }
}

如果使用支付宝 JSBridge

先确认页面确实运行在支持相应 API 的支付宝客户端容器中,再等待桥接对象准备完成后调用:

function onBridgeReady(callback) {
  if (window.AlipayJSBridge) {
    callback();
    return;
  }

  document.addEventListener('AlipayJSBridgeReady', callback, {
    once: true
  });
}

onBridgeReady(() => {
  AlipayJSBridge.call('tradePay', paymentParams, result => {
    console.log('tradePay result:', result);

    // 此处只触发服务端订单查询,不能仅凭 result 修改订单为已支付。
    refreshPaymentStatus(orderNo).catch(console.error);
  });
});

paymentParams 使用哪些字段,取决于接入的支付产品。不要混用不同产品或不同容器的参数格式,具体设置应以项目所用接口的官方文档为准。

排查重点

建议按以下顺序检查:

  1. notify_url 能否从公网访问,HTTPS 证书是否有效。
  2. 支付宝通知是否到达服务端,验签是否通过。
  3. 服务端是否核对订单号、金额和商户身份等信息。
  4. 通知处理是否具备幂等性。
  5. 苹果端完成支付后是否返回同一个页面,页面是否重新加载。
  6. JSBridge API 是否适用于当前支付宝客户端和页面容器。
  7. 回调是否在 JSBridge 初始化完成后注册。
  8. return_url、URL Scheme 或 Universal Link 是否已针对 iOS 正确配置。
  9. 不要只用 alert() 排查,还应记录 console.log、页面生命周期事件和服务端日志。

最终业务状态应以支付宝服务端通知验签成功,或服务端主动查询支付宝订单并确认成功为准。即使苹果端的回调问题已经修复,也必须保留服务端确认机制。

备注:内容仅供参考。