支付宝验签须用verify_v2并传request.POST.dict()剔除sign/sign_type,公钥为PEM格式沙箱密钥;微信需按字典序拼接非sign字段含空值+key,解析XML为小写key字典;Django回调要@csrf_exempt、select_for_update锁行、幂等校验、异步耗时操作。

支付宝回调验签失败:verify_v2 用错函数或没传对参数
支付宝官方 SDK 的 AlipayTradeService 类里,验签必须用 verify_v2,不是 verify(老版本)也不是 verify_and_return_data(不存在)。很多人卡在这一步,返回 False 却以为是密钥错了。
关键点在于:verify_v2 要求传入原始回调请求的 request.POST.dict()(Django 中),且必须剔除 sign 和 sign_type 字段;同时要确保传的是支付宝公钥(alipay_public_key),不是你自己的私钥。
-
request.POST.dict()得先转成普通 dict,不能直接传QueryDict - 手动 pop 掉
'sign'和'sign_type',否则验签必过不了 - 公钥格式必须是 PEM,开头为
-----BEGIN PUBLIC KEY-----,且不能有换行符或空格混入(读文件时用.replace('\n', '').replace(' ', '')不安全,建议用textwrap.dedent+strip) - 如果用了沙箱环境,公钥必须是沙箱版的——生产环境密钥在沙箱回调里一定失败
微信支付回调验签报 invalid signature:没按规范拼接原始字符串
微信不提供现成验签函数,得自己算。错误几乎都出在「待签名字符串」拼接逻辑上:字段必须按字典序排序、只取 key != 'sign' 的项、值为空也得参与拼接、末尾加 &key=xxx——漏任一环节就 invalid signature。
特别注意:微信回调 body 是 XML,但验签用的是解析后的 dict,不是原始 XML 字符串。Django 默认不会自动解析 XML POST body,得手动用 xml.etree.ElementTree 解析,再转成 dict(别用第三方库自动转,字段名大小写/下划线习惯容易错)。
立即学习“Python免费学习笔记(深入)”;
- 排序前先确保所有 key 都是小写(微信文档里字段如
appid、mch_id全小写,别写成AppId) -
sign字段必须排除,但其他空值字段(比如attach为空)仍要参与拼接 -
key是你微信商户平台设置的 API 密钥,不是证书密码,也不是 APIv3 的 secret - 签名算法固定是
HMAC-SHA256,别用 MD5 或 RSA
Django 视图处理异步回调:不能用 @csrf_exempt 就完事
支付宝/微信回调是服务器直连,没有浏览器上下文,所以 @csrf_exempt 必须加,但这只是起点。更大的坑在并发和幂等性上——同一个通知可能重试多次,而 Django 视图默认不保证原子性。
典型错误是:收到回调 → 查订单 → 更新状态 → 发货 → 返回 success。一旦中间步骤失败(比如库存扣减异常),下次重试就会重复发货。
- 必须在更新数据库前,用
select_for_update()锁住订单记录(需数据库支持事务) - 验签通过后,立刻查一遍该订单是否已处理成功(根据
out_trade_no或transaction_id),已存在则直接返回 success - 不要在视图里调用耗时操作(如发邮件、调外部 API),改用 Celery 异步任务,但任务体里仍要加幂等判断
- 微信回调要求 5 秒内响应,超时会反复推送;支付宝是 200ms 内,所以验签+DB 查询必须极快,索引要建在
out_trade_no上
证书与密钥管理:别把 apiclient_cert.pem 直接丢进代码里
微信 APIv3 回调虽不用证书验签,但如果你用证书调用查询订单等接口,apiclient_cert.pem 和 apiclient_key.pem 就必须安全加载。常见错误是路径硬编码、权限设成 777、或用 open().read() 读取后明文拼进请求头。
更隐蔽的问题是:Django 多进程下,证书内容被反复读取甚至缓存,导致 OpenSSL 报 SSL routines:ssl3_read_bytes:sslv3 alert bad certificate。
- 证书路径用
os.path.join(BASE_DIR, 'certs', 'apiclient_cert.pem'),别用相对路径 - 读取后用
ssl.SSLContext.load_cert_chain()加载,不要手动拼cert + key字符串 - 密钥文件权限必须是 600,部署时用 Ansible 或 shell 脚本强制修正
- 本地开发用沙箱密钥,生产环境密钥绝不能提交到 Git,走环境变量或 secrets 后端(如 HashiCorp Vault)
非对称加密本身不难,难的是每家支付平台对「原始数据」的定义、字段过滤规则、重试策略、时间窗口容忍度都不一样。一个字段排序错、一个换行符多、一次没锁行,就可能引发资损。做支付回调,本质是写分布式系统里的临界区代码,不是调个 SDK 那么简单。











