微信支付回调验签必须先读取原始未解析的XML字节流,否则因Body被提前消费导致sign verification failed;V2用MD5+字典序拼接(不含sign,末加key),V3用RSA校验HTTP头;响应必须严格返回UTF-8 XML格式SUCCESS。

微信支付回调验签前必须先解析原始 body
Iris 默认的 ctx.PostValue 或 ctx.FormValue 会触发自动解析(如 urldecode、form 解析),但微信支付回调是 raw XML,且验签必须用原始未解码、未解析的字节流。一旦调用过任何解析方法,ctx.Request().Body 就被读取并关闭,后续再读就是空。
正确做法是:在路由 handler 开头立刻用 io.ReadAll(ctx.Request().Body) 拿到原始 []byte,然后手动解析 XML;之后再做验签——否则验签必失败。
常见错误现象:sign verification failed 或验签通过但业务逻辑不执行,往往就是 body 被提前消费了。
- 务必在 handler 最开始读取
ctx.Request().Body,且只读一次 - 读完后需用
bytes.NewReader(rawBody)重新包装,供后续 XML 解析使用 - 不要调用
ctx.PostValue*、ctx.FormValue、ctx.ReadForm等任何自动解析方法
验签时要用微信官方推荐的签名算法和参数顺序
微信支付 V3 接口用的是 HMAC-SHA256 + 商户 API 密钥,而老版 JSAPI/APP/NATIVE 支付回调(V2)用的是 MD5 + 字段拼接 + key 后缀。Iris 本身不内置验签逻辑,你得自己实现或引入 github.com/go-pay/wechat 这类成熟封装。
关键点在于字段排序:V2 验签要求对所有非空字段按 key 字典序升序排列,拼成 key1=val1&key2=val2&key=YOUR_KEY 再 MD5。漏掉 sign 字段、多加空格、大小写错(如 return_code 写成 returncode)、或把 sign 参与拼接都会导致失败。
让 AI 读懂微信公众号。自研 7 阶段提取管道,穿透反爬率 99.89%,Token 消耗降低 50%–87%。支持 ChatGPT、Claude、Perplexity、Gemini 等平台无缝引用。
- V2 回调验签必须排除
sign字段本身,但拼接末尾要加上&key=YOUR_API_KEY - V3 回调验签走 HTTP 头
WECHATPAY-SIGNATURE和WECHATPAY-NONCE,用的是 RSA 签名,需校验时间戳、随机串、body hash - 商户 API 密钥(
KEY)不是公众号 AppSecret,也不是 APIv3 密钥,别混用
Iris 中处理回调响应必须严格遵循微信要求的 XML 格式
微信支付回调接口不是“收到就完事”,它会重试直到收到符合规范的 XML 响应。如果 Iris handler 返回 JSON、HTML、空 body 或 XML 格式错误(比如没闭合标签、编码不对),微信会持续发起回调,造成重复扣款风险。
必须返回且仅返回如下格式的 UTF-8 编码 XML:
<xml> <return_code>SUCCESS</return_code> <return_msg>OK</return_msg> </xml>
注意:return_code 是外层状态,不是 result_code;哪怕业务失败,只要验签成功,也得先回 SUCCESS,否则微信不停重发。
- 用
ctx.XML()时确保结构体字段 tag 是xml:"return_code",且无额外字段 - 避免用
ctx.Text()或ctx.JSON(),它们会设错 Content-Type - 响应前不要调用
ctx.StopExecution()或 panic,否则微信收不到完整 XML
在 MVC 结构里把验签逻辑抽成中间件还是 service?
放在 controller 里写死验签代码会导致复用性差、测试难、升级麻烦。Iris 的 MVC 不强制分层,但推荐把验签和 XML 解析封装进独立 service,比如 wechat.PaymentValidator,由 controller 调用。
中间件不合适:微信回调是特定路径(如 /api/pay/callback/wechat),不是全站行为;且验签依赖具体业务参数(如商户号、密钥),中间件难以注入。
- service 方法签名建议为:
ValidateNotifyXML(rawBody []byte, mchID, apiKey string) (map[string]string, error) - controller 中只做三件事:读 rawBody → 调 service 验签 → 解析成功后异步处理订单(别阻塞 HTTP 响应)
- 别在验签 service 里直接操作数据库或发消息,那属于业务层,要解耦


















