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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user