支付回调收不到数据主因是django默认不解析raw body:微信需用request.body读xml并解析,支付宝要用request.post.dict()验签;务必@csrf_exempt、验证content_type、正确预处理参数、使用正确密钥、加数据库唯一约束防重复、异步处理耗时任务、记录原始日志、配置secure_proxy_ssl_header和allowed_hosts。

支付回调为什么收不到数据?
绝大多数 Django 支付对接失败,卡在回调环节——不是没写,而是根本没收到请求。常见原因是支付平台(如微信、支付宝)发来的 POST 数据是 raw body(非 application/x-www-form-urlencoded),而 Django 默认的 request.POST 只解析表单编码,对 raw JSON 或 XML 为空。
- 微信回调:用
request.body读取原始字节,再用xml.etree.ElementTree解析 XML - 支付宝回调:通常为
application/x-www-form-urlencoded,但必须用request.POST.dict()+verify_sign()验签,不能直接信request.GET - 务必在视图开头加
@csrf_exempt,否则 Django 中间件会拦截 POST 回调 - 调试时用
print(request.META.get("CONTENT_TYPE"), len(request.body))确认实际接收内容
验签失败的三个高频原因
验签通过才是安全回调的起点,90% 的“验签失败”不是算法错,而是数据预处理不对。
- 微信:必须按字段名 ASCII 字典序拼接(
"".join(f"{k}={v}" for k, v in sorted(params.items()))),且过滤掉sign和空值字段 - 支付宝:要用
request.POST.dict()而非request.GET.dict(),且需剔除sign和sign_type后再排序拼接 - 密钥来源错误:别用商户后台看到的“API 密钥”,微信要
APIv3 密钥(32位),支付宝要应用私钥对应公钥(不是网关公钥)
如何安全地更新订单状态?
回调里直接 order.status = "paid" + save() 是危险操作——重放攻击、重复通知、网络抖动都可能造成状态错乱。
- 用数据库唯一约束兜底:在订单模型加
unique_together = ("out_trade_no", "trade_status")或用select_for_update() - 幂等判断必须包含:检查
out_trade_no是否已存在且状态非pending,已成功则直接返回 success - 避免在回调中触发耗时操作(如发邮件、调用物流接口),用
django-rq或celery异步处理 - 记录完整原始回调数据到日志或
PaymentCallbackLog表,字段含raw_body、timestamp、ip
Django settings 里漏配的两个关键项
本地开发能跑通,上线就 500,往往是因为没改透配置。
-
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https"):Nginx 反向代理后,Django 才能正确识别 HTTPS,否则微信回调验签失败(签名基于https协议生成) -
ALLOWED_HOSTS必须包含支付平台回调域名(如微信要求填你服务器公网 IP 或备案域名),否则DisallowedHost错误静默吞掉请求
回调地址 URL 路径别写死在代码里,统一从 settings.PAYMENT_CALLBACK_URL 读取,方便测试/生产环境切换。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











