package service import ( "context" "encoding/json" "errors" "fmt" "io" "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/google/uuid" "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.Config 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, 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().RegisterDownloaderWithClient(context.Background(), client, mchID, mchAPIv3Key) if err != nil { logx.Errorf("注册证书下载器失败: %v", err) panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) } // 初始化 notify.Handler // 使用 SHA256WithRSACombinedVerifier 同时支持平台证书和公钥验签 // 原因:微信回调目前仍使用平台证书签名,需要兼容处理;同时支持未来切换到公钥签名 certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID) notifyHandler := notify.NewNotifyHandler( mchAPIv3Key, verifiers.NewSHA256WithRSACombinedVerifier(certificateVisitor, mchPublicKeyID, *mchPublicKey)) logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)") return &WechatPayService{ config: c, 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.Wxpay.AppID), Mchid: core.String(w.config.Wxpay.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(w.config.Wxpay.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.WechatMini.AppID), Mchid: core.String(w.config.Wxpay.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(w.config.Wxpay.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 } // CreateWechatH5Order 创建微信H5支付订单 func (w *WechatPayService) CreateWechatH5Order(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.WechatH5.AppID), Mchid: core.String(w.config.Wxpay.MchID), Description: core.String(description), OutTradeNo: core.String(outTradeNo), NotifyUrl: core.String(w.config.Wxpay.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) logx.Infof("微信h5支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) 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, code string) (interface{}, error) { // 根据 ctx 中的 platform 判断平台(请求头 X-Platform: wxmini / wxh5 / app) platformValue := ctx.Value("platform") if platformValue == nil { return "", fmt.Errorf("平台信息不存在,请检查请求头 X-Platform") } platform, ok := platformValue.(string) if !ok { return "", fmt.Errorf("平台信息格式错误") } var prepayData interface{} var err error switch platform { case model.PlatformWxMini: userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID) if findAuthModelErr != nil { if errors.Is(findAuthModelErr, model.ErrNotFound) { // 用户未绑定微信,尝试通过 code 自动绑定 if code == "" { return "", fmt.Errorf("用户未绑定微信小程序账号,请先完成微信登录或提供授权码") } // 通过 code 获取 OpenID openid, getOpenidErr := w.getWechatMiniOpenID(ctx, code) if getOpenidErr != nil { return "", fmt.Errorf("获取微信小程序OpenID失败: %v", getOpenidErr) } // 自动绑定微信账号 bindErr := w.bindWechatAuth(ctx, userID, model.UserAuthTypeWxMiniOpenID, openid) if bindErr != nil { return "", fmt.Errorf("绑定微信小程序账号失败: %v", bindErr) } // 重新查询绑定后的认证信息 userAuthModel, findAuthModelErr = w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID) if findAuthModelErr != nil { return "", fmt.Errorf("查询绑定后的微信认证信息失败: %v", findAuthModelErr) } logx.Infof("用户 %s 已自动绑定微信小程序账号,OpenID: %s", userID, openid) } else { return "", fmt.Errorf("查询用户微信认证信息失败: %v", findAuthModelErr) } } prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) if err != nil { return "", err } case model.PlatformWxH5: userID, getUidErr := ctxdata.GetUidFromCtx(ctx) if getUidErr != nil { return "", getUidErr } userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) if findAuthModelErr != nil { if errors.Is(findAuthModelErr, model.ErrNotFound) { // 用户未绑定微信,尝试通过 code 自动绑定 if code == "" { return "", fmt.Errorf("用户未绑定微信H5账号,请先完成微信登录或提供授权码") } // 通过 code 获取 OpenID openid, getOpenidErr := w.getWechatH5OpenID(ctx, code) if getOpenidErr != nil { return "", fmt.Errorf("获取微信H5 OpenID失败: %v", getOpenidErr) } // 自动绑定微信账号 bindErr := w.bindWechatAuth(ctx, userID, model.UserAuthTypeWxh5OpenID, openid) if bindErr != nil { return "", fmt.Errorf("绑定微信H5账号失败: %v", bindErr) } // 重新查询绑定后的认证信息 userAuthModel, findAuthModelErr = w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) if findAuthModelErr != nil { return "", fmt.Errorf("查询绑定后的微信认证信息失败: %v", findAuthModelErr) } logx.Infof("用户 %s 已自动绑定微信H5账号,OpenID: %s", userID, openid) } else { return "", fmt.Errorf("查询用户微信认证信息失败: %v", findAuthModelErr) } } prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey) if err != nil { return "", err } case model.PlatformApp: // 如果是 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.Wxpay.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.Wxpay.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 } // getWechatMiniOpenID 通过 code 获取微信小程序 OpenID func (w *WechatPayService) getWechatMiniOpenID(ctx context.Context, code string) (string, error) { appID := w.config.WechatMini.AppID appSecret := w.config.WechatMini.AppSecret url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code) resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("请求微信API失败: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取响应失败: %v", err) } var data struct { Openid string `json:"openid"` ErrCode int `json:"errcode,omitempty"` ErrMsg string `json:"errmsg,omitempty"` } if err := json.Unmarshal(body, &data); err != nil { return "", fmt.Errorf("解析响应失败: %v", err) } if data.ErrCode != 0 { return "", fmt.Errorf("微信API返回错误: errcode=%d, errmsg=%s", data.ErrCode, data.ErrMsg) } if data.Openid == "" { return "", fmt.Errorf("openid为空") } return data.Openid, nil } // getWechatH5OpenID 通过 code 获取微信H5 OpenID func (w *WechatPayService) getWechatH5OpenID(ctx context.Context, code string) (string, error) { appID := w.config.WechatH5.AppID appSecret := w.config.WechatH5.AppSecret url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code) resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("请求微信API失败: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取响应失败: %v", err) } var data struct { Openid string `json:"openid"` ErrCode int `json:"errcode,omitempty"` ErrMsg string `json:"errmsg,omitempty"` } if err := json.Unmarshal(body, &data); err != nil { return "", fmt.Errorf("解析响应失败: %v", err) } if data.ErrCode != 0 { return "", fmt.Errorf("微信API返回错误: errcode=%d, errmsg=%s", data.ErrCode, data.ErrMsg) } if data.Openid == "" { return "", fmt.Errorf("openid为空") } return data.Openid, nil } // bindWechatAuth 绑定微信认证信息到用户账号 func (w *WechatPayService) bindWechatAuth(ctx context.Context, userID string, authType string, openid string) error { // 检查该 OpenID 是否已被其他用户绑定 existingAuth, err := w.userAuthModel.FindOneByAuthTypeAuthKey(ctx, authType, openid) if err != nil && !errors.Is(err, model.ErrNotFound) { return fmt.Errorf("查询认证信息失败: %v", err) } if existingAuth != nil { // 如果 OpenID 已被其他用户绑定,检查是否是当前用户 if existingAuth.UserId != userID { return fmt.Errorf("该微信账号已被其他用户绑定") } // 如果已经是当前用户绑定的,直接返回成功 return nil } // 创建新的认证记录 userAuth := &model.UserAuth{ Id: uuid.NewString(), UserId: userID, AuthType: authType, AuthKey: openid, } _, err = w.userAuthModel.Insert(ctx, nil, userAuth) if err != nil { return fmt.Errorf("创建认证记录失败: %v", err) } return nil }