订单链路全貌

支付是小程序里最不能出错的一块。用户下了单、付了钱,钱从微信走到商户,之后还要退款、对账。整条链路哪一步断了,轻则用户投诉,重则对不上账。

把整条链路想清楚再动手:选规格下单 → 后端生成订单 → 前端调起微信支付 → 支付平台异步回调后端 → 后端更新订单状态 → 用户看到支付成功。这一串是主干,每一步都要有失败兜底。

最怕的是那种「用户说付了钱,你这边订单却还是待支付」的情况。多半是回调没收到或者没处理成功。所以除了靠回调更新状态,前端在支付面板关闭后也应该主动向后端查一次这笔单的最新状态,拿后端结果为准刷新页面,别让用户对着一个错误的加载圈干等。

本文核心知识点
  • 下单库存和价格在后端定。
  • 支付前端调起、后端回调。
  • 退款原路退回、状态流转。
  • 对账每日核对钱和单。
小程序支付订单示意:蓝调渐变背景上循环流转的订单节点与连接箭头的扁平科技插画
钱和单要一一对上,每个节点都留好失败兜底。

下单:库存与价格

价格一定在后端算,不能信前端传过来的金额。前端传商品 ID 和数量,后端查价、扣库存、生成订单,再把订单号和应付金额返回给前端。

步骤谁做说明
算价后端不信前端金额
扣库存后端下单即预扣
生成订单号后端唯一、可追溯

库存要做预扣和超时释放:下单占上库存,几十分钟没付就自动放回去,不然热门商品会被占光。

支付:调起与回调

前端拿到支付参数后调起微信支付。但前端的「支付成功」不能当真,真正的成功依据是支付平台异步通知后端的回调。后端收到回调,校验签名、更新订单为已支付。

wx.requestPayment({
  timeStamp: res.timeStamp,
  nonceStr: res.nonceStr,
  package: res.package,
  signType: res.signType,
  paySign: res.paySign
});

前端只是把支付面板弹给用户,订单状态以后端回调为准。回调还可能重复来,后端要做幂等,同一条通知处理两次也没事。

还有一种情况要处理:用户在前端看到支付失败,或者干脆关了支付面板。这时候别让订单一直挂着,给订单设个有效期,超时没回调成功就自动关掉、把预扣的库存放回去。这样既不占库存,也不会有一笔永远悬浮的待支付单。

退款:原路退回

用户要退款,后端调微信的退款接口,钱原路退回到用户支付的账户。退款也有异步回调,后端收到后把订单状态改成已退款。

退款不是秒到账,一般要等一会儿才到用户微信里。所以发起退款后,页面上别跟用户说「已经退到账」,而是说「退款已受理,稍后到账」。等退款回调真的回来,再把订单标成已退款。这样预期管理清楚,用户不会反复来催问。

  • 退款金额不能超过原支付额。
  • 退款要留申请记录,谁退的、为什么。
  • 退款同样做幂等,别退两次。

对账:钱不能错

每天拉一份支付平台的账单,和自己库里的订单逐笔核对。对得上的就算了,对不上的要查清楚:是漏回调、重复回调,还是哪里算错了。

支付这块,宁可多核对一遍,也别让钱和单对不上。

对账不是跑一次就完事。当天的账当天对,差异要尽快定位:是支付平台回调晚到了,还是某笔单状态没更新。把常见的差异原因列成清单,遇到能照着查,比每次从零想快得多。

另外,订单状态本身要有个清晰的状态机:待支付、已支付、已发货、已完成、已退款、已关闭。每笔单在哪个状态一目了然,别用一堆布尔标志东拼西凑。状态机画清楚,退款和超时关闭这些逻辑才不会打架。