PAYATHON 2026

H5 网页如何调用 H5 或 Native 支付宝完成支付?

支付小周

明确结论

普通 H5 网页应使用支付宝手机网站支付。浏览器先请求业务服务端,由服务端创建订单、生成签名,再把支付宝支付页面的表单或跳转地址返回给浏览器。前端只需提交表单或跳转到相应地址。

H5 页面无法直接调用支付宝 Native SDK。Native 支付仅适用于 Android 和 iOS 应用,需要由原生代码接入支付宝 SDK。如果 H5 运行在自有 App 的 WebView 中,可以通过 JSBridge 通知原生层发起支付。

钉钉内的 H5 应用要根据实际环境选择支付方式:

  • 使用普通手机网站支付时,可以按 H5 支付流程跳转,但能否正常唤起支付宝,会受到钉钉 WebView、操作系统和支付宝客户端策略的影响。
  • 如果钉钉提供适用于当前场景的支付 JSAPI,应按对应的开放平台文档接入,同时确认应用类型、权限和客户端版本。
  • 不要自行拼接 alipays:// 等 URL Scheme 作为正式支付方案。这种方式的兼容性和可维护性都比较差。

为什么网页不能直接调用 Native 支付

支付宝 Native SDK 依赖 Android 或 iOS 原生运行环境。手机 Chrome 中的 JavaScript 无法直接访问原生 SDK,商户私钥和支付签名逻辑也不能放在前端。

无论使用 H5 支付还是 Native 支付,以下工作都应由服务端完成:

  1. 创建商户订单。
  2. 校验订单金额和商品信息。
  3. 调用支付宝接口并生成签名。
  4. 接收支付宝异步通知。
  5. 验签并更新订单状态。

前端只负责请求服务端创建支付,并打开服务端返回的支付页面。

普通浏览器中的推荐流程

通常可以按以下流程接入:

  1. H5 页面调用业务服务端的“创建支付”接口。
  2. 服务端生成唯一的商户订单号并保存订单。
  3. 服务端调用支付宝手机网站支付接口,例如 alipay.trade.wap.pay。
  4. 服务端将支付宝 SDK 生成的 HTML 表单返回给浏览器。
  5. 浏览器提交表单并进入支付宝收银台。
  6. 支付宝通过 notify_url 将支付结果异步通知给服务端。
  7. 服务端验签,核对订单号和金额,再更新订单状态。
  8. H5 页面主动查询业务服务端,显示最终支付结果。

从用户视角看,这个过程很像“访问一个 URL”,但该 URL 或表单不能由前端自行拼接,必须来自服务端生成并签名的支付请求。

前端示例

前端可以向业务服务端请求支付表单,再将表单写入一个临时页面。实际项目中,也可以让服务端直接返回完整的 HTML 页面,由浏览器跳转过去。

<button id="payButton">支付宝支付</button>

<script>
document.getElementById("payButton").addEventListener("click", async () => {
  const response = await fetch("/api/pay/alipay/wap", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    credentials: "include",
    body: JSON.stringify({
      orderId: "ORDER_202609090001"
    })
  });

  if (!response.ok) {
    throw new Error("创建支付请求失败");
  }

  const html = await response.text();

  const paymentWindow = window.open("", "_self");
  paymentWindow.document.open();
  paymentWindow.document.write(html);
  paymentWindow.document.close();
});
</script>

支付宝 SDK 返回的内容通常包含一个会自动提交的 HTML 表单。不要直接用 innerHTML 将它插入现有元素,因为以这种方式插入的 <script> 通常不会自动执行。直接返回完整页面,或者解析表单后主动调用 submit(),通常更可靠。

也可以使用普通表单,让浏览器直接访问服务端的支付入口:

<form action="/api/pay/alipay/wap" method="post">
  <input type="hidden" name="orderId" value="ORDER_202609090001">
  <button type="submit">支付宝支付</button>
</form>

服务端收到请求后创建支付宝支付表单,并将它作为 HTML 响应返回。浏览器随后自动提交表单,进入支付宝收银台。

服务端示例

下面的 Java 示例只保留了核心结构。网关地址、应用 ID、密钥和证书模式需要按商户的实际配置填写。

