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 支付,以下工作都应由服务端完成:
- 创建商户订单。
- 校验订单金额和商品信息。
- 调用支付宝接口并生成签名。
- 接收支付宝异步通知。
- 验签并更新订单状态。
前端只负责请求服务端创建支付,并打开服务端返回的支付页面。
普通浏览器中的推荐流程
通常可以按以下流程接入:
- H5 页面调用业务服务端的“创建支付”接口。
- 服务端生成唯一的商户订单号并保存订单。
- 服务端调用支付宝手机网站支付接口,例如
alipay.trade.wap.pay。 - 服务端将支付宝 SDK 生成的 HTML 表单返回给浏览器。
- 浏览器提交表单并进入支付宝收银台。
- 支付宝通过
notify_url将支付结果异步通知给服务端。 - 服务端验签,核对订单号和金额,再更新订单状态。
- 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。
备注:内容仅供参考。
