Node.js 网页如何动态展示支付宝返回的支付表单
支付老李
结论
支付宝返回的支付表单通常是完整或可嵌入的 HTML。Node.js 服务端收到后,应以 text/html 类型返回给浏览器,不能把它当作普通字符串展示。浏览器解析并提交表单后,会将用户带到支付宝收银台继续支付。
比较稳妥的做法是由支付接口直接返回包含该表单的 HTML 页面。
服务端直接返回支付表单
以 Express 为例。假设支付宝 SDK 返回的 formHtml 包含 <form>,其中可能还有自动提交脚本:
import express from 'express';
const app = express();
app.post('/pay/alipay', async (req, res, next) => {
try {
// 此处仅作示意,具体调用方式取决于使用的支付宝 SDK
const formHtml = await createAlipayPaymentForm({
outTradeNo: 'ORDER_202609090001',
totalAmount: '99.00',
subject: '订单支付'
});
res.status(200);
res.type('html');
res.send(`<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>正在跳转到支付宝</title>
</head>
<body>
<p>正在跳转到支付宝收银台,请稍候……</p>
${formHtml}
</body>
</html>`);
} catch (error) {
next(error);
}
});
如果 SDK 返回的内容已经是完整的 HTML 文档,可以直接发送:
res.status(200).type('html').send(formHtml);
响应类型必须设置正确。如果使用 res.json(),支付表单会变成 JSON 字符串,浏览器不会将其解析为表单,也不会提交。
前端如何发起支付
建议通过页面跳转或原生表单请求支付接口,不要使用 fetch() 或 AJAX:
<form method="post" action="/pay/alipay">
<input type="hidden" name="orderId" value="ORDER_202609090001">
<button type="submit">支付宝支付</button>
</form>
用户点击按钮后,浏览器会请求 Node.js 支付接口。接口生成支付宝表单并返回 HTML,随后浏览器执行表单中的自动提交逻辑。
如果接口支持 GET 请求,也可以直接跳转到支付地址:
window.location.href = '/pay/alipay?orderId=ORDER_202609090001';
不过,创建支付请求通常涉及订单状态处理。选择 GET 还是 POST,应以业务接口的设计为准。
已经通过 AJAX 获取表单时
如果前端已经拿到了表单 HTML 字符串,可以先把表单插入页面,再主动提交:
<div id="alipay-form-container"></div>
<script>
async function startPayment(orderId) {
const response = await fetch('/api/pay/alipay', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId })
});
if (!response.ok) {
throw new Error('创建支付宝支付请求失败');
}
const formHtml = await response.text();
const container = document.getElementById('alipay-form-container');
container.innerHTML = formHtml;
const form = container.querySelector('form');
if (!form) {
throw new Error('支付宝返回内容中未找到支付表单');
}
form.submit();
}
</script>
通过 innerHTML 插入的 <script> 通常不会自动执行,所以不要依赖返回内容中的自动提交脚本。插入表单后主动调用 form.submit() 会更可靠。
如果业务不要求必须使用 AJAX,仍然建议让服务端直接返回 HTML。这样流程更简单,也较少受到浏览器安全策略的影响。
注意事项
- 支付金额、订单号和商品名称应由服务端根据订单数据生成,不能直接信任浏览器提交的金额。
- 只有确认表单来自支付宝官方 SDK 或受严格控制的服务端代码时,才能将其作为 HTML 输出。不要把用户输入直接拼入 HTML,否则可能引发 XSS 漏洞。
- 不要转义整个支付表单,否则
<form>会显示成普通文本,无法提交。 - 创建支付订单前,应校验用户的登录状态、订单归属和订单当前状态,同时避免重复创建或支付同一订单。
- 支付完成后的订单状态应以支付宝服务端异步通知为准,不能仅凭浏览器同步跳转的结果判断支付成功。
- 处理异步通知时,必须按照支付宝接口要求验签,并核对商户身份、订单号、金额和支付状态。
- 如果站点配置了严格的 Content Security Policy,需要通过合适的
form-action规则允许向实际的支付宝支付网关域名提交表单。 - 不建议把支付宝收银台嵌入
iframe。支付页面可能禁止嵌入,登录、页面跳转和浏览器安全策略也可能受到影响。
备注:内容仅供参考。
