package service import ( "context" "crypto/rsa" "dianshang/internal/config" "dianshang/internal/model" "dianshang/internal/repository" "dianshang/pkg/logger" "dianshang/pkg/utils" "encoding/json" "fmt" "log" "strconv" "time" "github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" wechatutils "github.com/wechatpay-apiv3/wechatpay-go/utils" ) type WeChatPayService struct { config *config.WeChatPayConfig client *core.Client jsapiSvc *jsapi.JsapiApiService nativeSvc *native.NativeApiService refundSvc *refunddomestic.RefundsApiService privateKey *rsa.PrivateKey orderRepo *repository.OrderRepository refundSvcRef *RefundService } func NewWeChatPayService(cfg *config.WeChatPayConfig, orderRepo *repository.OrderRepository, refundService *RefundService) (*WeChatPayService, error) { // 检查是否为沙盒环境 if cfg.Environment == "sandbox" { logger.Info("微信支付配置为沙盒模式,将使用模拟支付") return &WeChatPayService{ config: cfg, orderRepo: orderRepo, refundSvcRef: refundService, }, nil } // 生产环境:加载商户私钥 privateKey, err := wechatutils.LoadPrivateKeyWithPath(cfg.KeyPath) if err != nil { logger.Warn("加载商户私钥失败,将使用模拟模式", "error", err) // 在开发环境下允许没有私钥,使用模拟模式 return &WeChatPayService{ config: cfg, orderRepo: orderRepo, refundSvcRef: refundService, }, nil } ctx := context.Background() // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 opts := []core.ClientOption{ option.WithWechatPayAutoAuthCipher(cfg.MchID, cfg.SerialNo, privateKey, cfg.APIv3Key), } client, err := core.NewClient(ctx, opts...) if err != nil { logger.Warn("初始化微信支付客户端失败,将使用模拟模式", "error", err) // 在开发环境下允许客户端初始化失败,使用模拟模式 return &WeChatPayService{ config: cfg, orderRepo: orderRepo, refundSvcRef: refundService, }, nil } // 创建JSAPI服务 jsapiSvc := &jsapi.JsapiApiService{Client: client} // 创建Native扫码支付服务 nativeSvc := &native.NativeApiService{Client: client} // 创建退款服务 refundSvc := &refunddomestic.RefundsApiService{Client: client} logger.Info("微信支付客户端初始化成功", "mchId", cfg.MchID, "serialNo", cfg.SerialNo, "environment", cfg.Environment) return &WeChatPayService{ config: cfg, client: client, jsapiSvc: jsapiSvc, nativeSvc: nativeSvc, refundSvc: refundSvc, privateKey: privateKey, orderRepo: orderRepo, refundSvcRef: refundService, }, nil } // CreateOrder 创建支付订单 func (s *WeChatPayService) CreateOrder(ctx context.Context, order *model.Order, openID string) (*WeChatPayResponse, error) { // 生成唯一的微信支付订单号 wechatOutTradeNo := utils.GenerateWechatOutTradeNo() logger.Info("开始创建微信支付订单", "orderNo", order.OrderNo, "wechatOutTradeNo", wechatOutTradeNo, "openID", openID, "totalAmount", order.TotalAmount, "hasClient", s.client != nil) // 更新订单的微信支付订单号 err := s.orderRepo.UpdateByOrderNo(order.OrderNo, map[string]interface{}{ "wechat_out_trade_no": wechatOutTradeNo, "updated_at": time.Now(), }) if err != nil { logger.Error("更新订单微信支付订单号失败", "error", err, "orderNo", order.OrderNo) return nil, fmt.Errorf("更新订单失败: %v", err) } // 如果没有客户端(开发环境),使用模拟数据 if s.client == nil { logger.Warn("开发环境下使用模拟支付数据") return s.createMockPayment(order, openID) } // 构建预支付请求,使用唯一的微信支付订单号 req := jsapi.PrepayRequest{ Appid: core.String(s.config.AppID), Mchid: core.String(s.config.MchID), Description: core.String(fmt.Sprintf("订单号: %s", order.OrderNo)), OutTradeNo: core.String(wechatOutTradeNo), // 使用唯一的微信支付订单号 NotifyUrl: core.String(s.config.NotifyURL), Amount: &jsapi.Amount{ Total: core.Int64(int64(order.TotalAmount)), // 金额已经是分为单位,无需转换 Currency: core.String("CNY"), }, Payer: &jsapi.Payer{ Openid: core.String(openID), }, } // 使用PrepayWithRequestPayment方法,直接获取调起支付的参数 resp, result, err := s.jsapiSvc.PrepayWithRequestPayment(ctx, req) if err != nil { log.Printf("call PrepayWithRequestPayment err:%s", err) logger.Error("创建支付订单失败", "error", err, "orderNo", order.OrderNo) return nil, fmt.Errorf("创建支付订单失败: %v", err) } if result.Response.StatusCode != 200 { log.Printf("PrepayWithRequestPayment status=%d", result.Response.StatusCode) return nil, fmt.Errorf("预支付请求失败,状态码: %d", result.Response.StatusCode) } log.Printf("PrepayWithRequestPayment success, prepay_id=%s", *resp.PrepayId) logger.Info("微信支付API响应", "prepayId", *resp.PrepayId, "orderNo", order.OrderNo) // 直接使用SDK返回的支付参数 payParams := &MiniProgramPayParams{ AppID: *resp.Appid, TimeStamp: *resp.TimeStamp, NonceStr: *resp.NonceStr, Package: *resp.Package, SignType: *resp.SignType, PaySign: *resp.PaySign, } return &WeChatPayResponse{ Code: 0, Message: "success", Data: map[string]interface{}{ "payInfo": payParams, }, }, nil } // CreateNativeOrder 创建Native扫码支付订单(用于PC端) func (s *WeChatPayService) CreateNativeOrder(ctx context.Context, order *model.Order) (*WeChatPayResponse, error) { // 生成唯一的微信支付订单号 wechatOutTradeNo := utils.GenerateWechatOutTradeNo() logger.Info("开始创建Native扫码支付订单", "orderNo", order.OrderNo, "wechatOutTradeNo", wechatOutTradeNo, "totalAmount", order.TotalAmount, "hasClient", s.client != nil) // 更新订单的微信支付订单号 err := s.orderRepo.UpdateByOrderNo(order.OrderNo, map[string]interface{}{ "wechat_out_trade_no": wechatOutTradeNo, "updated_at": time.Now(), }) if err != nil { logger.Error("更新订单微信支付订单号失败", "error", err, "orderNo", order.OrderNo) return nil, fmt.Errorf("更新订单失败: %v", err) } // 如果没有客户端(开发环境),使用模拟数据 if s.client == nil { logger.Warn("开发环境下使用模拟Native支付数据") return s.createMockNativePayment(order) } // 构建Native预支付请求 req := native.PrepayRequest{ Appid: core.String(s.config.AppID), Mchid: core.String(s.config.MchID), Description: core.String(fmt.Sprintf("订单号: %s", order.OrderNo)), OutTradeNo: core.String(wechatOutTradeNo), NotifyUrl: core.String(s.config.NotifyURL), Amount: &native.Amount{ Total: core.Int64(int64(order.TotalAmount)), Currency: core.String("CNY"), }, } // 调用Native预支付API resp, result, err := s.nativeSvc.Prepay(ctx, req) if err != nil { log.Printf("call Native Prepay err:%s", err) logger.Error("创建Native支付订单失败", "error", err, "orderNo", order.OrderNo) return nil, fmt.Errorf("创建Native支付订单失败: %v", err) } if result.Response.StatusCode != 200 { log.Printf("Native Prepay status=%d", result.Response.StatusCode) return nil, fmt.Errorf("Native预支付请求失败,状态码: %d", result.Response.StatusCode) } log.Printf("Native Prepay success, code_url=%s", *resp.CodeUrl) logger.Info("微信Native支付API响应", "codeUrl", *resp.CodeUrl, "orderNo", order.OrderNo) return &WeChatPayResponse{ Code: 0, Message: "success", Data: map[string]interface{}{ "qrcode_url": *resp.CodeUrl, "order_no": order.OrderNo, "amount": order.TotalAmount, }, }, nil } // createMockNativePayment 创建模拟Native支付数据(开发环境使用) func (s *WeChatPayService) createMockNativePayment(order *model.Order) (*WeChatPayResponse, error) { // 生成模拟的二维码URL mockQRCodeURL := fmt.Sprintf("weixin://wxpay/bizpayurl?pr=mock_%s", utils.GenerateRandomString(10)) logger.Info("生成模拟Native支付参数", "environment", s.config.Environment, "qrcodeUrl", mockQRCodeURL, "orderNo", order.OrderNo, "totalAmount", order.TotalAmount) return &WeChatPayResponse{ Code: 0, Message: "模拟支付创建成功", Data: map[string]interface{}{ "qrcode_url": mockQRCodeURL, "order_no": order.OrderNo, "amount": order.TotalAmount, "sandbox": true, "tips": "这是模拟环境的Native支付,请使用测试接口模拟支付成功", }, }, nil } // createMockPayment 创建模拟支付数据(沙盒环境使用) func (s *WeChatPayService) createMockPayment(order *model.Order, openID string) (*WeChatPayResponse, error) { mockPrepayID := fmt.Sprintf("wx%d%s", time.Now().Unix(), generateNonceStr()[:8]) timestamp := strconv.FormatInt(time.Now().Unix(), 10) nonceStr := generateNonceStr() // 生成更真实的模拟签名 mockSign := fmt.Sprintf("sandbox_%s_%s", nonceStr[:16], timestamp) payParams := &MiniProgramPayParams{ AppID: s.config.AppID, TimeStamp: timestamp, NonceStr: nonceStr, Package: fmt.Sprintf("prepay_id=%s", mockPrepayID), SignType: "RSA", PaySign: mockSign, } logger.Info("生成沙盒支付参数", "environment", s.config.Environment, "prepayId", mockPrepayID, "orderNo", order.OrderNo, "openID", openID, "totalAmount", order.TotalAmount, "description", fmt.Sprintf("订单号: %s", order.OrderNo)) return &WeChatPayResponse{ Code: 0, Message: "沙盒支付创建成功", Data: map[string]interface{}{ "payInfo": payParams, "sandbox": true, "tips": "这是沙盒环境的模拟支付,可以直接调用成功", }, }, nil } // QueryOrder 查询订单 func (s *WeChatPayService) QueryOrder(ctx context.Context, orderNo string) (*model.Order, error) { logger.Info("开始查询订单", "orderNo", orderNo, "hasClient", s.client != nil, "environment", s.config.Environment) // 如果没有客户端(沙盒环境或开发环境),返回模拟数据 if s.client == nil { if s.config.Environment == "sandbox" { logger.Info("沙盒环境下返回模拟查询结果") } else { logger.Warn("开发环境下返回模拟查询结果") } // 模拟不同的支付状态,让测试更真实 var status int if time.Now().Unix()%3 == 0 { status = 1 // 未付款 } else { status = 2 // 已付款 } return &model.Order{ OrderNo: orderNo, TotalAmount: 100.0, // 模拟金额 Status: status, }, nil } // 首先从数据库获取订单信息 order, err := s.orderRepo.GetByOrderNo(orderNo) if err != nil { logger.Error("从数据库获取订单失败", "error", err, "orderNo", orderNo) return nil, fmt.Errorf("订单不存在: %v", err) } // 如果没有微信支付订单号,说明还没有创建过微信支付订单 if order.WechatOutTradeNo == "" { logger.Warn("订单尚未创建微信支付订单", "orderNo", orderNo) return order, nil } // 使用微信支付订单号查询微信支付状态 req := jsapi.QueryOrderByOutTradeNoRequest{ OutTradeNo: core.String(order.WechatOutTradeNo), Mchid: core.String(s.config.MchID), } resp, result, err := s.jsapiSvc.QueryOrderByOutTradeNo(ctx, req) if err != nil { log.Printf("call QueryOrderByOutTradeNo err:%s", err) logger.Error("查询微信支付订单失败", "error", err, "wechatOutTradeNo", order.WechatOutTradeNo) return nil, fmt.Errorf("查询微信支付订单失败: %v", err) } if result.Response.StatusCode != 200 { log.Printf("QueryOrderByOutTradeNo status=%d", result.Response.StatusCode) return nil, fmt.Errorf("查询微信支付订单失败,状态码: %d", result.Response.StatusCode) } log.Printf("QueryOrderByOutTradeNo success, resp=%+v", resp) logger.Info("查询微信支付订单成功", "orderNo", orderNo, "wechatOutTradeNo", order.WechatOutTradeNo, "tradeState", *resp.TradeState) // 更新订单的微信交易号和支付状态 wechatStatus := convertWeChatPayStatus(*resp.TradeState) updates := map[string]interface{}{ "updated_at": time.Now(), } // 如果有微信交易号,保存到数据库 if resp.TransactionId != nil { updates["wechat_transaction_id"] = *resp.TransactionId } // 如果微信支付状态是已支付,更新订单状态 if wechatStatus == 2 && order.Status == 1 { updates["status"] = 2 updates["pay_status"] = 1 updates["paid_at"] = time.Now() } // 更新订单信息 if len(updates) > 1 { // 除了updated_at还有其他字段需要更新 err = s.orderRepo.UpdateByOrderNo(orderNo, updates) if err != nil { logger.Error("更新订单微信支付信息失败", "error", err, "orderNo", orderNo) } } // 更新订单对象的状态和金额信息 order.TotalAmount = float64(*resp.Amount.Total) // 保持分为单位,与系统内部一致 order.Status = wechatStatus if resp.TransactionId != nil { order.WechatTransactionID = *resp.TransactionId } return order, nil } // HandleNotify 处理支付回调 func (s *WeChatPayService) HandleNotify(ctx context.Context, body []byte, headers map[string]string) (*WeChatPayNotify, error) { // 解析回调数据 var notify WeChatPayNotify if err := json.Unmarshal(body, ¬ify); err != nil { return nil, fmt.Errorf("解析回调数据失败: %v", err) } logger.Info("收到微信支付回调", "eventType", notify.EventType, "id", notify.ID, "algorithm", notify.Resource.Algorithm) // 解密resource中的数据 if notify.Resource.Ciphertext != "" { // 使用AEAD_AES_256_GCM算法解密 decryptedData, err := s.decryptNotifyResource( notify.Resource.Ciphertext, notify.Resource.Nonce, notify.Resource.AssociatedData, ) if err != nil { logger.Error("解密回调数据失败", "error", err) return nil, fmt.Errorf("解密回调数据失败: %v", err) } // 解析解密后的JSON数据 var paymentData WeChatPayNotifyData if err := json.Unmarshal(decryptedData, &paymentData); err != nil { logger.Error("解析解密数据失败", "error", err, "data", string(decryptedData)) return nil, fmt.Errorf("解析解密数据失败: %v", err) } notify.DecryptedData = &paymentData logger.Info("成功解密回调数据", "outTradeNo", paymentData.OutTradeNo, "transactionID", paymentData.TransactionID, "tradeState", paymentData.TradeState) } return ¬ify, nil } // decryptNotifyResource 解密回调通知中的resource数据 func (s *WeChatPayService) decryptNotifyResource(ciphertext, nonce, associatedData string) ([]byte, error) { // 使用wechatpay-go SDK提供的解密工具 plaintext, err := wechatutils.DecryptAES256GCM(s.config.APIv3Key, associatedData, nonce, ciphertext) if err != nil { return nil, fmt.Errorf("AES解密失败: %v", err) } return []byte(plaintext), nil } // ProcessPaymentSuccess 处理支付成功回调 func (s *WeChatPayService) ProcessPaymentSuccess(ctx context.Context, notify *WeChatPayNotify) error { if notify.EventType != "TRANSACTION.SUCCESS" { return fmt.Errorf("不是支付成功回调: %s", notify.EventType) } logger.Info("开始处理支付成功回调", "eventType", notify.EventType) // 解析回调数据中的订单信息 var orderNo string var transactionID string // 如果有解密数据,从中获取订单号 if notify.DecryptedData != nil { orderNo = notify.DecryptedData.OutTradeNo transactionID = notify.DecryptedData.TransactionID logger.Info("从解密数据中获取订单信息", "orderNo", orderNo, "transactionID", transactionID) } else { // 开发环境下,可能需要从Resource字段中解析 // 或者从其他地方获取订单号 logger.Warn("回调数据中没有解密数据,尝试从Resource字段获取") // 在开发环境下,我们可以尝试解析Resource中的数据 if notify.Resource.Ciphertext != "" { // 这里可以添加解密逻辑,但在开发环境下我们先跳过 logger.Info("Resource中有加密数据,但开发环境暂不解密") } // 如果无法获取订单号,我们可以从最近的订单中查找 // 这是一个临时的开发环境解决方案 logger.Warn("无法从回调数据中获取订单号,这可能是开发环境的模拟回调") return fmt.Errorf("无法从回调数据中获取订单号") } if orderNo == "" { return fmt.Errorf("回调数据中缺少订单号") } logger.Info("处理支付成功回调", "orderNo", orderNo) // 查询订单 order, err := s.orderRepo.GetOrderByWechatOutTradeNo(orderNo) if err != nil { logger.Error("根据微信订单号查询订单失败", "error", err, "wechatOutTradeNo", orderNo) return fmt.Errorf("订单不存在: %v", err) } // 检查订单状态,避免重复处理 if order.Status >= 2 { logger.Info("订单已经是已支付状态,跳过处理", "orderNo", order.OrderNo, "status", order.Status) return nil } logger.Info("开始更新订单状态", "orderNo", order.OrderNo, "currentStatus", order.Status) // 更新订单状态为已支付 updates := map[string]interface{}{ "status": 2, // 已支付 "pay_status": 1, // 已支付 "pay_time": time.Now(), "updated_at": time.Now(), } // 如果有微信交易号,也保存 if transactionID != "" { updates["wechat_transaction_id"] = transactionID logger.Info("保存微信交易号", "transactionID", transactionID) } err = s.orderRepo.UpdateByOrderNo(order.OrderNo, updates) if err != nil { logger.Error("更新订单支付状态失败", "error", err, "orderNo", order.OrderNo) return fmt.Errorf("更新订单状态失败: %v", err) } logger.Info("订单支付状态更新成功", "orderNo", order.OrderNo, "newStatus", 2) return nil } // ProcessPaymentSuccessByOrderNo 根据订单号手动处理支付成功(用于测试) func (s *WeChatPayService) ProcessPaymentSuccessByOrderNo(ctx context.Context, orderNo string) error { logger.Info("手动处理支付成功", "orderNo", orderNo) // 查询订单 order, err := s.orderRepo.GetByOrderNo(orderNo) if err != nil { logger.Error("查询订单失败", "error", err, "orderNo", orderNo) return fmt.Errorf("订单不存在: %v", err) } // 检查订单状态,避免重复处理 if order.Status >= 2 { logger.Info("订单已经是已支付状态,跳过处理", "orderNo", order.OrderNo, "status", order.Status) return nil } logger.Info("开始更新订单状态", "orderNo", order.OrderNo, "currentStatus", order.Status) // 更新订单状态为已支付 updates := map[string]interface{}{ "status": 2, // 已支付 "pay_status": 1, // 已支付 "pay_time": time.Now(), "updated_at": time.Now(), } err = s.orderRepo.UpdateByOrderNo(order.OrderNo, updates) if err != nil { logger.Error("更新订单支付状态失败", "error", err, "orderNo", order.OrderNo) return fmt.Errorf("更新订单状态失败: %v", err) } logger.Info("订单支付状态更新成功", "orderNo", order.OrderNo, "newStatus", 2) return nil } // CreateRefund 创建微信退款 func (s *WeChatPayService) CreateRefund(ctx context.Context, refundRecord *model.Refund, order *model.Order) (*WeChatRefundResponse, error) { logger.Info("开始创建微信退款", "refundNo", refundRecord.RefundNo, "orderNo", order.OrderNo, "refundAmount", refundRecord.RefundAmount, "hasClient", s.client != nil) // 如果没有客户端(开发环境),使用模拟数据 if s.client == nil { logger.Warn("开发环境下使用模拟退款数据") return s.createMockRefund(refundRecord, order) } // 构建退款请求 req := refunddomestic.CreateRequest{ OutTradeNo: core.String(order.WechatOutTradeNo), OutRefundNo: core.String(refundRecord.WechatOutRefundNo), Reason: core.String(refundRecord.RefundReason), FundsAccount: (*refunddomestic.ReqFundsAccount)(core.String("AVAILABLE")), // 可用余额退款 Amount: &refunddomestic.AmountReq{ Refund: core.Int64(int64(refundRecord.RefundAmount)), Total: core.Int64(int64(order.TotalAmount)), Currency: core.String("CNY"), }, } // 只有当RefundNotifyURL不为空时才设置NotifyUrl if s.config.RefundNotifyURL != "" { req.NotifyUrl = core.String(s.config.RefundNotifyURL) } // 如果有微信交易号,优先使用 if order.WechatTransactionID != "" { req.TransactionId = core.String(order.WechatTransactionID) req.OutTradeNo = nil // 使用微信交易号时,不需要商户订单号 } // 调用微信退款API resp, result, err := s.refundSvc.Create(ctx, req) if err != nil { log.Printf("call CreateRefund err:%s", err) logger.Error("创建微信退款失败", "error", err, "refundNo", refundRecord.RefundNo) return nil, fmt.Errorf("创建微信退款失败: %v", err) } if result.Response.StatusCode != 200 { log.Printf("CreateRefund status=%d", result.Response.StatusCode) return nil, fmt.Errorf("微信退款请求失败,状态码: %d", result.Response.StatusCode) } log.Printf("CreateRefund success, refund_id=%s", *resp.RefundId) logger.Info("微信退款API响应", "refundId", *resp.RefundId, "refundNo", refundRecord.RefundNo, "status", *resp.Status) return &WeChatRefundResponse{ Code: 0, Message: "success", Data: map[string]interface{}{ "refund_id": *resp.RefundId, "out_refund_no": *resp.OutRefundNo, "transaction_id": getStringValue(resp.TransactionId), "out_trade_no": getStringValue(resp.OutTradeNo), "channel": getChannelValue(resp.Channel), "user_received_account": getStringValue(resp.UserReceivedAccount), "success_time": getTimeValue(resp.SuccessTime), "create_time": getTimeValue(resp.CreateTime), "status": getStatusValue(*resp.Status), "funds_account": getFundsAccountValue(resp.FundsAccount), "amount": map[string]interface{}{ "total": *resp.Amount.Total, "refund": *resp.Amount.Refund, "payer_total": getInt64Value(resp.Amount.PayerTotal), "payer_refund": getInt64Value(resp.Amount.PayerRefund), "settlement_refund": getInt64Value(resp.Amount.SettlementRefund), "settlement_total": getInt64Value(resp.Amount.SettlementTotal), "discount_refund": getInt64Value(resp.Amount.DiscountRefund), "currency": *resp.Amount.Currency, }, }, }, nil } // createMockRefund 创建模拟退款数据(开发环境使用) func (s *WeChatPayService) createMockRefund(refundRecord *model.Refund, order *model.Order) (*WeChatRefundResponse, error) { logger.Info("创建模拟退款数据", "refundNo", refundRecord.RefundNo) // 生成模拟的微信退款ID mockRefundID := fmt.Sprintf("mock_refund_%d", time.Now().Unix()) return &WeChatRefundResponse{ Code: 0, Message: "success", Data: map[string]interface{}{ "refund_id": mockRefundID, "out_refund_no": refundRecord.WechatOutRefundNo, "transaction_id": order.WechatTransactionID, "out_trade_no": order.WechatOutTradeNo, "channel": "ORIGINAL", "user_received_account": "招商银行信用卡0403", "success_time": time.Now().Format("2006-01-02T15:04:05+08:00"), "create_time": time.Now().Format("2006-01-02T15:04:05+08:00"), "status": "SUCCESS", "funds_account": "AVAILABLE", "amount": map[string]interface{}{ "total": int64(order.TotalAmount), "refund": int64(refundRecord.RefundAmount), "payer_total": int64(order.TotalAmount), "payer_refund": int64(refundRecord.RefundAmount), "settlement_refund": int64(refundRecord.RefundAmount), "settlement_total": int64(order.TotalAmount), "discount_refund": int64(0), "currency": "CNY", }, }, }, nil } // QueryRefund 查询微信退款状态 func (s *WeChatPayService) QueryRefund(ctx context.Context, outRefundNo string) (*model.Refund, error) { logger.Info("查询微信退款状态", "outRefundNo", outRefundNo) // 如果没有客户端(开发环境),返回模拟数据 if s.client == nil { logger.Warn("开发环境下使用模拟退款查询") return s.queryMockRefund(outRefundNo) } // 构建查询请求 req := refunddomestic.QueryByOutRefundNoRequest{ OutRefundNo: core.String(outRefundNo), } // 调用微信查询退款API resp, result, err := s.refundSvc.QueryByOutRefundNo(ctx, req) if err != nil { log.Printf("call QueryRefund err:%s", err) logger.Error("查询微信退款失败", "error", err, "outRefundNo", outRefundNo) return nil, fmt.Errorf("查询微信退款失败: %v", err) } if result.Response.StatusCode != 200 { log.Printf("QueryRefund status=%d", result.Response.StatusCode) return nil, fmt.Errorf("查询微信退款失败,状态码: %d", result.Response.StatusCode) } log.Printf("QueryRefund success, resp=%+v", resp) logger.Info("查询微信退款成功", "outRefundNo", outRefundNo, "refundId", *resp.RefundId, "status", *resp.Status) // 构建返回的退款记录(这里只是示例,实际应该从数据库获取完整记录) refundRecord := &model.Refund{ WechatRefundID: *resp.RefundId, WechatOutRefundNo: *resp.OutRefundNo, WechatRefundStatus: getStatusValue(*resp.Status), WechatUserReceivedAccount: getStringValue(resp.UserReceivedAccount), WechatRefundAccount: getFundsAccountValue(resp.FundsAccount), } // 如果退款成功,设置成功时间 if getStatusValue(*resp.Status) == "SUCCESS" && resp.SuccessTime != nil { successTime, err := time.Parse("2006-01-02T15:04:05+08:00", getTimeValue(resp.SuccessTime)) if err == nil { refundRecord.WechatSuccessTime = &successTime } } return refundRecord, nil } // queryMockRefund 查询模拟退款数据(开发环境使用) func (s *WeChatPayService) queryMockRefund(outRefundNo string) (*model.Refund, error) { logger.Info("查询模拟退款数据", "outRefundNo", outRefundNo) now := time.Now() return &model.Refund{ WechatRefundID: fmt.Sprintf("mock_refund_%d", now.Unix()), WechatOutRefundNo: outRefundNo, WechatRefundStatus: "SUCCESS", WechatUserReceivedAccount: "招商银行信用卡0403", WechatRefundAccount: "AVAILABLE", WechatSuccessTime: &now, }, nil } // HandleRefundNotify 处理微信退款回调 func (s *WeChatPayService) HandleRefundNotify(ctx context.Context, body []byte, headers map[string]string) (*WeChatRefundNotify, error) { // 解析回调数据 var notify WeChatRefundNotify if err := json.Unmarshal(body, ¬ify); err != nil { return nil, fmt.Errorf("解析退款回调数据失败: %v", err) } logger.Info("收到微信退款回调", "eventType", notify.EventType, "id", notify.ID, "algorithm", notify.Resource.Algorithm) // 解密resource中的数据 if notify.Resource.Ciphertext != "" { // 使用AEAD_AES_256_GCM算法解密 decryptedData, err := s.decryptNotifyResource( notify.Resource.Ciphertext, notify.Resource.Nonce, notify.Resource.AssociatedData, ) if err != nil { logger.Error("解密退款回调数据失败", "error", err) return nil, fmt.Errorf("解密退款回调数据失败: %v", err) } // 解析解密后的JSON数据 var refundData WeChatRefundNotifyData if err := json.Unmarshal(decryptedData, &refundData); err != nil { logger.Error("解析解密数据失败", "error", err, "data", string(decryptedData)) return nil, fmt.Errorf("解析解密数据失败: %v", err) } notify.DecryptedData = &refundData logger.Info("成功解密退款回调数据", "outRefundNo", refundData.OutRefundNo, "refundId", refundData.RefundId, "refundStatus", refundData.RefundStatus) } return ¬ify, nil } // ProcessRefundSuccess 处理退款成功回调 func (s *WeChatPayService) ProcessRefundSuccess(ctx context.Context, notify *WeChatRefundNotify) error { if notify.EventType != "REFUND.SUCCESS" { return fmt.Errorf("不是退款成功回调: %s", notify.EventType) } logger.Info("开始处理退款成功回调", "eventType", notify.EventType) // 解析回调数据中的退款信息 var outRefundNo string var refundID string // 如果有解密数据,从中获取退款单号 if notify.DecryptedData != nil { outRefundNo = notify.DecryptedData.OutRefundNo refundID = notify.DecryptedData.RefundId logger.Info("从解密数据中获取退款信息", "outRefundNo", outRefundNo, "refundId", refundID) } else { logger.Warn("退款回调数据中没有解密数据") return fmt.Errorf("无法从回调数据中获取退款单号") } if outRefundNo == "" { return fmt.Errorf("回调数据中缺少退款单号") } logger.Info("处理退款成功回调", "outRefundNo", outRefundNo) // 这里应该调用退款服务来更新退款状态 // 由于这是在微信支付服务中,我们只记录日志,实际更新由退款服务处理 logger.Info("退款成功回调处理完成", "outRefundNo", outRefundNo, "refundId", refundID) return nil } // 辅助函数 func getStringValue(ptr *string) string { if ptr == nil { return "" } return *ptr } func getInt64Value(ptr *int64) int64 { if ptr == nil { return 0 } return *ptr } func getChannelValue(ptr *refunddomestic.Channel) string { if ptr == nil { return "" } return string(*ptr) } func getFundsAccountValue(ptr *refunddomestic.FundsAccount) string { if ptr == nil { return "" } return string(*ptr) } func getTimeValue(ptr *time.Time) string { if ptr == nil { return "" } return ptr.Format("2006-01-02T15:04:05+08:00") } func getStatusValue(status refunddomestic.Status) string { return string(status) } // generateNonceStr 生成随机字符串用于微信支付 func generateNonceStr() string { return utils.GenerateRandomString(32) } // convertWeChatPayStatus 将微信支付状态转换为订单状态 func convertWeChatPayStatus(wechatStatus string) int { switch wechatStatus { case "SUCCESS": return model.OrderStatusPaid case "REFUND": return model.OrderStatusRefunded case "NOTPAY": return model.OrderStatusPending case "CLOSED": return model.OrderStatusCancelled case "REVOKED": return model.OrderStatusCancelled case "USERPAYING": return model.OrderStatusPending case "PAYERROR": return model.OrderStatusCancelled default: return model.OrderStatusPending } } // 微信支付相关数据结构 type MiniProgramPayParams struct { AppID string `json:"appId"` TimeStamp string `json:"timeStamp"` NonceStr string `json:"nonceStr"` Package string `json:"package"` SignType string `json:"signType"` PaySign string `json:"paySign"` } type WeChatPayResponse struct { Code int `json:"code"` Message string `json:"message"` Data map[string]interface{} `json:"data"` } type WeChatPayNotify struct { ID string `json:"id"` CreateTime string `json:"create_time"` ResourceType string `json:"resource_type"` EventType string `json:"event_type"` Summary string `json:"summary"` Resource struct { OriginalType string `json:"original_type"` Algorithm string `json:"algorithm"` Ciphertext string `json:"ciphertext"` AssociatedData string `json:"associated_data"` Nonce string `json:"nonce"` } `json:"resource"` DecryptedData *WeChatPayNotifyData `json:"decrypted_data,omitempty"` } type WeChatPayNotifyData struct { MchID string `json:"mchid"` AppID string `json:"appid"` OutTradeNo string `json:"out_trade_no"` TransactionID string `json:"transaction_id"` TradeType string `json:"trade_type"` TradeState string `json:"trade_state"` BankType string `json:"bank_type"` SuccessTime string `json:"success_time"` Payer struct { OpenID string `json:"openid"` } `json:"payer"` Amount struct { Total int `json:"total"` PayerTotal int `json:"payer_total"` Currency string `json:"currency"` PayerCurrency string `json:"payer_currency"` } `json:"amount"` } type WeChatRefundResponse struct { Code int `json:"code"` Message string `json:"message"` Data map[string]interface{} `json:"data"` } type WeChatRefundNotify struct { ID string `json:"id"` CreateTime string `json:"create_time"` ResourceType string `json:"resource_type"` EventType string `json:"event_type"` Summary string `json:"summary"` Resource struct { OriginalType string `json:"original_type"` Algorithm string `json:"algorithm"` Ciphertext string `json:"ciphertext"` AssociatedData string `json:"associated_data"` Nonce string `json:"nonce"` } `json:"resource"` DecryptedData *WeChatRefundNotifyData `json:"decrypted_data,omitempty"` } type WeChatRefundNotifyData struct { MchID string `json:"mchid"` OutTradeNo string `json:"out_trade_no"` TransactionID string `json:"transaction_id"` OutRefundNo string `json:"out_refund_no"` RefundId string `json:"refund_id"` RefundStatus string `json:"refund_status"` SuccessTime string `json:"success_time"` UserReceivedAccount string `json:"user_received_account"` Amount struct { Total int `json:"total"` Refund int `json:"refund"` PayerTotal int `json:"payer_total"` PayerRefund int `json:"payer_refund"` } `json:"amount"` }