## 问题定位 - 现象:POST `/api/pay/wechat/notify` 返回 400,日志提示“certificate [PUB_KEY_ID_0116104396352025041000211519001600] not found in verifier”。 - 原因:微信侧请求头 `Wechatpay-Serial` 为 `PUB_KEY_ID_*` 格式,说明该商户已启用“微信支付平台公钥”模式;当前代码仅使用“平台证书”验签(`verifiers.NewSHA256WithRSAVerifier` + `downloader`),导致找不到匹配的证书序列号而验签失败。 - 代码位置: - 路由注册:`internal/router/router.go:252` - 回调处理:`internal/api/pay/wechat_notify.go:40-46`(注册证书下载器+证书验签器) - 客户端初始化:`internal/pkg/pay/client.go:32-39`(`WithWechatPayAutoAuthCipher` 使用平台证书模式) ## 修复方案 - 增加“平台公钥”模式配置并切换验签/签名实现: - 在配置新增 `wechatpay.public_key_id` 与 `wechatpay.public_key_path`(商户后台下载的 `pub_key.pem` 与对应 `PUB_KEY_ID_*`)。 - 回调验签:当检测到已配置公钥,改用 `verifiers.NewSHA256WithRSAPubkeyVerifier(publicKeyID, publicKey)` 构造 `notify.Handler`;否则保持现有证书模式。 - 请求侧签名与自动加密:当检测到已配置公钥,改用 `option.WithWechatPayPublicKeyAuthCipher(mchid, serialNo, privateKey, publicKeyID, publicKey)` 初始化 `core.Client`;否则保持 `WithWechatPayAutoAuthCipher`。 - 初始化时机优化: - 将证书下载器注册/公钥加载前置至应用启动阶段,避免首次回调时的“尚未拉取证书”竞态问题。 - 兼容策略: - 双模式自动选择:优先检测公钥配置;缺省走证书模式,确保对旧商户不破坏。 ## 实施步骤 - 配置: - 在 `configs/*.toml` 的 `[wechatpay]` 增加 `public_key_id` 与 `public_key_path` 键;同时支持环境变量覆盖(如 `WECHAT_PUBLIC_KEY_ID`、`WECHAT_PUBLIC_KEY_PATH`)。 - 代码改动: - `internal/api/pay/wechat_notify.go`:根据配置分支选择 `verifiers.NewSHA256WithRSAPubkeyVerifier` 或现有证书验签器;加载公钥使用 `utils.LoadPublicKeyWithPath`。 - `internal/pkg/pay/client.go`:根据配置分支选择 `option.WithWechatPayPublicKeyAuthCipher` 或现有证书选项。 - 启动流程:在应用初始化位置集中完成下载器注册/公钥加载与复用,不在每次回调时重复注册。 ## 验证步骤 - 本地联调: - 使用真实微信回调数据(包含 `Wechatpay-Serial: PUB_KEY_ID_*`)验证回调成功返回 `{"code":"SUCCESS","message":"OK"}`。 - 下单→支付→回调链路验证:检查订单状态从 1→2,`paidAt` 按回调 `success_time` 生效(`internal/api/pay/wechat_notify.go:67-71`)。 - 日志与安全: - 打印初始化模式(证书/公钥)与关键配置是否完整,避免静默失败。 - 不输出密钥/私钥内容,符合安全规范。 ## 额外检查(关于“req 没有写”) - 当前回调处理直接使用 `ctx.Request()` 原始请求体(`internal/api/pay/wechat_notify.go:48`),未提前读取/篡改;若上游中间件读取了 `Body`,需保证使用 `io.NopCloser` 复位请求体后再交给 `notify.Handler`。本次改造同时确认路由链路未破坏请求体传递。 请确认是否按照“平台公钥”接入(已具备 `pub_key.pem` 与 `PUB_KEY_ID_*`)。确认后我将按上述方案实现代码与配置改动,并完成联调与验证。