Revel对接微信支付需手动实现签名验签与HTTP处理,关键点包括:提前读取并缓存原始Body、严格按规范构造验签串、原样读取大小写敏感Header、公钥用ParsePKIXPublicKey加载、回调5秒内返回纯净JSON响应,并用sync.Once单次初始化公钥。

Revel 框架本身不内置微信支付 SDK,对接必须手动实现签名验签与 HTTP 请求处理逻辑。直接套用 PHP/Java 的 SDK 文档会踩坑,因为 Revel 的中间件、Body 读取、Header 解析方式和标准库行为有差异——尤其是 request.Body 只能读一次,且默认会自动解码 JSON,导致验签时拿到的原始报文被篡改。
Revel 中读不到原始回调 Body 怎么办
微信支付回调验签要求使用「原始报文主体」,而 Revel 默认在 Controller.Params.JSON 或 c.Params.Get("json") 中解析后,request.Body 已 EOF。常见错误是直接 io.ReadAll(c.Request.Body) 返回空字节。
- 必须在任何 JSON 解析前,用
io.ReadAll一次性读取原始 Body,并缓存到c.Params.Set("raw_body", rawBytes) - 不要用
c.Params.JSON,改用json.Unmarshal(rawBytes, ¬ify) - 若用了
revel.Intercept或自定义中间件,确保它们没提前消费request.Body - 开发期加日志:打印
len(rawBytes)和前 50 字符,确认不是空或被 gzip 压缩(微信回调不压缩)
Wechatpay-Signature 头字段解析失败
Revel 默认把 Header 名转成首字母大写 + 连字符转驼峰(如 Wechatpay-Signature → WechatpaySignature),但微信支付严格区分大小写,且要求原样读取 Wechatpay-Signature、Wechatpay-Timestamp 等头字段。
- 必须用
c.Request.Header.Get("Wechatpay-Signature"),不能用c.Request.Header.Get("WechatpaySignature") - 同理获取
Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Serial - 检查是否启用了反向代理(如 Nginx),它可能默认 strip 掉带下划线或短横的 Header;需配置
underscores_in_headers on;和proxy_pass_request_headers on;
验签串构造必须严格按三行 \n 结尾
微信支付公钥验签要求签名串是「时间戳\n随机串\nbody\n」三行,末尾带换行符 \n(ASCII 0x0A)。Go 的 fmt.Sprintf("%s\n%s\n%s\n", ts, nonce, body) 是正确写法,漏掉最后一个 \n 或用 \r\n 都会失败。
- body 必须是原始字节解码后的字符串,不能是 Go struct 序列化结果(字段顺序、空格、换行都不同)
- 如果 body 是空(极少见),最后一行仍要是单个
\n - 验签前先用
log.Printf("signing string: %q", signingString)看实际拼接内容,比对文档示例 - 公钥加载要用
crypto/x509.ParsePKIXPublicKey,别用ParseCertificate—— 微信给的是公钥文件,不是证书
回调应答必须 5 秒内返回 200 且无额外输出
微信支付要求回调接口在 5 秒内返回 HTTP 200,且响应体只能是空或 {"code":"SUCCESS","message":"OK"}。Revel 默认会渲染模板或追加换行,导致返回 200 OK\n\n{...},被微信判定为失败并重试。
- 在 Controller 方法末尾调用
c.Response.Out.Header().Set("Content-Type", "application/json; charset=utf-8") - 用
c.RenderText(" ")或c.RenderJSON(map[string]string{"code": "SUCCESS"}),但要确保没其他中间件注入额外内容 - 禁用 Revel 的
DEFAULT_FORMATS自动渲染,避免 JSON 外层被包成{"result":{...}} - 上线前用
curl -v -X POST https://yoursite.com/pay/callback -d @sample_notify.json测试端到端耗时
最易忽略的一点:Revel 的 AppInit 阶段不适合加载微信公钥或证书——它可能被多次调用,或在热重载时重复初始化。应该在第一次验签前做单次加载,并用 sync.Once 保护。否则公钥解析失败会静默吞掉错误,只留下验签不过的假象。



















