发起支付

创建支付单,拿到支付页地址:

Node.js
Python
const { orderId, payUrl, expireAt } = await pay.orders.create({
  outTradeNo: 'R202608180001',
  amount: '100.00',
  subject: '积分充值100元',
  payMode: 'qrcode',
  attach: 'uid=42',
})

参数

参数必填说明
out_trade_no商户订单号,1-64 位字母、数字、下划线,商户侧唯一
amount金额,单位元,字符串,最多两位小数。以服务端价格为准,不要使用前端传入的金额
subject订单标题,不能包含 / = &
channel支付渠道:alipay(默认)支付宝;chinaums 银商,形态由 pay_mode 决定,用法见下
pay_modeqrcode 二维码收款(PC 场景);redirect 整页跳转(PC 跳支付宝收银台 / 微信等 App 内唤起支付)
qrcode_width二维码宽度(像素),默认 200。仅 alipay 生效
return_urlredirect 模式支付完成后的回跳地址(http/https,≤255 字符)。仅作页面回跳展示,入账一律以回调/查单为准
expire_minutes超时未支付自动关单,默认 15,范围 2 到 1440
attach附加数据,最长 128 字符,回调原样返回。常用于携带内部用户 ID

返回:

字段说明
order_id顽兔支付单号
out_trade_no原样返回商户订单号
channel下单时选择的渠道,决定 pay_url 的用法
pay_url支付页地址或二维码内容,按渠道用法见下
expire_at过期时间,北京时间 yyyy-MM-dd HH:mm:ss

Node SDK 的入参出参是 camelCase(如 outTradeNo),Python 与 HTTP 报文一致用 snake_case。

支付页形态(按渠道)

支付宝 qrcode:页面内嵌二维码。 适合充值页,用户不离开商户网站,手机支付宝扫码支付:

<iframe src="这里放 pay_url" width="240" height="240" frameborder="0"></iframe>

支付宝 redirect:跳转收银台。 服务端 302 或前端 location.href 跳转到 pay_url, 用户在支付宝收银台扫码或登录支付。

银商聚合码(channel: "chinaums" + pay_mode: "qrcode"):自己渲染二维码。 这个形态的 pay_url二维码的内容(银商账单地址),不是可打开的网页——不能放 iframe,普通浏览器也打不开(只能在微信、支付宝、云闪付内打开),前端用任意二维码库 把它渲染成码图即可:

<div id="payArea"></div>
<!-- 演示图省事走 CDN,生产建议把二维码库打进自己的前端构建 -->
<script src="https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
  new QRCode(document.getElementById('payArea'), { text: order.pay_url, width: 220, height: 220 })
</script>

用户拿微信、支付宝、云闪付任意一个 App 扫码,进银商收银台付款,用哪个钱包由用户 扫码的 App 决定。回调、查单、退款与支付宝渠道完全一致,不需要任何额外处理。

公众号支付(微信内打开的页面)

用户在微信里打开你的 H5 页面时(公众号菜单、聊天里分享的链接都算),页面里 展示二维码是没法付的——同一台手机扫不了自己屏幕上的码。这个场景用 channel: "chinaums" + pay_mode: "redirect":下单拿到的 pay_url 是银商收银台 的跳转地址,把用户整页跳过去,收银台自动唤起微信支付弹层,付完原路回跳。

你不需要有自己的微信公众号,也不需要接微信 OAuth 拿 openid——这些由银商收银台 用它自己的公众号主体完成,你只管跳转。同一个 pay_url 在支付宝、云闪付 App 里 打开也能付,收银台按环境自动适配。

Node.js
Python
const { payUrl } = await pay.orders.create({
  outTradeNo: 'R202608290001',
  amount: '100.00',
  subject: '积分充值100元',
  channel: 'chinaums',
  payMode: 'redirect',
  returnUrl: 'https://你的域名/recharge/result?out_trade_no=R202608290001',
  attach: 'uid=42',
})
// 把 payUrl 返回给前端,前端 location.href = payUrl

前端在充值页按环境分流即可(微信内跳转、PC 出码),一个判断搞定:

const inWallet = /MicroMessenger|AlipayClient|UnionPay/i.test(navigator.userAgent)
if (inWallet) {
  // 微信/支付宝/云闪付内:服务端按 pay_mode=redirect 下单后整页跳转
  location.href = order.pay_url
} else {
  // PC 或普通手机浏览器:按 pay_mode=qrcode 下单,渲染二维码给手机扫
}

几个要点:

  • 拿到 pay_url 后立即跳转——链接带时效签名,别缓存起来慢慢用。同单号重复下单 (幂等)返回的是同一条链接;用户中途退出再点支付,直接再跳原链接即可,若已临近 expire_at 或跳转被银商拒绝,换新单号重新下单
  • return_url 的回跳页只做展示:页面加载后轮询你自己的订单状态接口确认到账。 微信渠道的回跳受微信「点金计划」策略影响可能不自动发生,用户手动返回你的页面时 轮询同样能兜住,所以回跳页和充值页都要能查单
  • 入账只认 webhook 回调(兜底查单),和其他形态完全一致
Tip

银商渠道需要顽兔侧为你的商户号开通后才可用(未开通时下单返回 50002);聚合码与 公众号支付在银商侧是两个产品,需要分别开通。是否已开通、何时开通,问顽兔运营即可; 代码侧只是下单参数的差别。

前端配合

扫码支付完成时,PC 页面不会收到任何事件。前端做两件事:

  1. 展示二维码后,轮询商户自己的订单状态接口(后端状态由回调驱动),到账后刷新页面
  2. 订单过期后引导用户重新下单。支付宝内嵌二维码约 2 分钟自动刷新,无需处理; 银商聚合码在 expire_at 前一直有效

redirect 模式(含公众号支付)同理:回跳不可靠也不可信,结果页一律轮询自己的 订单状态接口,以后端(回调驱动)的状态为准。

重复下单

同一 out_trade_no

  • 参数完全相同,返回原单,不会重复创建
  • 参数不同,返回错误码 40901,需要更换订单号

网络超时后用原参数重试是安全的。