微信支付 APIv3 支付成功回调:4 个验签头、5 秒应答和那张 15 次重试表
回调是 POST JSON。外层字段:id(通知 ID)、create_time、event_type(支付成功为 TRANSACTION.SUCCESS)、resource_type(固定 encrypt-resource)、resource{algorithm、ciphertext、associated_data、original_type、nonce、summary}。resource.algorithm 固定 AEAD_AES_256_GCM,ciphertext 是 Base64,最长 1048576 字节,associated_data 选填,实际可能是空。
验签的 4 个请求头
Wechatpay-Serial:用哪把密钥验签Wechatpay-Signature:Base64 签名Wechatpay-TimestampWechatpay-Nonce
验签串拼法是固定的三段加换行,最后一行 body 后面也必须有换行:
message = f"{timestamp}\n{nonce}\n{body}\n"
body 必须是 HTTP 请求的原始字节,不要 json.loads 之后再 json.dumps 拼回去——字段顺序和空格一变,签名就对不上。
Wechatpay-Serial 看前缀区分密钥类型:PUB_KEY_ID_ 加数字串(如 PUB_KEY_ID_3000000001)是微信支付公钥模式,用公钥验;不符合该格式则按平台证书序列号取对应平台证书验签。
serial = headers["Wechatpay-Serial"]
if serial.startswith("PUB_KEY_ID_"):
verify_key = wechatpay_public_key # 公钥模式
else:
verify_key = platform_certs[serial] # 平台证书模式
5 秒内应答,先应答再处理业务
验签通过返回 200 或 204,无应答报文;验签不通过返回 4XX/5XX,带 {"code":"FAIL","message":"..."}。
应答要在 5 秒内完成,业务逻辑放到应答之后异步做(入队、起协程都行)。业务耗时长导致应答超时,微信侧就认为失败。
微信收到成功应答后不再重发;应答失败或超时,按下面的间隔重试:
15s / 15s / 30s / 3m / 10m / 20m / 30m / 30m / 30m / 60m / 3h / 3h / 3h / 6h / 6h
最多 15 次,直到应答成功。所以不能只依赖回调,要配合查询接口兜底。
同一通知会重复投递,处理逻辑必须幂等:用通知 id 做唯一键落库,重复的直接返回 200。
应答代码骨架
@app.post("/wxpay/notify")
def wxpay_notify():
raw = request.get_data() # 原始字节,不要二次序列化
h = request.headers
message = f"{h['Wechatpay-Timestamp']}\n{h['Wechatpay-Nonce']}\n{raw.decode('utf-8')}\n"
ok = verify_sign(
serial=h["Wechatpay-Serial"],
signature=h["Wechatpay-Signature"],
message=message,
)
if not ok:
# WECHATPAY/SIGNTEST/ 开头的探测签名同样走这里,验失败就失败
return {"code": "FAIL", "message": "验签失败"}, 401
notify_id = json.loads(raw)["id"]
enqueue_notify(notify_id, raw) # 业务异步处理
return "", 204
resource 解密:
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
plaintext = AESGCM(api_v3_key).decrypt(
nonce.encode(),
base64.b64decode(ciphertext),
(associated_data or "").encode(), # 可能为空
)
签名探测流量
极少数回调的 Wechatpay-Signature 以 WECHATPAY/SIGNTEST/ 开头,用来检测你是否真的验签。不要特判放行,按正常流程验签,验签失败照常返回 4XX/5XX。
notify_url 与接入条件
- 必须是商户真实地址,
https://开头,带完整路径,公网可访问; - 不能是
localhost、127.0.0.1、192.168.x.x这类内网地址; - 不能携带参数;
- 回调处理逻辑不能做登录态校验,否则收不到通知;
- 商户侧防火墙需对微信支付回调 IP 段开白名单;
- 未设置 APIv3 密钥时,微信支付不会发送回调。
取不到头、验签失败怎么查
某些代理服务器或 CDN 转发时会过滤微信支付扩展的 HTTP 头,应用层直接取不到 Wechatpay-*,需要改代理配置或直连。
验签失败的应答 detail 里会带 sign_information(method、url、truncated_sign_message、sign_message_length),照着它比对自己拼的串。常见原因:
- 签名串最后一行没加换行符(空 body 的 GET,最后一行就是一个换行符);
- 拼接用的 URL 与实际请求不一致;
- 签名和设置
Authorization用了两个时间戳或两个随机串; - JSON 被序列化了两次;
- 编码不是 UTF-8;
- 拼接顺序错。