微信小程序下单金额被改成 0.01:我用服务端价格快照、优惠复核和签名校验堵住篡改
一次 0.01 元订单提醒我:前端传来的金额不能信。后来我把下单金额计算收回 Node.js 服务端,用价格快照、优惠复核和支付前签名校验,把篡改挡在支付之前。

作者
ZHOUYI
晚上在 Nuxt 写的后台里复盘订单时,我看到一笔原价 68 元的商品只付了 0.01 元。最开始以为是运营配错券,查日志才发现:下单接口把 amount 当成普通参数传给后端,请求体被工具改掉后,服务端没有复核就进入了支付流程。这类问题一旦放开,薅羊毛和异常客诉都会跟着来。
第一步:不再相信前端金额
改造很简单:小程序只提交 skuId、数量、优惠码和用户身份,不传最终金额。Node.js 服务根据 MongoDB 里的商品价、活动规则和用户券重新算价。算完后,订单先落一个待支付状态,同时把金额、优惠明细、过期时间写成价格快照。前台展示可以错,但支付依据必须以服务端为准。
第二步:用 Redis 快照和签名锁住支付
- 下单成功后生成 orderId,把金额快照写入 Redis,设置 15 分钟过期。
- 支付统一下单前,服务端重新读取快照,校验金额、优惠是否仍有效。
- 把 orderId、金额、用户 ID 做 HMAC 签名,前端只带签名发起支付。
- 支付回调到达时,再比对网关金额和快照金额,不一致就拦截并告警。
const key = `pay:${orderId}`;
const snapshot = { amount, couponId, exp: Date.now() + 900000 };
await redis.set(key, JSON.stringify(snapshot), 'PX', 900000);
const sign = hmac(`${orderId}:${amount}:${userId}`, SECRET);
return { orderId, amount, sign };第三步:优惠复核比金额校验更关键
真正容易翻车的是优惠。比如用户下单时券还有效,支付前券过期了;或者同一张券并发使用,导致金额被重复扣减。我会在支付前二次查券:状态必须是未使用,归属用户匹配,且订单金额满足门槛。券核销用 Redis 原子占位,支付成功后再写回 MongoDB,失败超时则释放。这样即使前端被改,服务端也能把低价单挡在支付前。
“客户端只负责展示和发起,服务端才是账房先生。”
上线后,我没有再遇到这种 0.01 元异常单。回头看,最有效的不是复杂风控,而是把关键数据的计算权和校验权收回来。对做微信小程序支付的小团队来说,少信任一点前端,多留一份快照和日志,真的能省掉很多半夜排查。