支付回调必须做幂等性校验,因为微信、支付宝在异常网络下会重复推送notify_url;需基于签名验证后的out_trade_no或trade_no与channel联合唯一索引实现原子写入,避免重复发货或扣库存。

支付回调为什么必须做幂等性校验
因为支付网关(如微信、支付宝)在异常网络下会重复推送 notify_url 回调,Django 后端若不做校验,可能重复发货、重复扣库存、重复生成订单记录——这不是“偶尔出错”,而是生产环境必然发生的场景。
核心判断依据是:支付平台回调中携带的唯一业务标识(如微信的 out_trade_no、支付宝的 out_trade_no 或 trade_no),不是请求 ID,也不是时间戳。
- 不要用
request.META.get('HTTP_X_FORWARDED_FOR')或request.path做去重依据 - 不要依赖客户端传来的随机
nonce字段(它不经过签名验证,可伪造) - 必须基于平台签名验证通过后解析出的
out_trade_no+ 业务状态(如trade_status=SUCCESS)做原子写入
Django 视图中如何安全处理微信支付回调
微信回调是 POST 请求,且 body 是 XML 格式,不是 JSON;同时要求你返回特定格式的 XML 响应(),否则它会持续重试。
关键动作顺序不能错:
- 用
request.body原始字节解析 XML,别用request.POST(它对 XML 无效) - 调用
wechatpay_v3.verify_signature()或手动校验sign字段(需拼接所有非空字段+密钥) - 校验通过后,立即用
out_trade_no尝试创建一条PayCallbackRecord模型(带unique_together = ('out_trade_no', 'channel')) - 如果数据库报
IntegrityError,说明已处理过,直接返回 SUCCESS;否则执行业务逻辑(如更新订单状态)、再保存记录
示例片段:
try:
PayCallbackRecord.objects.create(
out_trade_no=data['out_trade_no'],
channel='wechat',
raw_data=data,
status='processed'
)
except IntegrityError:
return HttpResponse(WECHAT_SUCCESS_XML, content_type='application/xml')
支付宝回调的幂等控制更依赖 trade_no 而非 out_trade_no
支付宝文档写“推荐用 out_trade_no”,但实际线上发现:同一笔订单多次回调时,out_trade_no 相同,但 trade_no 可能不同(比如转单、合并支付);而真正稳定的、平台保证全局唯一的是 trade_no。所以你的幂等表最好同时索引两者,并优先用 trade_no 查重。
-
trade_no是支付宝系统内唯一 ID,长度固定(28 位),且在所有回调类型(支付、退款、转账)中都存在 -
out_trade_no是你传过去的,可能被人工改写或前端重复提交,不可信 - 建表时加
unique_together = ('trade_no', 'channel'),而不是只靠out_trade_no - 回调里先查
PayCallbackRecord.objects.filter(trade_no=trade_no).exists(),存在就跳过
用数据库行锁替代 Redis 实现轻量幂等(适合中小流量)
很多人第一反应是上 Redis + SETNX,但在 Django 已有 PostgreSQL/MySQL 的场景下,用 select_for_update() 更可靠:避免缓存穿透、无需维护额外服务、天然支持事务回滚。
- 在事务中执行:
PayCallbackRecord.objects.select_for_update().get_or_create(out_trade_no=..., defaults={...}) - 注意:必须在
transaction.atomic()内,且查询字段要有索引(否则锁表) - 如果用 MySQL,确保隔离级别是
REPEATABLE READ或更高;PostgreSQL 默认即可 - 不要在锁内做 HTTP 请求(比如调第三方 API),否则拖慢整个事务,增加死锁概率
真正容易被忽略的是:回调接口必须设置超时(Nginx 或 Gunicorn 的 timeout),且数据库连接池要足够——否则并发回调进来,全卡在锁等待上,导致支付平台判定失败并持续重推。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











