package payment import ( "context" "errors" paymentbiz "kra/internal/biz/payment" "strings" "time" "kra/internal/service/dto" ) type PaymentService struct{ uc *paymentbiz.PaymentUsecase } func NewPaymentService(uc *paymentbiz.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (*dto.PaymentOrderResponse, error) { order, err := s.uc.Order(ctx, provider, tradeNo) if err != nil { return nil, err } response, err := paymentOrderResponse(order) if err != nil { return nil, err } events, err := s.uc.OrderEvents(ctx, provider, tradeNo, 100) if err != nil { return nil, err } response.Events = paymentEventResponses(events) return response, nil } func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRequest) ([]*dto.PaymentOrderResponse, int64, int, int, error) { if req == nil { return nil, 0, 1, 10, errors.New("支付订单列表请求为空") } page, pageSize := req.Page, req.PageSize if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 10 } filter, err := paymentOrderFilter(req) if err != nil { return nil, 0, page, pageSize, err } items, total, err := s.uc.Orders(ctx, page, pageSize, filter) if err != nil { return nil, 0, page, pageSize, err } result := make([]*dto.PaymentOrderResponse, 0, len(items)) for _, item := range items { converted, convertErr := paymentOrderResponse(item) if convertErr != nil { return nil, 0, page, pageSize, convertErr } result = append(result, converted) } return result, total, page, pageSize, nil } func (s *PaymentService) OrderSummary(ctx context.Context, req *dto.PaymentOrderListRequest) (*dto.PaymentOrderSummaryResponse, error) { filter, err := paymentOrderFilter(req) if err != nil { return nil, err } summary, err := s.uc.OrderSummary(ctx, filter) if err != nil { return nil, err } response := &dto.PaymentOrderSummaryResponse{ OrderCount: summary.OrderCount, PendingCount: summary.PendingCount, PaidCount: summary.PaidCount, RefundedCount: summary.RefundedCount, IssueCount: summary.IssueCount, Currencies: make([]*dto.PaymentCurrencySummaryResponse, 0, len(summary.Currencies)), } for _, item := range summary.Currencies { response.Currencies = append(response.Currencies, &dto.PaymentCurrencySummaryResponse{ Currency: item.Currency, OrderAmount: item.OrderAmount, PaidAmount: item.PaidAmount, RefundedAmount: item.RefundedAmount, SettlementAmount: item.SettlementAmount, NetAmount: item.NetAmount, }) } return response, nil } func (s *PaymentService) OrderEvents(ctx context.Context, provider, tradeNo string, limit int) ([]*dto.PaymentEventResponse, error) { events, err := s.uc.OrderEvents(ctx, provider, tradeNo, limit) if err != nil { return nil, err } return paymentEventResponses(events), nil } func paymentOrderFilter(req *dto.PaymentOrderListRequest) (paymentbiz.PaymentOrderFilter, error) { if req == nil { return paymentbiz.PaymentOrderFilter{}, errors.New("支付订单筛选请求为空") } createdFrom, err := parsePaymentFilterTime(req.CreatedFrom, false) if err != nil { return paymentbiz.PaymentOrderFilter{}, err } createdTo, err := parsePaymentFilterTime(req.CreatedTo, true) if err != nil { return paymentbiz.PaymentOrderFilter{}, err } paidFrom, err := parsePaymentFilterTime(req.PaidFrom, false) if err != nil { return paymentbiz.PaymentOrderFilter{}, err } paidTo, err := parsePaymentFilterTime(req.PaidTo, true) if err != nil { return paymentbiz.PaymentOrderFilter{}, err } if createdFrom != nil && createdTo != nil && !createdFrom.Before(*createdTo) { return paymentbiz.PaymentOrderFilter{}, errors.New("订单创建时间范围无效") } if paidFrom != nil && paidTo != nil && !paidFrom.Before(*paidTo) { return paymentbiz.PaymentOrderFilter{}, errors.New("支付完成时间范围无效") } return paymentbiz.PaymentOrderFilter{ Provider: req.Provider, TradeNo: req.TradeNo, ProviderTradeNo: req.ProviderTradeNo, Keyword: req.Keyword, BusinessType: req.BusinessType, BusinessID: req.BusinessID, PaymentStatus: req.PaymentStatus, RefundStatus: req.RefundStatus, CreatedFrom: createdFrom, CreatedTo: createdTo, PaidFrom: paidFrom, PaidTo: paidTo, IssueOnly: req.IssueOnly, }, nil } func parsePaymentFilterTime(value string, endOfDate bool) (*time.Time, error) { value = strings.TrimSpace(value) if value == "" { return nil, nil } if parsed, err := time.Parse(time.RFC3339, value); err == nil { return &parsed, nil } parsed, err := time.ParseInLocation("2006-01-02", value, time.Local) if err != nil { return nil, errors.New("支付订单时间格式无效") } if endOfDate { parsed = parsed.AddDate(0, 0, 1) } return &parsed, nil } func paymentEventResponses(events []*paymentbiz.PaymentEvent) []*dto.PaymentEventResponse { result := make([]*dto.PaymentEventResponse, 0, len(events)) for _, event := range events { if event == nil { continue } result = append(result, &dto.PaymentEventResponse{ ID: event.ID, Type: event.Type, Source: event.Source, Status: event.Status, ProviderStatus: event.ProviderStatus, Message: event.Message, EventID: event.EventID, PayloadHash: event.PayloadHash, Amount: event.Amount, Currency: event.Currency, OperatorID: event.OperatorID, OperatorName: event.OperatorName, ClientIP: event.ClientIP, UserAgent: event.UserAgent, DeviceID: event.DeviceID, CreatedAt: event.CreatedAt, }) } return result } func operationContext(ctx context.Context, source, reason string, operatorID uint, operatorName, clientIP, userAgent, deviceID string) context.Context { return paymentbiz.WithPaymentOperation(ctx, paymentbiz.PaymentOperation{ Source: source, Reason: reason, OperatorID: operatorID, OperatorName: operatorName, ClientIP: clientIP, UserAgent: userAgent, DeviceID: deviceID, }) } func paymentOrderResponse(order *paymentbiz.PaymentOrder) (*dto.PaymentOrderResponse, error) { if order == nil { return nil, errors.New("支付订单为空") } return &dto.PaymentOrderResponse{ ID: order.ID, Provider: order.Provider, TradeNo: order.TradeNo, ProviderTradeNo: order.ProviderTradeNo, BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject, PaymentMode: order.PaymentMode, OriginalAmount: order.OriginalAmount, Amount: order.Amount, PaidAmount: order.PaidAmount, PayerPaidAmount: order.PayerPaidAmount, CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, DiscountAmount: order.DiscountAmount, ProviderDiscountAmount: order.ProviderDiscountAmount, MerchantDiscountAmount: order.MerchantDiscountAmount, SettlementAmount: order.SettlementAmount, Currency: order.Currency, PayerCurrency: order.PayerCurrency, AmountBreakdownKnown: order.AmountBreakdownKnown, PaymentStatus: order.PaymentStatus, ProviderStatus: order.ProviderStatus, FulfillmentStatus: order.FulfillmentStatus, RefundStatus: order.RefundStatus, RefundedAmount: order.RefundedAmount, RefundRequestedAmount: order.RefundRequestedAmount, RefundNo: order.RefundNo, LastError: order.LastError, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, PaidAt: order.PaidAt, FulfilledAt: order.FulfilledAt, RefundedAt: order.RefundedAt, }, nil } func (s *PaymentService) Create(ctx context.Context, req *dto.PaymentRequest) (*dto.PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付请求为空") } ctx = operationContext(ctx, "client", "客户端创建支付订单", 0, "", req.ClientIP, req.UserAgent, req.DeviceID) result, err := s.uc.Create(ctx, &paymentbiz.PaymentRequest{ Provider: req.Provider, TradeNo: req.TradeNo, Subject: req.Subject, Amount: req.Amount, OriginalAmount: req.OriginalAmount, Currency: req.Currency, NotifyURL: req.NotifyURL, ReturnURL: req.ReturnURL, ClientIP: req.ClientIP, Extra: req.Extra, BusinessType: req.BusinessType, BusinessID: req.BusinessID, PaymentMode: req.PaymentMode, UserAgent: req.UserAgent, DeviceID: req.DeviceID, }) return paymentResultResponse(result), err } func (s *PaymentService) Query(ctx context.Context, req *dto.PaymentQueryRequest) (*dto.PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付查询请求为空") } ctx = operationContext(ctx, "admin", "主动同步支付平台状态", req.OperatorID, req.OperatorName, req.ClientIP, req.UserAgent, req.DeviceID) result, err := s.uc.Query(ctx, req.Provider, req.TradeNo) return paymentResultResponse(result), err } func (s *PaymentService) Refund(ctx context.Context, req *dto.PaymentRefundRequest) (*dto.PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付退款请求为空") } reason := strings.TrimSpace(req.Reason) if reason == "" { reason = "未填写退款原因" } ctx = operationContext(ctx, "admin", reason, req.OperatorID, req.OperatorName, req.ClientIP, req.UserAgent, req.DeviceID) result, err := s.uc.Refund(ctx, req.Provider, req.TradeNo, req.Amount) return paymentResultResponse(result), err } func (s *PaymentService) TestProvider(ctx context.Context, provider string) (*dto.PaymentTestResult, error) { result, err := s.uc.TestProvider(ctx, provider) if result == nil { return nil, err } response := &dto.PaymentTestResult{Provider: result.Provider, TradeNo: result.TradeNo, Passed: result.Passed, FullFlow: result.FullFlow, Mode: result.Mode, Stages: make([]*dto.PaymentTestStage, 0, len(result.Stages))} for _, stage := range result.Stages { response.Stages = append(response.Stages, &dto.PaymentTestStage{Name: stage.Name, Status: stage.Status, Message: stage.Message, TradeNo: stage.TradeNo, Duration: stage.Duration}) } response.Result = paymentResultResponse(result.Result) return response, err } func (s *PaymentService) Fulfill(ctx context.Context, req *dto.PaymentFulfillRequest) (*dto.PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付发货请求为空") } ctx = operationContext(ctx, "admin", "人工执行或重试业务发货", req.OperatorID, req.OperatorName, req.ClientIP, req.UserAgent, req.DeviceID) result, err := s.uc.Fulfill(ctx, req.Provider, req.TradeNo) return paymentResultResponse(result), err } func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackRequest) (dto.PaymentCallbackAck, error) { if req == nil { return paymentCallbackAckResponse(paymentbiz.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") } ctx = operationContext(ctx, req.Provider, "支付平台异步通知", 0, "", req.ClientIP, req.UserAgent, "") result, err := s.uc.Callback(ctx, &paymentbiz.PaymentCallback{Provider: req.Provider, Headers: req.Headers, Body: req.Body, Query: req.Query}) if err != nil { return paymentCallbackAckResponse(paymentbiz.CallbackFailure(err, paymentbiz.DefaultPaymentCallbackAck(req.Provider, false))), err } ack := paymentbiz.DefaultPaymentCallbackAck(req.Provider, true) if result != nil && result.SuccessAck.StatusCode != 0 { ack = result.SuccessAck } return paymentCallbackAckResponse(ack), nil } func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { return paymentCallbackAckResponse(paymentbiz.DefaultPaymentCallbackAck(provider, success)) } func paymentResultResponse(result *paymentbiz.PaymentResult) *dto.PaymentResultResponse { if result == nil { return nil } return &dto.PaymentResultResponse{ Provider: result.Provider, Status: result.Status, TradeNo: result.TradeNo, ProviderTradeNo: result.ProviderTradeNo, Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount, PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount, ProviderDiscountAmount: result.ProviderDiscountAmount, MerchantDiscountAmount: result.MerchantDiscountAmount, SettlementAmount: result.SettlementAmount, Currency: result.Currency, PayerCurrency: result.PayerCurrency, AmountBreakdownKnown: result.AmountBreakdownKnown, Duplicate: result.Duplicate, Payload: result.Payload, OrderStatus: result.OrderStatus, FulfillmentStatus: result.FulfillmentStatus, RefundStatus: result.RefundStatus, } } func paymentCallbackAckResponse(ack paymentbiz.PaymentCallbackAck) dto.PaymentCallbackAck { return dto.PaymentCallbackAck{StatusCode: ack.StatusCode, ContentType: ack.ContentType, Body: ack.Body} }