package service import ( "context" "fmt" "net/http" "qnc-server/app/main/api/internal/config" "qnc-server/app/main/model" "qnc-server/common/ctxdata" "qnc-server/pkg/lzkit/lzUtils" "strconv" "time" "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" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/app" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" "github.com/wechatpay-apiv3/wechatpay-go/utils" "github.com/zeromicro/go-zero/core/logx" ) const ( TradeStateSuccess = "SUCCESS" // 支付成功 TradeStateRefund = "REFUND" // 转入退款 TradeStateNotPay = "NOTPAY" // 未支付 TradeStateClosed = "CLOSED" // 已关闭 TradeStateRevoked = "REVOKED" // 已撤销(付款码支付) TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付) TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败) ) // InitType 初始化类型 type InitType string const ( InitTypePlatformCert InitType = "platform_cert" // 平台证书初始化 InitTypeWxPayPubKey InitType = "wxpay_pubkey" // 微信支付公钥初始化 ) type WechatPayService struct { config config.WxpayConfig wechatClient *core.Client notifyHandler *notify.Handler userAuthModel model.UserAuthModel } // NewWechatPayService 创建微信支付服务实例 func NewWechatPayService(c config.Config, userAuthModel model.UserAuthModel, initType InitType) *WechatPayService { switch initType { case InitTypePlatformCert: return newWechatPayServiceWithPlatformCert(c, userAuthModel) case InitTypeWxPayPubKey: return newWechatPayServiceWithWxPayPubKey(c, userAuthModel) default: logx.Errorf("不支持的初始化类型: %s", initType) panic(fmt.Sprintf("初始化失败,服务停止: %s", initType)) } } // newWechatPayServiceWithPlatformCert 使用平台证书初始化微信支付服务 func newWechatPayServiceWithPlatformCert(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService { // 从配置中加载商户信息 mchID := c.Wxpay.MchID mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber mchAPIv3Key := c.Wxpay.MchApiv3Key // 从文件中加载商户私钥 mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath) if err != nil { logx.Errorf("加载商户私钥失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序 } // 使用商户私钥和其他参数初始化微信支付客户端 opts := []core.ClientOption{ option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), } client, err := core.NewClient(context.Background(), opts...) if err != nil { logx.Errorf("创建微信支付客户端失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序 } // 在初始化时获取证书访问器并创建 notifyHandler certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor)) if err != nil { logx.Errorf("获取证书访问器失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } logx.Infof("微信支付客户端初始化成功(平台证书方式)") return &WechatPayService{ config: c.Wxpay, wechatClient: client, notifyHandler: notifyHandler, userAuthModel: userAuthModel, } } // newWechatPayServiceWithWxPayPubKey 使用微信支付公钥初始化微信支付服务 func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService { // 从配置中加载商户信息 mchID := c.Wxpay.MchID mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber mchAPIv3Key := c.Wxpay.MchApiv3Key mchPrivateKeyPath := c.Wxpay.MchPrivateKeyPath mchPublicKeyID := c.Wxpay.MchPublicKeyID mchPublicKeyPath := c.Wxpay.MchPublicKeyPath // 从文件中加载商户私钥 mchPrivateKey, err := utils.LoadPrivateKeyWithPath(mchPrivateKeyPath) if err != nil { logx.Errorf("加载商户私钥失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } // 从文件中加载支付公钥 mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath) if err != nil { logx.Errorf("加载微信支付平台证书失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } // 使用商户私钥和其他参数初始化微信支付客户端 opts := []core.ClientOption{ option.WithWechatPayPublicKeyAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchPublicKeyID, mchPublicKey), } client, err := core.NewClient(context.Background(), opts...) if err != nil { logx.Errorf("创建微信支付客户端失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(context.Background(), mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key) if err != nil { logx.Errorf("注册下载器失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) notifyHandler := notify.NewNotifyHandler( mchAPIv3Key, verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, mchPublicKeyID, *mchPublicKey)) logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)") return &WechatPayService{ config: c.Wxpay, wechatClient: client, notifyHandler: notifyHandler, userAuthModel: userAuthModel, } } // CreateWechatAppOrder 创建微信APP支付订单 func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) { totalAmount := lzUtils.ToWechatAmount(amount) // 构建支付请求参数 payRequest := app.PrepayRequest{ Appid: core.String(w.config.AppID), Mchid: core.String(w.config.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(w.config.NotifyUrl), Amount: &app.Amount{ Total: core.Int64(totalAmount), }, } // 初始化 AppApiService svc := app.AppApiService{Client: w.wechatClient} // 发起预支付请求 resp, result, err := svc.Prepay(ctx, payRequest) if err != nil { return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) } // 返回预支付交易会话标识 return *resp.PrepayId, nil } // CreateWechatMiniProgramOrder 创建微信小程序支付订单 func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) { totalAmount := lzUtils.ToWechatAmount(amount) // 构建支付请求参数 payRequest := jsapi.PrepayRequest{ Appid: core.String(w.config.AppID), Mchid: core.String(w.config.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(w.config.NotifyUrl), Amount: &jsapi.Amount{ Total: core.Int64(totalAmount), }, Payer: &jsapi.Payer{ Openid: core.String(openid), // 用户的 OpenID,通过前端传入 }} // 初始化 AppApiService svc := jsapi.JsapiApiService{Client: w.wechatClient} // 发起预支付请求 resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) if err != nil { return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) } // 返回预支付交易会话标识 return resp, nil } // CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序) func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) { // 根据 ctx 中的 platform 判断平台 platform := ctx.Value("platform").(string) var prepayData interface{} var err error switch platform { case "mp-weixin": userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMini) if findAuthModelErr != nil { return "", findAuthModelErr } prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) if err != nil { return "", err } case "h5-weixin": userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5) if findAuthModelErr != nil { return "", findAuthModelErr } prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) if err != nil { return "", err } case "app": // 如果是 APP 平台,调用 APP 支付订单创建 prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo) default: return "", fmt.Errorf("不支持的支付平台: %s", platform) } // 如果创建支付订单失败,返回错误 if err != nil { return "", fmt.Errorf("支付订单创建失败: %v", err) } // 返回预支付ID return prepayData, nil } // HandleWechatPayNotification 处理微信支付回调 func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) { transaction := new(payments.Transaction) _, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction) if err != nil { return nil, fmt.Errorf("微信支付通知处理失败: %v", err) } // 返回交易信息 return transaction, nil } // HandleRefundNotification 处理微信退款回调 func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) { refund := new(refunddomestic.Refund) _, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund) if err != nil { return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err) } return refund, nil } // QueryOrderStatus 主动查询订单状态 func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) { svc := jsapi.JsapiApiService{Client: w.wechatClient} // 调用 QueryOrderById 方法查询订单状态 resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{ TransactionId: core.String(transactionID), Mchid: core.String(w.config.MchID), }) if err != nil { return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode) } return resp, nil } // WeChatRefund 申请微信退款 func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error { // 生成唯一的退款单号 outRefundNo := fmt.Sprintf("%s-refund", outTradeNo) // 初始化退款服务 svc := refunddomestic.RefundsApiService{Client: w.wechatClient} // 创建退款请求 resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{ OutTradeNo: core.String(outTradeNo), OutRefundNo: core.String(outRefundNo), NotifyUrl: core.String(w.config.RefundNotifyUrl), Amount: &refunddomestic.AmountReq{ Currency: core.String("CNY"), Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)), Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)), }, }) if err != nil { return fmt.Errorf("微信订单申请退款错误: %v", err) } // 打印退款结果 logx.Infof("退款申请成功,状态码=%d,退款单号=%s,微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId) return nil } // GenerateOutTradeNo 生成唯一订单号 func (w *WechatPayService) GenerateOutTradeNo() string { length := 16 timestamp := time.Now().UnixNano() timeStr := strconv.FormatInt(timestamp, 10) randomPart := strconv.Itoa(int(timestamp % 1e6)) combined := timeStr + randomPart if len(combined) >= length { return combined[:length] } for len(combined) < length { combined += strconv.Itoa(int(timestamp % 10)) } return combined }