PAYATHON 2026

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。支付页面可能禁止嵌入,登录、页面跳转和浏览器安全策略也可能受到影响。

备注:内容仅供参考。