fix(billing): 微信支付钉死「微信支付公钥」验签体系 —— 商户 2025-09 开户没有平台证书

用户拿旧项目代码对出来的真问题:我此前用 WithWechatPayAutoAuthCipher(平台证书
模式,APIv3 密钥自动下载平台证书验签),但 2024 起新注册商户只发「微信支付公钥」
(PUB_KEY_ID_ 开头)、没有平台证书——在该商户号上初始化/回调验签都会挂。

- 改 WithWechatPayPublicKeyAuthCipher(商户私钥+公钥ID+公钥文件);回调验签用
  NewSHA256WithRSAPubkeyVerifier;平台证书模式不留双模式赘肉(YAGNI)。
- Config 增 public_key_path/public_key_id(必填,公钥文件同样只存路径);
  admin 卡片补两字段;env 兜底加 WECHAT_PUBLIC_KEY(_ID)。
- 顺手修 live 撞出的真 bug:sundynix_setting.value 是 varchar(255),
  支付配置 JSON(含加密密钥)一条就超(SQLSTATE 22001)→ 改 text。
live:列类型已迁 text;缺公钥两项报「配置不全,缺: public_key_path,
public_key_id」;GET 回显含新字段。go 6 包测试+tsc+41 vitest 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-17 11:12:26 +08:00
parent d1e1e0fc4a
commit 5d7eca5de3
6 changed files with 43 additions and 10 deletions
+16 -6
View File
@@ -5,13 +5,13 @@ package payment
import (
"context"
"crypto/rsa"
"errors"
"fmt"
"net/http"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
@@ -29,21 +29,31 @@ type Wechat struct {
apiv3Key string
client *core.Client
svc native.NativeApiService
pubKey *rsa.PublicKey // 微信支付公钥(验签回调用;构造时必填)
pubKeyID string
}
// New 按完整配置装配微信渠道(私钥从磁盘路径加载;调用方保证字段齐全)。
// New 按完整配置装配微信渠道(商户私钥/微信支付公钥都从磁盘路径加载;调用方保证字段齐全)。
// 验签体系钉死「微信支付公钥」模式(WithWechatPayPublicKeyAuthCipher):本项目商户
// 2025-09 开户,只有公钥体系;老商户的平台证书模式(AutoAuthCipher)不支持。
func New(ctx context.Context, c Config) (*Wechat, error) {
priv, err := utils.LoadPrivateKeyWithPath(c.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("商户私钥加载失败(%s): %w", c.PrivateKeyPath, err)
}
client, err := core.NewClient(ctx, option.WithWechatPayAutoAuthCipher(c.MchID, c.CertSerial, priv, c.APIv3Key))
pub, err := utils.LoadPublicKeyWithPath(c.PublicKeyPath)
if err != nil {
return nil, fmt.Errorf("微信支付公钥加载失败(%s): %w", c.PublicKeyPath, err)
}
client, err := core.NewClient(ctx,
option.WithWechatPayPublicKeyAuthCipher(c.MchID, c.CertSerial, priv, c.PublicKeyID, pub))
if err != nil {
return nil, fmt.Errorf("客户端初始化失败: %w", err)
}
return &Wechat{
mchID: c.MchID, appID: c.AppID, notifyURL: c.NotifyURL, apiv3Key: c.APIv3Key,
client: client, svc: native.NativeApiService{Client: client},
pubKey: pub, pubKeyID: c.PublicKeyID,
}, nil
}
@@ -111,11 +121,11 @@ func (w *Wechat) QueryOrder(ctx context.Context, orderID string) (QueryResult, e
return fromTransaction(t), nil
}
// VerifyCallback 验签 + 解密支付回调(APIv3:平台证书验签、AES-GCM 解密资源)。
// VerifyCallback 验签 + 解密支付回调(微信支付公钥验签 + APIv3 密钥 AES-GCM 解密资源)。
// 验签失败一律拒绝——回调路由是公开的,签名是唯一的门。
func (w *Wechat) VerifyCallback(req *http.Request) (QueryResult, error) {
certVisitor := downloader.MgrInstance().GetCertificateVisitor(w.mchID)
h, err := notify.NewRSANotifyHandler(w.apiv3Key, verifiers.NewSHA256WithRSAVerifier(certVisitor))
h, err := notify.NewRSANotifyHandler(w.apiv3Key,
verifiers.NewSHA256WithRSAPubkeyVerifier(w.pubKeyID, *w.pubKey))
if err != nil {
return QueryResult{}, fmt.Errorf("回调处理器初始化失败: %w", err)
}