代码 微信支付 Native 扫码收款后端接入:code_url 拿回去别直接当已支付,五个坑一次讲清

2026-09-07 09:03:23

微信支付 Native 扫码收款:后端接入时最容易踩的五个坑

先说适用边界。Native 支付适合 PC 网站、商家收银台这类场景:用户用手机上的微信扫电脑屏幕上的二维码完成付款。Native 不适用于「需要在微信内拉起收银台」的场景——那种需要 openid、走 JSAPI。Native 扫码不需要 openid,用户拿任意微信 App 扫码即可,服务端也不需要额外获取用户身份。

官方文档:

统一下单与 code_url

服务端以 trade_type=NATIVE 调用:

POST https://api.mch.weixin.qq.com/v3/pay/transactions/native

请求体核心字段:

字段说明
appid商户 AppID
mchid商户号
description商品描述
out_trade_no商户业务订单号
notify_url支付结果回调地址
amount.total金额,单位:分
time_expire订单失效时间
attach商户自定义数据,回调时会原样带回来

响应返回的关键参数是 code_url,形如:

weixin://wxpay/bizpayurl?pr=xxxxxxxx

这个值不是直接给前端用的,需要服务端/前端自己把它转成二维码图片展示给用户。

两个时效注意点:

  • code_url 有效期 2 小时
  • 同一订单号下单选用的 prepay_id 也是 2 小时有效。

下单幂等:别每次刷新都生成新订单号

微信支付侧对「同一 out_trade_no」在有效期内是幂等的。也就是说,同一笔业务订单在 2 小时内重复调统一下单,返回的 code_url 是同一个。

如果你的页面一刷新就重新生成业务订单号再去下单,会产生大量待支付脏单。正确做法:

  • 业务订单创建后,out_trade_no 固定不变;
  • 二维码超时/页面刷新时直接用同一个 out_trade_no 重新调统一下单,拿到的是同一个 code_url

只有确认该笔订单已关单或已支付,才需要换新订单号。

扫码后“已支付”的判断来源

用户扫码支付是一个完整的交互过程。微信 没有 给网页 JS 提供可靠的“用户支付成功”即时信号。真正可信的支付结果只有两个来源:

  1. 服务端收到的支付结果回调 notify_url
  2. 服务端主动调查单接口确认。

先看查单接口与状态。

查单:

GET /v3/pay/transactions/out-trade-no/{out_trade_no}

trade_state 取值:

状态含义
SUCCESS支付成功
REFUND转入退款
NOTPAY未支付
CLOSED已关闭
REVOKED已撤销(仅付款码支付)
USERPAYING用户支付中,正在确认扣款
PAYERROR支付失败(其他原因)

需要特别留意 USERPAYING:它表示用户已经点了确认/正在输入密码或生物识别,但微信还没完成扣款。此时业务上 不能 当作支付成功。

推荐做法:

  1. 用户在下单成功那一刻,把订单写入轮询任务;
  2. 前端约每秒轮询一次自己的服务端订单状态接口;
  3. 服务端订单状态接口在本地订单状态非终态时,主动调微信查单接口兜底,把微信侧的 trade_state 与本地订单状态对齐;
  4. 当微信侧连续多次返回 SUCCESS(或收到回调并验签通过)后,本地订单置为已支付,此后前端再轮询即返回“已支付”。

关键一点:前端轮询和后端兜底查单,最终都要汇到 同一个幂等入口。不要在客户端依据任何微信支付后的前端返回值(Web 场景下也基本拿不到)直接置“已支付”。服务端入口统一处理后,前端只消费自己服务端的状态。

未支付订单的超时与关单

code_url 本身 2 小时失效。当业务订单超时(例如你设置的 time_expire 早于 code_url 失效时间),需要做两步:

  1. 先调查单接口确认 trade_state == NOTPAY
  2. 再调关单接口。

关单接口:

POST /v3/pay/transactions/out-trade-no/{out_trade_no}/close

边界条件:

  • 只能对 未支付成功 的订单调关单;
  • 订单已支付成功或已关闭的,调关单会报错(如 ORDER_CLOSED);
  • 如果你给订单设置了 time_expire,到达该时间后微信侧会自动关单,此时可以不再手动调关单;只有需要提前关闭时才需要主动调。

所以代码写到位的话,关单前必须查单,确认是 NOTPAY 才继续。

接入与排障清单

  • Native 场景下单前确认:商品页是 PC/线下扫码,用户用手机微信扫;不需要 openid;不要误走 JSAPI。
  • 统一下单请求体里 amount.total 单位是 ,不是元。
  • 下单响应只取 code_url,自己生成二维码图片后展示给用户。
  • 业务层缓存同一 out_trade_nocode_url,重复下单要拿到相同链接。
  • 后端配置好 notify_url,回调里做签名验证 + 金额核对(回调里的 amount 要与本地订单一致)。
  • 前端轮询频率别太高,建议 1 秒一次;服务端查单兜底频率建议降低(例如每 5~10 秒一次),避免触发微信接口频率限制。
  • 本地订单状态机至少要覆盖:待支付、支付中、已支付、已关闭、已退款。
  • 收到 USERPAYING 时继续等待,不要置为已支付。
  • 关单前先查单,确认 NOTPAY 再调关单接口。

推荐文章

程序员茄子在线接单