package service import ( "context" "fmt" "log/slog" "math" "pay-bridge/internal/channel" "pay-bridge/internal/model" "pay-bridge/internal/repository" ) // ServiceFeeService 服务费服务 type ServiceFeeService struct { feeRepo *repository.ServiceFeeRepository tradeRepo *repository.TradeOrderRepository channelSvc *ChannelService } func NewServiceFeeService( feeRepo *repository.ServiceFeeRepository, tradeRepo *repository.TradeOrderRepository, channelSvc *ChannelService, ) *ServiceFeeService { return &ServiceFeeService{ feeRepo: feeRepo, tradeRepo: tradeRepo, channelSvc: channelSvc, } } // ChargeServiceFee 交易完成后扣收服务费 func (s *ServiceFeeService) ChargeServiceFee(ctx context.Context, tradeNo string) error { order, err := s.tradeRepo.GetByTradeNo(ctx, tradeNo) if err != nil || order == nil { return fmt.Errorf("order not found: %s", tradeNo) } // 幂等检查 existing, err := s.feeRepo.GetLog(ctx, tradeNo, "CHARGE") if err != nil { return err } if existing != nil { return nil // 已扣收 } // 获取服务费配置 group := model.PayMethodToGroup(order.PayMethod) cfg, err := s.feeRepo.GetConfig(ctx, order.AppID, group) if err != nil { return err } if cfg == nil || cfg.FeeRate == 0 { return nil // 未配置或费率为0 } // 计算服务费(四舍五入到分) feeAmount := calculateFee(order.Amount, cfg.FeeRate) if feeAmount <= 0 { return nil // 不足1分不扣收 } // 更新订单服务费金额快照 s.tradeRepo.UpdateStatus(ctx, tradeNo, model.TradeStatusPaid, model.TradeStatusPaid, map[string]any{"service_fee_amount": feeAmount}) // 创建服务费流水 log := &model.ServiceFeeLog{ TradeNo: tradeNo, ConfigID: cfg.ID, FeeAmount: feeAmount, FeeRate: cfg.FeeRate, ReceiverMerchantID: cfg.FeeReceiverMerchantID, Action: "CHARGE", Status: "PENDING", } if err := s.feeRepo.CreateLog(ctx, log); err != nil { return err } // 调用渠道分账 ch, err := s.channelSvc.GetChannel(ctx, order.AppID, order.ChannelCode) if err != nil { s.feeRepo.UpdateLogStatus(ctx, log.ID, "FAILED", "") return err } resp, err := ch.ProfitSharing(ctx, &channel.ProfitSharingReq{ TradeNo: tradeNo, ChannelTradeNo: order.ChannelTradeNo, SharingNo: fmt.Sprintf("FEE%s", tradeNo), ReceiverMerchantID: cfg.FeeReceiverMerchantID, Amount: feeAmount, }) if err != nil { s.feeRepo.UpdateLogStatus(ctx, log.ID, "FAILED", "") slog.WarnContext(ctx, "charge service fee failed", "trade_no", tradeNo, "err", err) return err } s.feeRepo.UpdateLogStatus(ctx, log.ID, "SUCCESS", resp.ChannelSharingNo) slog.InfoContext(ctx, "service fee charged", "trade_no", tradeNo, "fee_amount", feeAmount) return nil } // RollbackServiceFee 退款时回退服务费 func (s *ServiceFeeService) RollbackServiceFee(ctx context.Context, tradeNo string) error { // 幂等检查 existing, err := s.feeRepo.GetLog(ctx, tradeNo, "ROLLBACK") if err != nil { return err } if existing != nil { return nil // 已回退 } chargeLog, err := s.feeRepo.GetLog(ctx, tradeNo, "CHARGE") if err != nil { return err } if chargeLog == nil || chargeLog.Status != "SUCCESS" { return nil // 没有成功扣收,无需回退 } order, err := s.tradeRepo.GetByTradeNo(ctx, tradeNo) if err != nil || order == nil { return fmt.Errorf("order not found: %s", tradeNo) } rollbackLog := &model.ServiceFeeLog{ TradeNo: tradeNo, ConfigID: chargeLog.ConfigID, FeeAmount: chargeLog.FeeAmount, FeeRate: chargeLog.FeeRate, ReceiverMerchantID: chargeLog.ReceiverMerchantID, Action: "ROLLBACK", Status: "PENDING", } if err := s.feeRepo.CreateLog(ctx, rollbackLog); err != nil { return err } ch, err := s.channelSvc.GetChannel(ctx, order.AppID, order.ChannelCode) if err != nil { return err } sharingNo := fmt.Sprintf("FEE%s", tradeNo) if err := ch.RollbackProfitSharing(ctx, &channel.RollbackSharingReq{ SharingNo: sharingNo, ChannelSharingNo: chargeLog.ChannelSharingNo, TradeNo: tradeNo, }); err != nil { s.feeRepo.UpdateLogStatus(ctx, rollbackLog.ID, "FAILED", "") return err } s.feeRepo.UpdateLogStatus(ctx, rollbackLog.ID, "SUCCESS", "") return nil } // CalculateAndValidate 下单时校验分润+服务费不超过订单金额 func (s *ServiceFeeService) CalculateAndValidate(ctx context.Context, appID string, payMethod model.PayMethod, orderAmount, sharingAmount int64) (int64, error) { group := model.PayMethodToGroup(payMethod) cfg, err := s.feeRepo.GetConfig(ctx, appID, group) if err != nil { return 0, err } var feeAmount int64 if cfg != nil && cfg.FeeRate > 0 { feeAmount = calculateFee(orderAmount, cfg.FeeRate) } if sharingAmount+feeAmount > orderAmount { return 0, fmt.Errorf(errSharingFeeExceed) } return feeAmount, nil } const errSharingFeeExceed = "30007" // errcode.ErrSharingFeeExceed // calculateFee 计算服务费(四舍五入到分) func calculateFee(amount int64, rate float64) int64 { fee := float64(amount) * rate return int64(math.Round(fee)) }