292 lines
9.5 KiB
Go
292 lines
9.5 KiB
Go
package payment
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rsa"
|
||
"fmt"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||
|
||
"dd_fiber_api/config"
|
||
)
|
||
|
||
// min 返回两个整数中的较小值
|
||
func min(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
// WechatPayV3Service 微信支付V3服务(使用官方SDK)
|
||
type WechatPayV3Service struct {
|
||
config *config.WechatConfig
|
||
client *core.Client // 官方SDK客户端
|
||
privateKey *rsa.PrivateKey // 保存私钥,用于生成支付签名
|
||
}
|
||
|
||
// NewWechatPayV3Service 创建微信支付V3服务实例(使用官方SDK)
|
||
func NewWechatPayV3Service(cfg *config.WechatConfig) (*WechatPayV3Service, error) {
|
||
// 加载私钥
|
||
var privateKey *rsa.PrivateKey
|
||
var err error
|
||
|
||
// 优先使用私钥内容,其次使用文件路径
|
||
if cfg.PrivateKey != "" {
|
||
// 从字符串加载私钥
|
||
privateKey, err = utils.LoadPrivateKey(cfg.PrivateKey)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载私钥失败: %v", err)
|
||
}
|
||
} else if cfg.PrivateKeyPath != "" {
|
||
// 从文件路径加载私钥
|
||
privateKey, err = utils.LoadPrivateKeyWithPath(cfg.PrivateKeyPath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载私钥文件失败: %v", err)
|
||
}
|
||
} else {
|
||
return nil, fmt.Errorf("未配置私钥,请设置 private_key 或 private_key_path")
|
||
}
|
||
|
||
// 使用官方SDK初始化客户端
|
||
ctx := context.Background()
|
||
|
||
// 验证配置参数
|
||
apiKeyV3 := strings.TrimSpace(cfg.APIKeyV3)
|
||
if apiKeyV3 == "" {
|
||
return nil, fmt.Errorf("API Key V3 不能为空")
|
||
}
|
||
if len(apiKeyV3) < 32 {
|
||
log.Printf("⚠️ 警告: API Key V3 长度异常(%d 字符),请确认配置正确", len(apiKeyV3))
|
||
}
|
||
if cfg.MchID == "" {
|
||
return nil, fmt.Errorf("商户号(mch_id)不能为空")
|
||
}
|
||
if cfg.SerialNo == "" {
|
||
return nil, fmt.Errorf("证书序列号(serial_no)不能为空")
|
||
}
|
||
|
||
log.Printf(" 正在初始化微信支付客户端: 商户号=%s, 证书序列号=%s, API Key V3长度=%d",
|
||
cfg.MchID, cfg.SerialNo, len(apiKeyV3))
|
||
|
||
opts := []core.ClientOption{
|
||
option.WithWechatPayAutoAuthCipher(
|
||
cfg.MchID,
|
||
cfg.SerialNo,
|
||
privateKey,
|
||
apiKeyV3,
|
||
),
|
||
}
|
||
|
||
client, err := core.NewClient(ctx, opts...)
|
||
if err != nil {
|
||
// 提供更详细的错误信息
|
||
errMsg := fmt.Sprintf("初始化微信支付客户端失败: %v", err)
|
||
if strings.Contains(err.Error(), "decrypt") || strings.Contains(err.Error(), "cipher") {
|
||
errMsg += "\n可能的原因:"
|
||
errMsg += "\n1. API Key V3 配置错误(请检查微信商户平台中的 API v3 密钥)"
|
||
errMsg += "\n2. 证书序列号(serial_no)不正确"
|
||
errMsg += "\n3. 私钥文件与证书序列号不匹配"
|
||
errMsg += "\n4. 商户号(mch_id)配置错误"
|
||
errMsg += fmt.Sprintf("\n当前配置: mch_id=%s, serial_no=%s, api_key_v3长度=%d",
|
||
cfg.MchID, cfg.SerialNo, len(apiKeyV3))
|
||
}
|
||
return nil, fmt.Errorf(errMsg)
|
||
}
|
||
|
||
log.Println(" ✅ 微信支付 V3 客户端初始化成功(使用官方SDK)")
|
||
|
||
// 由于WithWechatPayAutoAuthCipher已经配置了自动证书更新,我们需要获取证书管理器
|
||
// 但是client没有公开方法获取,我们需要通过其他方式
|
||
// 实际上,我们可以创建一个新的证书管理器,或者使用client的内部方法
|
||
// 这里我们先尝试使用client作为CertificateGetter(如果它实现了接口)
|
||
// 如果不行,我们需要手动创建证书管理器
|
||
|
||
// 暂时先不设置certificateGetter,在GetVerifier中处理
|
||
service := &WechatPayV3Service{
|
||
config: cfg,
|
||
client: client,
|
||
privateKey: privateKey, // 保存私钥用于生成支付签名
|
||
}
|
||
|
||
// 尝试从client获取证书管理器(如果client实现了CertificateGetter接口)
|
||
// 由于client没有公开方法,我们需要使用反射或其他方式
|
||
// 或者,我们可以创建一个新的证书管理器
|
||
// 这里我们先返回service,在GetVerifier中处理
|
||
|
||
return service, nil
|
||
}
|
||
|
||
// CreateWechatPayV3Request 创建微信支付V3订单请求
|
||
type CreateWechatPayV3Request struct {
|
||
OrderID string `json:"order_id"` // 关联的订单ID(雪花ID,字符串格式)
|
||
Description string `json:"description"` // 商品描述
|
||
OpenID string `json:"openid"` // 用户openid(小程序支付必需)
|
||
Amount int32 `json:"amount"` // 支付金额(分)
|
||
}
|
||
|
||
// CreateWechatPayV3Response 创建微信支付V3订单响应
|
||
type CreateWechatPayV3Response struct {
|
||
OrderID string `json:"order_id"` // 关联的订单ID
|
||
PrepayID string `json:"prepay_id"` // 预支付交易会话标识
|
||
AppID string `json:"app_id"` // 微信应用ID
|
||
TimeStamp string `json:"time_stamp"` // 时间戳
|
||
NonceStr string `json:"nonce_str"` // 随机字符串
|
||
Package string `json:"package"` // 订单详情扩展字符串(prepay_id=xxx)
|
||
SignType string `json:"sign_type"` // 签名方式(RSA)
|
||
PaySign string `json:"pay_sign"` // 签名
|
||
Success bool `json:"success"` // 是否成功
|
||
Message string `json:"message"` // 响应消息
|
||
}
|
||
|
||
// CreateWechatPayV3 创建微信支付V3订单(使用官方SDK)
|
||
func (s *WechatPayV3Service) CreateWechatPayV3(ctx context.Context, req *CreateWechatPayV3Request) (*CreateWechatPayV3Response, error) {
|
||
// 验证 openid(小程序支付必需)
|
||
if req.OpenID == "" {
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: "openid 是必需的(小程序支付)",
|
||
}, nil
|
||
}
|
||
|
||
// 验证金额
|
||
if req.Amount <= 0 {
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: "支付金额必须大于0",
|
||
}, nil
|
||
}
|
||
|
||
// 使用官方SDK创建小程序支付订单
|
||
svc := jsapi.JsapiApiService{Client: s.client}
|
||
|
||
request := jsapi.PrepayRequest{
|
||
Appid: core.String(s.config.AppID),
|
||
Mchid: core.String(s.config.MchID),
|
||
Description: core.String(req.Description),
|
||
OutTradeNo: core.String(req.OrderID),
|
||
NotifyUrl: core.String(s.config.NotifyURL),
|
||
Amount: &jsapi.Amount{
|
||
Total: core.Int64(int64(req.Amount)),
|
||
Currency: core.String("CNY"),
|
||
},
|
||
Payer: &jsapi.Payer{
|
||
Openid: core.String(req.OpenID),
|
||
},
|
||
}
|
||
|
||
// 调用官方SDK创建订单
|
||
resp, result, err := svc.Prepay(ctx, request)
|
||
if err != nil {
|
||
log.Printf("⚠️ 创建微信支付订单失败: %v", err)
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: fmt.Sprintf("创建支付订单失败: %v", err),
|
||
}, nil
|
||
}
|
||
|
||
// 检查HTTP响应状态码(从result中获取)
|
||
if result != nil && result.Response != nil && result.Response.StatusCode != 200 {
|
||
log.Printf("⚠️ 微信支付返回错误状态码: %d", result.Response.StatusCode)
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: fmt.Sprintf("微信支付返回错误: HTTP %d", result.Response.StatusCode),
|
||
}, nil
|
||
}
|
||
|
||
// 获取 prepay_id(从resp中获取)
|
||
prepayID := ""
|
||
if resp != nil && resp.PrepayId != nil {
|
||
prepayID = *resp.PrepayId
|
||
}
|
||
|
||
if prepayID == "" {
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: "微信支付返回缺少 prepay_id",
|
||
}, nil
|
||
}
|
||
|
||
log.Printf("✅ 创建微信支付订单成功: prepay_id=%s", prepayID)
|
||
|
||
// 生成小程序支付所需的签名参数
|
||
timestamp := time.Now().Unix()
|
||
nonceStr, err := utils.GenerateNonce()
|
||
if err != nil {
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: fmt.Sprintf("生成随机字符串失败: %v", err),
|
||
}, nil
|
||
}
|
||
packageStr := fmt.Sprintf("prepay_id=%s", prepayID)
|
||
|
||
// 使用官方SDK生成支付签名
|
||
signStr := fmt.Sprintf("%s\n%d\n%s\n%s\n", s.config.AppID, timestamp, nonceStr, packageStr)
|
||
signature, err := utils.SignSHA256WithRSA(signStr, s.privateKey)
|
||
if err != nil {
|
||
return &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
Success: false,
|
||
Message: fmt.Sprintf("生成支付签名失败: %v", err),
|
||
}, nil
|
||
}
|
||
|
||
// 构造返回结果
|
||
resultResp := &CreateWechatPayV3Response{
|
||
OrderID: req.OrderID,
|
||
PrepayID: prepayID,
|
||
AppID: s.config.AppID,
|
||
TimeStamp: fmt.Sprintf("%d", timestamp),
|
||
NonceStr: nonceStr,
|
||
Package: packageStr,
|
||
SignType: "RSA",
|
||
PaySign: signature,
|
||
Success: true,
|
||
Message: "创建支付订单成功",
|
||
}
|
||
|
||
return resultResp, nil
|
||
}
|
||
|
||
// GetClient 获取官方SDK客户端(用于notify handler)
|
||
func (s *WechatPayV3Service) GetClient() *core.Client {
|
||
return s.client
|
||
}
|
||
|
||
// GetAPIKeyV3 获取APIv3密钥(用于notify handler)
|
||
func (s *WechatPayV3Service) GetAPIKeyV3() string {
|
||
return strings.TrimSpace(s.config.APIKeyV3)
|
||
}
|
||
|
||
// GetVerifier 获取验证器(用于notify handler)
|
||
// 直接使用官方SDK的downloader管理器获取证书访问器
|
||
// 这与WithWechatPayAutoAuthCipher使用的机制相同,是官方推荐的方式
|
||
func (s *WechatPayV3Service) GetVerifier() (auth.Verifier, error) {
|
||
// 使用官方SDK的downloader管理器获取证书访问器
|
||
mgr := downloader.MgrInstance()
|
||
certGetter := mgr.GetCertificateVisitor(s.config.MchID)
|
||
|
||
if certGetter == nil {
|
||
return nil, fmt.Errorf("无法从downloader获取证书访问器,商户号: %s", s.config.MchID)
|
||
}
|
||
|
||
// 使用证书管理器创建verifier
|
||
verifier := verifiers.NewSHA256WithRSAVerifier(certGetter)
|
||
return verifier, nil
|
||
}
|