退款回调慢半拍之后,我把微信小程序退款改成了主动查询加对账
微信小程序退款回调并不总是准时,甚至可能漏。这里记录我用 Node.js 把退款流程改成状态机、主动查询、Redis 锁和对账兜底的过程,解决退款成功但业务没回滚的问题。

作者
ZHOUYI
前段时间小程序里出现一个很烦的 BUG:用户发起退款后,微信侧已经成功了,但我们订单还显示退款处理中,会员权益也没有恢复。刚开始我以为是回调没收到,后来发现回调只是慢,有时还会重复。支付回调如果当成唯一事实来源,业务状态很容易被动。
问题不是没回调,而是状态不同步
原来的逻辑很简单:发起退款后等回调,回调成功再更新订单。问题在于退款链路里有商户系统、微信支付、网络重试和异步队列,任何一环延迟都会让页面状态慢半拍。更麻烦的是,退款成功但没有回滚库存、权益或广告转化归因,会造成客服解释成本和数据偏差。
我改成状态机、主动查询和 Redis 锁
- 退款单落库时只允许 pending、processing、success、failed、closed 几个状态。
- 发起退款后记录 refund_id、order_id 和最近一次查询时间。
- Node.js 定时任务扫描超过 2 分钟仍不是终态的退款单。
- 调用微信查退款接口,以查询结果推进状态机,而不是只等回调。
- 回调和查询都走同一个更新入口,用条件更新避免重复回滚。
const locked = await redis.set(`refund:lock:${refundId}`, '1', 'EX', 30, 'NX');
if (!locked) return;
const res = await wxPay.queryRefund(refundId);
if (res.status === 'SUCCESS') {
const updated = await db.refunds.updateOne(
{ _id: refundId, status: { $ne: 'success' } },
{ $set: { status: 'success', updatedAt: new Date() } }
);
if (updated.matchedCount) await rollbackOrder(orderId);
}这里我踩过的坑是:查到 SUCCESS 后不能直接回滚,要先用 status: { $ne: 'success' } 做条件更新。否则回调和定时任务同时到达,可能重复恢复权益。Redis 锁只是降低并发压力,最终一致性仍要靠数据库状态约束。
对账兜底和异常告警
每天凌晨我会再跑一次对账任务,把最近一段时间的微信退款结果和本地 MongoDB 状态比对。差异单进入待处理列表,并通过邮件或企业微信告警。对账结果也会展示在 Nuxt 3 写的后台页里,方便观察是否影响订单、流量转化和广告结算。
“回调只能当通知,不能当事实来源;主动查询、状态机和对账,才是支付链路的兜底。”
改完后,退款状态延迟的问题明显少了,最重要的是业务不再依赖某一次回调是否准时。对支付类需求,我现在都会先问一句:如果回调丢了或慢了,系统还能不能自己把状态拉正。