微信支付回调超时后重复生成订单怎么处理?
在集成微信H5支付时遇到个难题,用户支付成功后回调接口偶尔会超时,导致订单重复创建。
我尝试在支付回调接口里用setTimeout模拟网络延迟,发现当延迟超过微信规定的10秒超时时间后,微信会重新发起回调请求。以下是核心代码:
async function handleWxCallback(req, res) {
const { out_trade_no, total_fee } = req.query;
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 12000));
try {
// 验证签名和订单状态的逻辑
const order = await Order.findOne({ orderNo: out_trade_no });
if (!order) {
await createOrder(out_trade_no, total_fee);
}
res.send('<xml>...</xml>');
} catch(err) {
console.error(err);
res.status(500).end();
}
}
但测试时发现,当第一次请求超时后,第二次回调会再次创建新订单。想问下除了设置超时时间,有没有更好的幂等性校验方案?比如应该在哪个环节校验订单唯一性?
核心是做幂等性控制,得在创建订单前用数据库唯一索引锁住。具体步骤:
第一,给订单表的 out_trade_no 字段加唯一索引,这是底线。
第二,调整逻辑顺序:收到回调后先尝试插入订单,而不是先查再插。用 insert ignore 或 upsert 操作,靠数据库保证不重复。
比如 MySQL 可以这么写:
第三,处理插入结果。如果插入成功,说明这是第一次回调,正常走后续流程;如果主键冲突或影响行数为0,说明订单已存在,直接返回成功即可。
另外 setTimeout 模拟的12秒延迟必须干掉,线上接口不能超过10秒,建议把耗时操作比如发短信、发通知这些扔到消息队列里异步执行。
最后别忘了微信要求 return
总之就是一句话:靠数据库唯一索引+insert ignore防重复,业务逻辑后置,响应速度提上来。
核心思路是:用数据库的唯一索引+状态机来实现幂等,而不是等到业务逻辑走到创建订单那一步才去查。
你应该在进入回调的第一时间就检查这个
out_trade_no是否已经处理过。更进一步,建议你在调用统一下单 API 创建微信订单前,先在本地生成一个带唯一约束的订单号,并插入数据库,状态为“待支付”。这样即使回调延迟或重试,你也能通过唯一索引防止重复插入。具体操作步骤:
1. 在发起微信支付前,先在本地数据库插入一条订单记录,使用
out_trade_no作为唯一键(加唯一索引),状态设为“初始化”或“待支付”。2. 回调进来时,第一步就根据
out_trade_no查询订单是否存在。- 如果存在且已是“已支付”状态,直接返回成功响应,不再处理。
- 如果存在但还是“待支付”,则执行后续的更新订单、发货逻辑。
- 注意这里要加事务,避免并发请求同时进入判断。
3. 支付成功的回调处理完后,更新订单状态为“已支付”,并记录微信的交易流水号等信息。
4. 无论什么情况,只要收到回调,验证签名通过后都必须返回
给微信,否则它会持续重试。另外你现在用 setTimeout 模拟延迟测试是对的,但生产环境绝对不能有这种阻塞操作。所有耗时操作比如发短信、发通知这些,都应该丢到异步队列里去处理,不要卡在回调接口里。
总结下关键点:
- 唯一索引防重复下单
- 状态机控制流程推进
- 回调接口尽快返回,别干重活
- 幂等判断放在最前面
这才是稳的。