PAYATHON 2026

H5 页面如何调用支付宝 JSAPI 支付?

支付小周

结论

普通 H5 页面无法在任意浏览器中直接调用支付宝小程序的 my.tradePay。聚合支付码牌通常走的是下面这条受控链路,而不是由外部浏览器中的 H5 调用小程序 JSAPI:

  1. 用户使用支付宝客户端扫码;
  2. 收银页面在支付宝客户端的内置 WebView 中打开;
  3. 服务端通过支付宝开放平台接口创建交易;
  4. 页面调用支付宝客户端注入的 AlipayJSBridge,传入支付宝交易号并唤起收银台。

页面能否发起支付,不只是看有没有调用代码,还要看扫码入口、客户端环境、应用权限、签约产品、域名配置和订单创建方式。把同一个 URL 复制到微信、系统浏览器或普通 WebView 中打开,通常无法使用这条链路。

如果需要兼容外部浏览器,应接入支付宝手机网站支付,例如 alipay.trade.wap.pay,不要强行调用 JSAPI。

为什么会提示“唤起方式不正确”

支付宝会校验支付请求的运行环境和来源。常见原因有:

  • 页面没有在支付宝客户端的内置 WebView 中打开;
  • 调用了仅供支付宝小程序使用的 my.tradePay;
  • 页面虽然在支付宝中打开,但没有通过允许的扫码入口或业务入口进入;
  • 开放平台应用没有相应支付产品的权限;
  • 页面域名、应用、商户账号或支付产品没有正确关联;
  • 前端传入的不是服务端创建交易后返回的有效支付宝交易号;
  • 订单所属的应用或商户与当前调用环境不一致;
  • 页面通过调试工具、复制链接或自定义 scheme 模拟入口,触发了风控或来源校验。

支付宝客户端内存在 AlipayJSBridge,不等于任何网页都能无条件调用支付能力。

推荐的实现流程

1. 准备支付宝开放平台应用

使用已完成必要认证的支付宝开放平台应用,并根据实际业务签约对应的支付产品。

开放平台可能会调整产品名称、准入范围和配置入口,具体应以当前商户后台展示的能力为准。不能只看前端接口名称,就认定应用已经获得支付权限。

还需要检查:

  • app_id 是否正确;
  • 商户账号是否已与应用绑定;
  • 支付产品是否已经生效;
  • H5 收银页是否使用 HTTPS;
  • 页面域名是否已按产品要求配置;
  • 服务端是否正确保存了支付宝应用私钥和支付宝公钥。

2. 让用户从支付宝客户端进入页面

码牌中的二维码一般指向一个 HTTPS 收银地址,例如:

https://pay.example.com/cashier?store_id=1001&terminal_id=2001

用户使用支付宝扫码后,该地址会在支付宝客户端内打开。二维码中不要直接写入金额、商品名称或可复用的支付参数。金额和门店信息应由服务端根据可信数据确定。

只通过 User-Agent 判断页面是否处于支付宝浏览器并不可靠,还要检查 AlipayJSBridge 是否真正可用。

3. 获取付款用户身份

如果创建交易的接口需要付款人的支付宝身份,通常要先完成支付宝网页授权:

  1. 服务端生成授权地址;
  2. 用户在支付宝内完成静默授权或主动授权;
  3. 支付宝回跳业务页面,并携带 auth_code;
  4. 服务端使用 auth_code 换取用户标识;
  5. 服务端为该用户创建交易。

授权范围应根据业务需要选择。仅需识别支付宝用户时,通常优先使用最小授权范围。授权地址、回调地址和相关参数都应由服务端生成并校验,不能直接信任前端传值。

4. 服务端创建订单

订单必须由服务端创建。前端只提交业务订单标识,服务端负责:

  • 查询真实金额;
  • 校验门店和终端;
  • 生成唯一的 out_trade_no;
  • 调用支付宝交易创建接口;
  • 保存业务订单号与支付宝交易号的对应关系;
  • 将唤起支付所需的交易号返回前端。

下面是示意代码,具体 SDK 方法名应以实际使用的支付宝服务端 SDK 为准:

app.post('/api/pay/create', async (req, res) => {
  const { orderId } = req.body;

  const order = await loadAndValidateOrder(orderId);

  // 金额必须从服务端订单中读取,不能采用前端提交的金额。
  const result = await alipaySdk.exec('alipay.trade.create', {
    bizContent: {
      out_trade_no: order.outTradeNo,
      total_amount: order.totalAmount,
      subject: order.subject,
      buyer_id: order.alipayUserId
    }
  });

  res.json({
    tradeNO: result.tradeNo
  });
});

这里的接口和字段只用于说明典型流程。是否必须传入 buyer_id,以及应该使用哪个交易创建接口,取决于已签约的支付产品和当前开放平台文档。

