Files
pay-bridge/backend/internal/service/service_fee.go
2026-03-13 15:51:59 +08:00

190 lines
5.1 KiB
Go

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))
}