PAYATHON 2026

手机网站支付转 native URL 在沙盒环境无法拦截拉起支付宝沙盒版

支付老李

结论

服务器 SDK 生成的沙盒支付 URL 可以直接完成付款,通常意味着签名、应用密钥和支付参数没有明显问题。二合一页面无法拦截并拉起支付宝沙盒版,更可能是因为沙盒客户端对 H5 唤起链路、URL Scheme、Universal Link 或域名白名单的支持与线上支付宝不同,而非支付接口本身调用失败。

判断手机网站支付是否接入成功,不能只看支付宝沙盒版能否被拉起。应先确认沙盒 H5 收银台能正常下单、付款并发送异步通知。如果业务必须测试 App 唤起,还需确认当前支付宝沙盒版是否支持这条二合一链路。沙盒未覆盖的客户端能力,通常要等生产应用审核通过后,再通过小额订单验证。

为什么直接打开能支付,二合一页面却无法拉起

两种方式验证的并不是同一套能力:

  1. 直接打开 SDK 返回的 URL,验证的是 alipay.trade.wap.pay、签名和沙盒网关能否正常工作。
  2. 二合一页面还要尝试拉起客户端,因此会受到浏览器跳转策略、客户端注册的 Scheme、关联域名、页面来源和支付宝客户端版本等因素影响。
  3. 线上支付宝和支付宝沙盒版是两个不同的客户端。线上客户端能够识别的链接,沙盒版不一定注册或接管。
  4. 微信、内嵌 WebView、没有用户手势的 JavaScript 跳转等场景,可能会主动阻止外部 App 唤起。
  5. 如果二合一页面提取、拼接或改写了 SDK 返回的支付 URL,还可能破坏签名参数、编码或原有跳转结构。

所以,“线上能拉起、沙盒不能拉起”不能直接说明二合一代码有问题。在确认沙盒的能力范围之前,也不能仅凭这个现象认定原因一定是沙盒限制。

建议的排查顺序

1. 确认使用的是手机网站支付接口

移动浏览器场景应调用:

alipay.trade.wap.pay

不要混用电脑网站支付接口 alipay.trade.page.pay、App 支付接口 alipay.trade.app.pay 或当面付接口生成的内容。

2. 检查沙盒网关和密钥环境

沙盒请求应发送到:

https://openapi.alipaydev.com/gateway.do

同时确认以下配置都属于同一个沙盒应用:

  • appId
  • 应用私钥
  • 支付宝公钥
  • 签名算法
  • 沙盒买家账号

不要将生产 appId、生产支付宝公钥与沙盒网关混用。

3. 保留 SDK 返回的完整支付地址

后端应直接使用 SDK 生成的结果完成浏览器跳转。不要先解析,再自行拼装所谓的 native URL,也不要修改以下参数:

  • sign
  • sign_type
  • biz_content
  • timestamp
  • return_url
  • notify_url

如果 URL 中的 +、%2B、%2F、%3D 等内容被重复解码或重新编码,请求就可能出现异常。

4. 使用真实浏览器和用户点击测试

外部 App 的唤起通常需要明确的用户操作。建议优先使用按钮或链接:

<a href="/pay/create">立即支付</a>

避免在页面加载完成后自动执行:

window.location.href = payUrl;

还需要分别测试以下环境:

  • Android 系统浏览器或 Chrome
  • iOS Safari
  • 支付宝内置浏览器
  • 微信内置浏览器
  • 业务 App 的 WebView

如果只有微信或 WebView 无法完成跳转,原因通常是容器限制,不是支付接口故障。

5. 检查二合一页面是否拦截了跳转

重点排查以下情况:

  • 通过 AJAX 请求支付 URL,但没有让顶层页面跳转;
  • 在 iframe 中打开收银台;
  • 设置了限制跳转的 sandbox 属性;
  • 路由器或安全组件改写了 URL;
  • 使用 preventDefault() 阻止链接的默认行为;
  • 将沙盒地址误判为普通网页,因此没有进入唤起分支。

支付收银台更适合通过顶层页面跳转:

window.location.assign(payUrl);

不建议将其放入 iframe。

Node.js 示例

以下示例使用 alipay.trade.wap.pay。不同版本的 alipay-sdk 返回值形式可能不同,具体应以项目实际安装版本的接口定义为准。

const AlipaySdk = require('alipay-sdk').default;

const alipaySdk = new AlipaySdk({
  appId: process.env.ALIPAY_SANDBOX_APP_ID,
  privateKey: process.env.ALIPAY_APP_PRIVATE_KEY,
  alipayPublicKey: process.env.ALIPAY_SANDBOX_PUBLIC_KEY,
  signType: 'RSA2',
  gateway: 'https://openapi.alipaydev.com/gateway.do'
});

async function createWapPayment(req, res) {
  const payUrl = await alipaySdk.pageExec('alipay.trade.wap.pay', {
    method: 'GET',
    returnUrl: 'https://example.com/pay/return',
    notifyUrl: 'https://example.com/pay/notify',
    bizContent: {
      outTradeNo: `TEST_${Date.now()}`,
      productCode: 'QUICK_WAP_WAY',
      totalAmount: '0.01',
      subject: '沙盒支付测试'
    }
  });

  // 直接跳转到 SDK 返回的地址,不解析或重新拼接签名参数。
  res.redirect(payUrl);
}

如果二合一页面需要先从后端获取地址,可以让后端返回 JSON,再通过用户点击事件触发顶层页面跳转:

payButton.addEventListener('click', async () => {
  const response = await fetch('/api/pay/create', {
    method: 'POST'
  });

  const { payUrl } = await response.json();
  window.location.assign(payUrl);
});

不要在把 payUrl 输出到 HTML 后,再执行一次没有必要的 decodeURIComponent()。

如何判断接入是否已经正常

即使没有拉起支付宝沙盒版,只要以下流程可以完整走通,通常就能确认手机网站支付接口已经接通:

  1. SDK 成功生成沙盒收银台地址;
  2. 手机浏览器可以打开沙盒收银台;
  3. 沙盒买家可以完成付款;
  4. 服务端可以收到 notify_url 异步通知;
  5. 验签成功;
  6. 可以根据 out_trade_no 查询到正确的交易状态。

订单结果应以服务端异步通知和主动查询为准,不能依赖 return_url,也不能根据客户端是否成功拉起来判断。

仍然无法确定时

可以保留以下信息并提交给支付宝技术支持确认,但不要公开私钥、完整签名或买家账号密码:

  • 沙盒应用 appId
  • 调用的 API 名称
  • 支付宝沙盒版版本
  • 手机系统及版本
  • 浏览器或 WebView 类型
  • 请求时间和 out_trade_no
  • SDK 返回 URL 的脱敏结果
  • 是否由用户点击触发
  • 页面是否位于 iframe 或微信内置浏览器
  • 失败时的录屏和浏览器控制台日志

需要向官方确认的是,当前支付宝沙盒版是否支持该二合一方案使用的 H5-to-App 唤起方式。如果官方确认沙盒客户端未覆盖这项能力,就不应继续修改 Scheme 试图强行绕过。此时应先在沙盒中验证支付主链路,再到生产环境通过受控的小额订单测试客户端唤起。

备注:内容仅供参考。