5. 在支付宝 WebView 中调用 AlipayJSBridge

页面应等待桥接对象初始化完成,再调用 tradePay:

<script>
  function ready(callback) {
    if (window.AlipayJSBridge) {
      callback();
      return;
    }

    document.addEventListener(
      'AlipayJSBridgeReady',
      callback,
      false
    );
  }

  async function startPay(orderId) {
    if (!window.AlipayJSBridge) {
      throw new Error('当前页面不在支持支付的支付宝客户端环境中');
    }

    const response = await fetch('/api/pay/create', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ orderId })
    });

    if (!response.ok) {
      throw new Error('创建支付订单失败');
    }

    const { tradeNO } = await response.json();

    return new Promise((resolve, reject) => {
      AlipayJSBridge.call(
        'tradePay',
        { tradeNO },
        result => {
          if (result.resultCode === '9000') {
            resolve(result);
          } else {
            reject(result);
          }
        }
      );
    });
  }

  ready(() => {
    document
      .querySelector('#pay-button')
      .addEventListener('click', async () => {
        try {
          await startPay('BUSINESS_ORDER_ID');
          location.href = '/pay/result?order_id=BUSINESS_ORDER_ID';
        } catch (error) {
          console.error('支付未完成', error);
        }
      });
  });
</script>

<button id="pay-button" type="button">支付宝支付</button>

AlipayJSBridge.call('tradePay', ...) 是支付宝客户端内的网页桥接调用,与小程序中的 my.tradePay 分属不同运行环境,不能混用。

不同支付产品允许传入的参数可能不同。有些文档或历史方案中还会出现其他参数形式。接入时应严格按照当前产品文档传参,不要同时提交多套订单参数进行试探。

外部浏览器应使用手机网站支付

如果页面可能通过短信、普通二维码、搜索结果或其他 App 打开,应准备手机网站支付方案。服务端调用 alipay.trade.wap.pay 生成支付表单或跳转内容,再引导用户前往支付宝完成付款。

典型流程如下:

外部浏览器 H5
    → 服务端调用 alipay.trade.wap.pay
    → 跳转支付宝收银台
    → 支付宝同步返回页面
    → 支付宝异步通知服务端

这种方式不依赖页面中的 AlipayJSBridge,更适合通用 H5 场景。

可以根据运行环境分流:

function isAlipayClient() {
  return /AlipayClient/i.test(navigator.userAgent);
}

if (isAlipayClient()) {
  // 尝试支付宝客户端内的授权和 JSBridge 支付流程。
} else {
  // 请求服务端生成手机网站支付跳转地址。
}

User-Agent 只能用于界面分流,不能作为安全校验,也不能替代支付产品的准入和来源校验。

支付结果必须由服务端确认

前端回调中的成功状态,只能说明客户端支付流程返回了成功结果,不能直接作为发货、充值或核销的依据。

服务端至少应完成以下处理:

  • 接收支付宝异步通知;
  • 验证通知签名;
  • 校验 app_id、商户身份、订单号、金额和币种;
  • 判断交易状态;
  • 使用幂等逻辑更新业务订单;
  • 必要时主动查询支付宝订单状态。

前端支付完成后,可以跳转到结果页,轮询业务订单状态:

async function waitForPayment(orderId) {
  for (let i = 0; i < 10; i += 1) {
    const response = await fetch(
      `/api/pay/status?order_id=${encodeURIComponent(orderId)}`
    );
    const result = await response.json();

    if (result.status === 'PAID') {
      return true;
    }

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

  return false;
}

排查“唤起方式不正确”

可以按以下顺序检查:

  1. 确认调用的是网页环境中的 AlipayJSBridge.call('tradePay', ...),不是小程序的 my.tradePay。
  2. 确认页面由支付宝客户端扫码进入,而不是将 URL 复制到普通浏览器中打开。
  3. 在真机页面中确认 window.AlipayJSBridge 存在,并等待 AlipayJSBridgeReady。
  4. 确认交易由服务端通过当前应用创建,返回的支付宝交易号有效。
  5. 核对创建订单和前端唤起支付时使用的 app_id、商户账号和签约产品。
  6. 检查授权回调域名、支付页面域名和应用配置。
  7. 查看服务端接口响应中的错误码、sub_code 和 sub_msg,不要只看前端提示。
  8. 如果配置和代码都没有异常,向支付宝技术支持提供应用 ID、接口名、调用时间、支付宝交易号和完整错误信息,请其确认该产品是否允许从当前入口调用。

聚合支付服务商能够实现这类流程,通常是因为已经完成了支付产品签约、应用配置、商户进件和域名配置,并通过支付宝认可的客户端入口发起调用。只复制其前端调用代码,无法绕过这些前置条件。

备注:内容仅供参考。