package wechat import ( "context" "net/http" "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models" "github.com/ArtisanCloud/PowerWeChat/v3/src/payment" "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/notify/request" orderRequest "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/order/request" "github.com/break/junhong_cmp_fiber/pkg/errors" "go.uber.org/zap" ) // PaymentService 微信支付服务实现 type PaymentService struct { app *payment.Payment logger *zap.Logger } // NewPaymentService 创建微信支付服务 func NewPaymentService(app *payment.Payment, logger *zap.Logger) *PaymentService { return &PaymentService{ app: app, logger: logger, } } // JSAPIPayResult JSAPI 支付结果 type JSAPIPayResult struct { PrepayID string `json:"prepay_id"` PayConfig interface{} `json:"pay_config"` } // H5PayResult H5 支付结果 type H5PayResult struct { H5URL string `json:"h5_url"` } // OrderInfo 订单信息 type OrderInfo struct { TransactionID string `json:"transaction_id"` OutTradeNo string `json:"out_trade_no"` TradeState string `json:"trade_state"` TradeStateDesc string `json:"trade_state_desc"` SuccessTime string `json:"success_time"` TradeType string `json:"trade_type"` BankType string `json:"bank_type"` Attach string `json:"attach"` PayerOpenID string `json:"payer_openid"` TotalAmount int64 `json:"total_amount"` PayerTotal int64 `json:"payer_total"` Currency string `json:"currency"` } // PaymentNotifyResult 支付通知结果 type PaymentNotifyResult struct { TransactionID string `json:"transaction_id"` OutTradeNo string `json:"out_trade_no"` TradeState string `json:"trade_state"` SuccessTime string `json:"success_time"` PayerOpenID string `json:"payer_openid"` TotalAmount int64 `json:"total_amount"` Attach string `json:"attach"` } // CreateJSAPIOrder 创建 JSAPI 支付订单 func (s *PaymentService) CreateJSAPIOrder(ctx context.Context, orderNo, description, openID string, amount int) (*JSAPIPayResult, error) { if orderNo == "" || openID == "" || amount <= 0 { return nil, errors.New(errors.CodeInvalidParam, "订单号、OpenID 和金额不能为空") } resp, err := s.app.Order.JSAPITransaction(ctx, &orderRequest.RequestJSAPIPrepay{ Description: description, OutTradeNo: orderNo, Amount: &orderRequest.JSAPIAmount{ Total: amount, Currency: "CNY", }, Payer: &orderRequest.JSAPIPayer{ OpenID: openID, }, }) if err != nil { s.logger.Error("创建 JSAPI 支付订单失败", zap.String("order_no", orderNo), zap.Error(err), ) return nil, errors.Wrap(errors.CodeWechatPayFailed, err) } if resp == nil || resp.PrepayID == "" { s.logger.Error("创建 JSAPI 支付订单失败:空 PrepayID", zap.String("order_no", orderNo)) return nil, errors.New(errors.CodeWechatPayFailed, "创建支付订单失败") } payConfig, err := s.app.JSSDK.BridgeConfig(resp.PrepayID, false) if err != nil { s.logger.Error("生成支付配置失败", zap.String("order_no", orderNo), zap.Error(err), ) return nil, errors.Wrap(errors.CodeWechatPayFailed, err) } s.logger.Info("创建 JSAPI 支付订单成功", zap.String("order_no", orderNo), zap.String("prepay_id", resp.PrepayID), ) return &JSAPIPayResult{ PrepayID: resp.PrepayID, PayConfig: payConfig, }, nil } // CreateH5Order 创建 H5 支付订单 func (s *PaymentService) CreateH5Order(ctx context.Context, orderNo, description string, amount int, sceneInfo *H5SceneInfo) (*H5PayResult, error) { if orderNo == "" || amount <= 0 { return nil, errors.New(errors.CodeInvalidParam, "订单号和金额不能为空") } req := &orderRequest.RequestH5Prepay{ Description: description, OutTradeNo: orderNo, Amount: &orderRequest.H5Amount{ Total: amount, Currency: "CNY", }, } if sceneInfo != nil { req.SceneInfo = &orderRequest.H5SceneInfo{ PayerClientIP: sceneInfo.PayerClientIP, H5Info: &orderRequest.H5H5Info{ Type: sceneInfo.H5Type, }, } } resp, err := s.app.Order.TransactionH5(ctx, req) if err != nil { s.logger.Error("创建 H5 支付订单失败", zap.String("order_no", orderNo), zap.Error(err), ) return nil, errors.Wrap(errors.CodeWechatPayFailed, err) } if resp == nil || resp.H5URL == "" { s.logger.Error("创建 H5 支付订单失败:空 H5URL", zap.String("order_no", orderNo)) return nil, errors.New(errors.CodeWechatPayFailed, "创建 H5 支付订单失败") } s.logger.Info("创建 H5 支付订单成功", zap.String("order_no", orderNo), zap.String("h5_url", resp.H5URL), ) return &H5PayResult{ H5URL: resp.H5URL, }, nil } // H5SceneInfo H5 支付场景信息 type H5SceneInfo struct { PayerClientIP string `json:"payer_client_ip"` H5Type string `json:"h5_type"` } // QueryOrder 查询订单 func (s *PaymentService) QueryOrder(ctx context.Context, orderNo string) (*OrderInfo, error) { if orderNo == "" { return nil, errors.New(errors.CodeInvalidParam, "订单号不能为空") } resp, err := s.app.Order.QueryByOutTradeNumber(ctx, orderNo) if err != nil { s.logger.Error("查询订单失败", zap.String("order_no", orderNo), zap.Error(err), ) return nil, errors.Wrap(errors.CodeWechatPayFailed, err) } if resp == nil { return nil, errors.New(errors.CodeNotFound, "订单不存在") } orderInfo := &OrderInfo{ TransactionID: resp.TransactionID, OutTradeNo: resp.OutTradeNo, TradeState: resp.TradeState, TradeStateDesc: resp.TradeStateDesc, SuccessTime: resp.SuccessTime, TradeType: resp.TradeType, BankType: resp.BankType, Attach: resp.Attach, } if resp.Amount != nil { orderInfo.TotalAmount = resp.Amount.Total orderInfo.PayerTotal = resp.Amount.PayerTotal orderInfo.Currency = resp.Amount.Currency } if resp.Payer != nil { orderInfo.PayerOpenID = resp.Payer.OpenID } s.logger.Debug("查询订单成功", zap.String("order_no", orderNo), zap.String("trade_state", resp.TradeState), ) return orderInfo, nil } // CloseOrder 关闭订单 func (s *PaymentService) CloseOrder(ctx context.Context, orderNo string) error { if orderNo == "" { return errors.New(errors.CodeInvalidParam, "订单号不能为空") } _, err := s.app.Order.Close(ctx, orderNo) if err != nil { s.logger.Error("关闭订单失败", zap.String("order_no", orderNo), zap.Error(err), ) return errors.Wrap(errors.CodeWechatPayFailed, err) } s.logger.Info("关闭订单成功", zap.String("order_no", orderNo)) return nil } // PaymentNotifyCallback 支付通知回调函数 type PaymentNotifyCallback func(result *PaymentNotifyResult) error // HandlePaymentNotify 处理支付回调通知 func (s *PaymentService) HandlePaymentNotify(r *http.Request, callback PaymentNotifyCallback) (*http.Response, error) { return s.app.HandlePaidNotify(r, func(notify *request.RequestNotify, transaction *models.Transaction, fail func(message string)) interface{} { if transaction == nil { s.logger.Error("支付通知数据为空") fail("支付通知数据为空") return nil } result := &PaymentNotifyResult{ OutTradeNo: transaction.OutTradeNo, TradeState: transaction.TradeState, SuccessTime: transaction.SuccessTime, Attach: transaction.Attach, } result.TransactionID = transaction.TransactionID if transaction.Payer != nil { result.PayerOpenID = transaction.Payer.OpenID } if transaction.Amount != nil { result.TotalAmount = transaction.Amount.Total } if err := callback(result); err != nil { s.logger.Error("处理支付通知回调失败", zap.String("out_trade_no", result.OutTradeNo), zap.Error(err), ) fail(err.Error()) return nil } s.logger.Info("支付通知处理成功", zap.String("out_trade_no", result.OutTradeNo), zap.String("transaction_id", result.TransactionID), ) return true }) }