代码 微信支付 APIv3 支付成功回调:4 个验签头、5 秒应答和那张 15 次重试表

2026-10-27 21:04:23

微信支付 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-Timestamp
  • Wechatpay-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;
  • 拼接顺序错。
复制全文 生成海报 支付接口 微信支付 PHP

推荐文章

程序员茄子在线接单