duidui_fiber/internal/payment/service.go
2026-03-27 10:34:03 +08:00

292 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}