订单链路全貌
支付是小程序里最不能出错的一块。用户下了单、付了钱,钱从微信走到商户,之后还要退款、对账。整条链路哪一步断了,轻则用户投诉,重则对不上账。
把整条链路想清楚再动手:选规格下单 → 后端生成订单 → 前端调起微信支付 → 支付平台异步回调后端 → 后端更新订单状态 → 用户看到支付成功。这一串是主干,每一步都要有失败兜底。
最怕的是那种「用户说付了钱,你这边订单却还是待支付」的情况。多半是回调没收到或者没处理成功。所以除了靠回调更新状态,前端在支付面板关闭后也应该主动向后端查一次这笔单的最新状态,拿后端结果为准刷新页面,别让用户对着一个错误的加载圈干等。
- 下单库存和价格在后端定。
- 支付前端调起、后端回调。
- 退款原路退回、状态流转。
- 对账每日核对钱和单。
下单:库存与价格
价格一定在后端算,不能信前端传过来的金额。前端传商品 ID 和数量,后端查价、扣库存、生成订单,再把订单号和应付金额返回给前端。
| 步骤 | 谁做 | 说明 |
|---|---|---|
| 算价 | 后端 | 不信前端金额 |
| 扣库存 | 后端 | 下单即预扣 |
| 生成订单号 | 后端 | 唯一、可追溯 |
库存要做预扣和超时释放:下单占上库存,几十分钟没付就自动放回去,不然热门商品会被占光。
支付:调起与回调
前端拿到支付参数后调起微信支付。但前端的「支付成功」不能当真,真正的成功依据是支付平台异步通知后端的回调。后端收到回调,校验签名、更新订单为已支付。
wx.requestPayment({
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package,
signType: res.signType,
paySign: res.paySign
});
前端只是把支付面板弹给用户,订单状态以后端回调为准。回调还可能重复来,后端要做幂等,同一条通知处理两次也没事。
还有一种情况要处理:用户在前端看到支付失败,或者干脆关了支付面板。这时候别让订单一直挂着,给订单设个有效期,超时没回调成功就自动关掉、把预扣的库存放回去。这样既不占库存,也不会有一笔永远悬浮的待支付单。
退款:原路退回
用户要退款,后端调微信的退款接口,钱原路退回到用户支付的账户。退款也有异步回调,后端收到后把订单状态改成已退款。
退款不是秒到账,一般要等一会儿才到用户微信里。所以发起退款后,页面上别跟用户说「已经退到账」,而是说「退款已受理,稍后到账」。等退款回调真的回来,再把订单标成已退款。这样预期管理清楚,用户不会反复来催问。
- 退款金额不能超过原支付额。
- 退款要留申请记录,谁退的、为什么。
- 退款同样做幂等,别退两次。
对账:钱不能错
每天拉一份支付平台的账单,和自己库里的订单逐笔核对。对得上的就算了,对不上的要查清楚:是漏回调、重复回调,还是哪里算错了。
支付这块,宁可多核对一遍,也别让钱和单对不上。
对账不是跑一次就完事。当天的账当天对,差异要尽快定位:是支付平台回调晚到了,还是某笔单状态没更新。把常见的差异原因列成清单,遇到能照着查,比每次从零想快得多。
另外,订单状态本身要有个清晰的状态机:待支付、已支付、已发货、已完成、已退款、已关闭。每笔单在哪个状态一目了然,别用一堆布尔标志东拼西凑。状态机画清楚,退款和超时关闭这些逻辑才不会打架。
常见支付坑
| 坑 | 后果 | 应对 |
|---|---|---|
| 信前端金额 | 被改价刷钱 | 后端算价 |
| 没做幂等 | 重复回调重复加钱 | 回调幂等 |
| 库存不释放 | 下单不付占光库存 | 超时释放 |
订单流程清单
- 价格库存一律后端定。
- 支付以前端调起、后端回调为准。
- 退款原路退,留好申请记录。
- 每天对账,差异当天查。
- 所有回调做幂等。
钱上的事,慢一点也要做对。