@PostMapping(
    value = "/api/pay/alipay/wap",
    produces = MediaType.TEXT_HTML_VALUE
)
@ResponseBody
public String createWapPayment(@RequestParam String orderId) throws Exception {
    Order order = orderService.getPayableOrder(orderId);

    if (order == null || !order.isPayable()) {
        throw new IllegalStateException("订单不存在或不可支付");
    }

    AlipayClient alipayClient = new DefaultAlipayClient(
        "https://openapi.alipay.com/gateway.do",
        alipayAppId,
        merchantPrivateKey,
        "json",
        "UTF-8",
        alipayPublicKey,
        "RSA2"
    );

    AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
    request.setNotifyUrl("https://example.com/api/pay/alipay/notify");
    request.setReturnUrl("https://example.com/pay/result?orderId=" + orderId);

    AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
    model.setOutTradeNo(order.getPaymentNo());
    model.setSubject(order.getSubject());
    model.setTotalAmount(order.getAmount().toPlainString());
    model.setProductCode("QUICK_WAP_WAY");

    request.setBizModel(model);

    AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);

    if (!response.isSuccess()) {
        throw new IllegalStateException("创建支付宝支付请求失败");
    }

    return response.getBody();
}

不同版本的 SDK 在类名、构造参数和证书配置方式上可能存在差异,请以项目实际使用的支付宝服务端 SDK 文档为准。

必须使用 Native 支付时

如果 H5 运行在自有 Android 或 iOS App 的 WebView 中,可以采用以下调用链:

H5 JavaScript
    → JSBridge
    → Android/iOS 原生代码
    → 支付宝 Native SDK
    → 原生代码接收结果
    → JSBridge 通知 H5

H5 侧的调用可能类似:

window.NativeBridge.payWithAlipay({
  orderId: "ORDER_202609090001"
});

这里的 NativeBridge 是 App 自行实现并注入 WebView 的接口,不是浏览器的标准 API。原生层应先向业务服务端请求已经签名的支付参数,再调用支付宝 SDK。商户私钥和签名逻辑都不能放在 JavaScript 或 App 客户端中。

手机 Chrome 没有这个 JSBridge,因此无法采用这种方案。

钉钉 H5 中如何选择

钉钉 H5 页面不等同于商户自己的 Native App,不能默认访问支付宝 Native SDK。

如果直接使用手机网站支付,建议先在真机上验证以下事项:

  • 钉钉内是否允许打开支付宝收银台。
  • 是否会跳转到系统浏览器或支付宝客户端。
  • 支付完成后能否返回钉钉。
  • iOS 和 Android 上的行为是否一致。
  • 当前应用类型是否支持钉钉提供的支付 JSAPI。

使用钉钉 JSAPI 时,需要先完成钉钉环境检测、鉴权和错误处理。具体 API 名称、适用范围和授权条件可能发生变化,请以当前钉钉开放平台文档为准,不要根据普通浏览器的行为推测钉钉内的处理方式。

可以先判断运行环境,但判断结果只能用于选择支付流程,不能代替服务端的安全校验:

const isDingTalk =
  /DingTalk/i.test(navigator.userAgent) ||
  typeof window.dd !== "undefined";

if (isDingTalk) {
  // 根据已开通的钉钉支付能力选择 JSAPI,
  // 或打开服务端生成的支付宝 H5 收银台。
} else {
  // 使用支付宝手机网站支付。
}

支付结果必须以服务端通知为准

return_url 只是用户支付后的返回页面地址。用户可能关闭页面,也可能返回失败,甚至伪造查询参数。因此,即使页面显示“支付成功”,也不能直接将订单标记为已支付。

服务端处理 notify_url 时,至少要完成以下检查:

  • 验证支付宝签名。
  • 检查 app_id、商户身份等信息。
  • 核对 out_trade_no 是否属于当前系统。
  • 核对支付金额和币种。
  • 判断交易状态是否符合业务要求。
  • 保证通知处理幂等,防止重复入账。
  • 验证成功后,按协议返回指定响应。

前端完成支付后,应查询自己的订单接口:

async function queryPaymentResult(orderId) {
  const response = await fetch(
    `/api/orders/${encodeURIComponent(orderId)}/payment-status`,
    {
      credentials: "include"
    }
  );

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

  return response.json();
}

注意事项

  • 商户私钥只能保存在服务端,不能写入 H5、App 安装包或公开仓库。
  • 订单金额必须由服务端根据订单数据计算,不能信任前端传入的金额。
  • 商户订单号必须唯一,用户重复点击支付时还要做好幂等处理。
  • notify_url 必须是支付宝服务器能够访问的 HTTPS 地址,不能使用 localhost。
  • 支付操作最好由用户点击触发,以免被浏览器的弹窗或跳转策略拦截。
  • 能否唤起支付宝客户端,不能作为支付成功的判断依据。
  • 钉钉、微信等内置 WebView 可能限制外部跳转,需要准备降级方案,例如提示用户在系统浏览器中打开,或者改用平台提供的正式支付能力。
  • 如果业务需要稳定的 Native 支付体验,应开发原生 App,并通过 JSBridge 与 H5 协作。普通网页则应优先使用 alipay.trade.wap.pay。

备注:内容仅供参考。