收款
中台持有商户密钥,站点只下单与收货。两种接法,推荐第一种。
方式 A:托管收银台(不占你的支付授权目录名额)
# 1) 站点下单
POST https://wx.onzx.cn/api/v1/pay/order
{ "out_trade_no": "VIP-20261015-0001",
"amount_total": 50000,
"description": "心理咨询 · 初诊 60 分钟",
"scene": "cashier",
"return_url": "https://vip.onzx.cn/me",
"attach": { "appointment_id": 3391 },
"channel": "yzy" } # 可选,留空用默认通道
# 2) 返回
{ "ok": true, "data": {
"cashier_url": "https://wx.onzx.cn/cashier/1f9c…",
"order": { "out_trade_no": "…", "status": "pending", "amount_total": 50000 },
"expire_at": 1760000900 } }
# 3) 站点把用户跳到 cashier_url。微信内会静默授权拿 openid 并调起支付;
# PC 上自动显示微信扫码页。付完自动回 return_url。
商户平台只需要一条支付授权目录:
https://wx.onzx.cn/cashier/。
微信对"顶级域名目录"只校验 host、不校验后缀,所以这一条覆盖中台所有支付页面,
5 条名额用掉 1 条,以后再接多少个站点都不用回商户平台改配置。
方式 B:站点自己调起(需要额外名额)
{ …, "scene": "jsapi", "openid": "oXXXX…" }
→ data.jsapi_params = { appId, timeStamp, nonceStr, package, signType:"RSA", paySign }
站点页面里执行:
WeixinJSBridge.invoke('getBrandWCPayRequest', jsapi_params, function (res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') { /* 只是体验,最终以服务端通知为准 */ }
});
这时"发起支付的页面 host"必须是支付授权目录之一,请在商户平台为该站点另加一条(形如
https://vip.onzx.cn/,必须以 / 结尾)。已有 5 条上限,最多再接 4 个站点。
场景与前置条件
| scene | 用在哪 | 前置 |
|---|---|---|
| cashier | 默认,中台托管 | 入口域名可 https 访问;openid 由收银台静默授权取得 |
| jsapi | 站点页面内直接调起 | 该 AppID 已关联商户号;页面 host 已配支付授权目录;openid 属于该 AppID |
| native | 外部浏览器/PC 出二维码 | 商户号已开通 Native 支付 |
| h5 | 微信外手机浏览器 | 需在产品中心单独申请 H5 支付,并传真实终端 IP |
| miniprogram | 小程序内 | 小程序 AppID 已关联商户号;用小程序的 openid |
回调:站点必须做的三件事
POST 你的 webhook_url(中台用「回调密钥」签名,算法同 /docs/sign)
{ "event": "pay.success", "site_key": "vip", "sent_at": 1760000950, "callback_id": 88,
"data": { "out_trade_no": "VIP-…", "transaction_id": "4200…", "status": "paid",
"amount_total": 50000, "amount_paid": 50000, "openid": "oX…",
"attach": { "appointment_id": 3391 }, "paid_at": 1760000940 } }
- 验签:用回调密钥(不是接入密钥)。
- 幂等:按
event + out_trade_no去重。微信会重复通知,中台也会补发。 - 快速返回 2xx:先落库再处理业务。返回 3xx/4xx/超时都算失败,会按 15s/60s/5m/30m/2h 重试,超过次数进死信。
没配 webhook 也能用:站点可以主动轮询 POST /api/v1/pay/query { "out_trade_no": "…", "refresh": "1" },
其中 refresh=1 会让中台向微信查一次单并以微信结果为准修正状态。
其余接口
| 接口 | 入参 | 说明 |
|---|---|---|
| /pay/query | out_trade_no[, refresh] | 查状态;refresh=1 触发向微信查单 |
| /pay/close | out_trade_no | 关单。仅 pending 可关;已支付会报 ORDERPAID 并自动转查单入账 |
| /pay/refund | out_trade_no, out_refund_no, amount_refund[, reason] | 退款走原路,结果同样通过回调通知 |
状态机(中台不允许非法跳转)
pending → paid | closed | failed
paid → refunding | refunded | partial
partial → refunded
refunding → refunded | partial
closed / refunded → 终局
实付金额 != 应付金额时中台不会入账,只写一条
amount_mismatch 事件等人工核对。
这类情况几乎都来自"订单号复用"或"改价重试",所以同单号改金额会被直接拒绝(order_conflict)。
合规红线
- 不能替别的法人收款(二清)。不同经营主体必须用各自的商户号,或走服务商 + 特约商户模式。
- 商品描述要与实际一致、明码标价;单次全款预付,退款要有可达的客服入口。
- 收银台页不做缓存、不在 URL 里暴露金额以外的个人信息;交易与回调日志默认保留 30 天(可调)。
本页内容随代码一起版本化(V0.1.0)。发现文档与实际行为不一致,请当缺陷报告。