Files
junhong_cmp_fiber/internal/service/recharge/service.go
huang 37f43d2e2d 重构: 将卡/设备的套餐系列绑定从分配ID改为系列ID
- 数据库: 重命名 series_allocation_id → series_id
- Model: IotCard 和 Device 字段重命名
- DTO: 所有请求/响应字段统一为 series_id
- Store: 方法重命名,新增 GetByShopAndSeries 查询
- Service: 业务逻辑优化,系列验证和权限验证分离
- 测试: 更新所有测试用例,新增 shop_series_allocation_store_test.go
- 文档: 更新 API 文档说明参数变更

BREAKING CHANGE: API 参数从 series_allocation_id 改为 series_id
2026-02-02 12:09:53 +08:00

728 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package recharge
import (
"context"
"fmt"
"math/rand"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"go.uber.org/zap"
"gorm.io/gorm"
)
// ForceRechargeRequirement 强充要求信息
type ForceRechargeRequirement struct {
NeedForceRecharge bool `json:"need_force_recharge"` // 是否需要强充
ForceRechargeAmount int64 `json:"force_recharge_amount"` // 强充金额(分)
TriggerType string `json:"trigger_type"` // 触发类型: single_recharge/accumulated_recharge
MinAmount int64 `json:"min_amount"` // 最小充值金额
MaxAmount int64 `json:"max_amount"` // 最大充值金额
CurrentAccumulated int64 `json:"current_accumulated"` // 当前累计充值
Threshold int64 `json:"threshold"` // 佣金触发阈值
Message string `json:"message"` // 提示信息
FirstCommissionPaid bool `json:"first_commission_paid"` // 一次性佣金是否已发放
}
// Service 充值服务
// 负责充值订单的创建、预检、支付回调处理等业务逻辑
type Service struct {
db *gorm.DB
rechargeStore *postgres.RechargeStore
walletStore *postgres.WalletStore
walletTransactionStore *postgres.WalletTransactionStore
iotCardStore *postgres.IotCardStore
deviceStore *postgres.DeviceStore
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
commissionRecordStore *postgres.CommissionRecordStore
logger *zap.Logger
}
// New 创建充值服务实例
func New(
db *gorm.DB,
rechargeStore *postgres.RechargeStore,
walletStore *postgres.WalletStore,
walletTransactionStore *postgres.WalletTransactionStore,
iotCardStore *postgres.IotCardStore,
deviceStore *postgres.DeviceStore,
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
commissionRecordStore *postgres.CommissionRecordStore,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
rechargeStore: rechargeStore,
walletStore: walletStore,
walletTransactionStore: walletTransactionStore,
iotCardStore: iotCardStore,
deviceStore: deviceStore,
shopSeriesAllocationStore: shopSeriesAllocationStore,
commissionRecordStore: commissionRecordStore,
logger: logger,
}
}
// Create 创建充值订单
// 验证资源、金额范围、强充要求,生成订单号
func (s *Service) Create(ctx context.Context, req *dto.CreateRechargeRequest, userID uint) (*dto.RechargeResponse, error) {
// 1. 验证金额范围
if req.Amount < constants.RechargeMinAmount {
return nil, errors.New(errors.CodeRechargeAmountInvalid, "充值金额不能低于1元")
}
if req.Amount > constants.RechargeMaxAmount {
return nil, errors.New(errors.CodeRechargeAmountInvalid, "充值金额不能超过100000元")
}
// 2. 获取资源(卡或设备)
var wallet *model.Wallet
var err error
if req.ResourceType == "iot_card" {
wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, "iot_card", req.ResourceID, "main")
} else if req.ResourceType == "device" {
wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, "device", req.ResourceID, "main")
} else {
return nil, errors.New(errors.CodeInvalidParam, "无效的资源类型")
}
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeWalletNotFound, "钱包不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
}
// 3. 验证强充要求
forceReq, err := s.checkForceRechargeRequirement(ctx, req.ResourceType, req.ResourceID)
if err != nil {
return nil, err
}
if forceReq.NeedForceRecharge && req.Amount != forceReq.ForceRechargeAmount {
return nil, errors.New(errors.CodeForceRechargeAmountMismatch,
fmt.Sprintf("必须充值%d分才能满足强充要求", forceReq.ForceRechargeAmount))
}
// 4. 生成充值订单号
rechargeNo := s.generateRechargeNo()
// 5. 创建充值订单
recharge := &model.RechargeRecord{
BaseModel: model.BaseModel{
Creator: userID,
Updater: userID,
},
UserID: userID,
WalletID: wallet.ID,
RechargeNo: rechargeNo,
Amount: req.Amount,
PaymentMethod: req.PaymentMethod,
Status: constants.RechargeStatusPending,
}
if err := s.rechargeStore.Create(ctx, recharge); err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
s.logger.Info("创建充值订单成功",
zap.Uint("recharge_id", recharge.ID),
zap.String("recharge_no", rechargeNo),
zap.Int64("amount", req.Amount),
zap.Uint("user_id", userID),
)
return s.buildRechargeResponse(recharge), nil
}
// GetRechargeCheck 充值预检
// 返回强充要求、金额限制等信息
func (s *Service) GetRechargeCheck(ctx context.Context, resourceType string, resourceID uint) (*ForceRechargeRequirement, error) {
// 验证资源类型
if resourceType != "iot_card" && resourceType != "device" {
return nil, errors.New(errors.CodeInvalidParam, "无效的资源类型")
}
// 验证资源是否存在
if resourceType == "iot_card" {
if _, err := s.iotCardStore.GetByID(ctx, resourceID); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
}
} else {
if _, err := s.deviceStore.GetByID(ctx, resourceID); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "设备不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
}
}
return s.checkForceRechargeRequirement(ctx, resourceType, resourceID)
}
// GetByID 根据ID查询充值订单详情
// 支持数据权限过滤
func (s *Service) GetByID(ctx context.Context, id uint, userID uint) (*dto.RechargeResponse, error) {
recharge, err := s.rechargeStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeRechargeNotFound, "充值订单不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
// 数据权限检查:只能查看自己的充值订单
if recharge.UserID != userID {
return nil, errors.New(errors.CodeForbidden, "无权查看此充值订单")
}
return s.buildRechargeResponse(recharge), nil
}
// List 查询充值订单列表
// 支持分页、筛选、数据权限
func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID uint) (*dto.RechargeListResponse, error) {
page := req.Page
pageSize := req.PageSize
if page == 0 {
page = 1
}
if pageSize == 0 {
pageSize = constants.DefaultPageSize
}
params := &postgres.ListRechargeParams{
Page: page,
PageSize: pageSize,
UserID: &userID, // 数据权限:只能查看自己的
}
if req.Status != nil {
params.Status = req.Status
}
if req.WalletID != nil {
params.WalletID = req.WalletID
}
if req.StartTime != nil {
params.StartTime = req.StartTime
}
if req.EndTime != nil {
params.EndTime = req.EndTime
}
recharges, total, err := s.rechargeStore.List(ctx, params)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单列表失败")
}
var list []*dto.RechargeResponse
for _, r := range recharges {
list = append(list, s.buildRechargeResponse(r))
}
totalPages := int(total) / pageSize
if int(total)%pageSize > 0 {
totalPages++
}
return &dto.RechargeListResponse{
List: list,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
}, nil
}
// HandlePaymentCallback 支付回调处理
// 支持幂等性检查、事务处理、更新余额、触发佣金
func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string, paymentMethod string, paymentTransactionID string) error {
// 1. 查询充值订单
recharge, err := s.rechargeStore.GetByRechargeNo(ctx, rechargeNo)
if err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
if recharge == nil {
return errors.New(errors.CodeRechargeNotFound, "充值订单不存在")
}
// 2. 幂等性检查:已支付则直接返回成功
if recharge.Status == constants.RechargeStatusPaid || recharge.Status == constants.RechargeStatusCompleted {
s.logger.Info("充值订单已支付,跳过处理",
zap.String("recharge_no", rechargeNo),
zap.Int("status", recharge.Status),
)
return nil
}
// 3. 检查订单状态是否允许支付
if recharge.Status != constants.RechargeStatusPending {
return errors.New(errors.CodeInvalidStatus, "订单状态不允许支付")
}
// 4. 获取钱包信息
wallet, err := s.walletStore.GetByID(ctx, recharge.WalletID)
if err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
}
// 5. 获取钱包对应的资源类型和ID
resourceType := wallet.ResourceType
resourceID := wallet.ResourceID
// 6. 事务处理:更新订单状态、增加余额、更新累计充值、触发佣金
now := time.Now()
err = s.db.Transaction(func(tx *gorm.DB) error {
// 6.1 更新充值订单状态(带状态检查,实现乐观锁)
oldStatus := constants.RechargeStatusPending
if err := s.rechargeStore.UpdateStatus(ctx, recharge.ID, &oldStatus, constants.RechargeStatusPaid, &now, nil); err != nil {
if err == gorm.ErrRecordNotFound {
// 状态已变更,幂等处理
return nil
}
return errors.Wrap(errors.CodeDatabaseError, err, "更新充值订单状态失败")
}
// 6.2 更新支付信息
if err := s.rechargeStore.UpdatePaymentInfo(ctx, recharge.ID, &paymentMethod, &paymentTransactionID); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新支付信息失败")
}
// 6.3 增加钱包余额(使用乐观锁)
balanceBefore := wallet.Balance
result := tx.Model(&model.Wallet{}).
Where("id = ? AND version = ?", wallet.ID, wallet.Version).
Updates(map[string]any{
"balance": gorm.Expr("balance + ?", recharge.Amount),
"version": gorm.Expr("version + 1"),
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新钱包余额失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInternalError, "钱包版本冲突,请重试")
}
// 6.4 创建钱包交易记录
remark := "钱包充值"
refType := "recharge"
transaction := &model.WalletTransaction{
WalletID: wallet.ID,
UserID: recharge.UserID,
TransactionType: "recharge",
Amount: recharge.Amount,
BalanceBefore: balanceBefore,
BalanceAfter: balanceBefore + recharge.Amount,
Status: 1,
ReferenceType: &refType,
ReferenceID: &recharge.ID,
Remark: &remark,
Creator: recharge.UserID,
}
if err := tx.Create(transaction).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建钱包交易记录失败")
}
// 6.5 更新累计充值
if err := s.updateAccumulatedRechargeInTx(ctx, tx, resourceType, resourceID, recharge.Amount); err != nil {
return err
}
// 6.6 触发一次性佣金判断
if err := s.triggerOneTimeCommissionIfNeededInTx(ctx, tx, resourceType, resourceID, recharge.Amount, recharge.UserID); err != nil {
return err
}
// 6.7 更新充值订单状态为已完成
if err := tx.Model(&model.RechargeRecord{}).
Where("id = ?", recharge.ID).
Update("status", constants.RechargeStatusCompleted).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新充值订单完成状态失败")
}
return nil
})
if err != nil {
return err
}
s.logger.Info("充值支付回调处理成功",
zap.String("recharge_no", rechargeNo),
zap.Int64("amount", recharge.Amount),
zap.String("resource_type", resourceType),
zap.Uint("resource_id", resourceID),
)
return nil
}
// checkForceRechargeRequirement 检查强充要求
// 根据资源类型和ID检查是否需要强制充值指定金额
func (s *Service) checkForceRechargeRequirement(ctx context.Context, resourceType string, resourceID uint) (*ForceRechargeRequirement, error) {
result := &ForceRechargeRequirement{
NeedForceRecharge: false,
MinAmount: constants.RechargeMinAmount,
MaxAmount: constants.RechargeMaxAmount,
Message: "无强充要求,可自由充值",
}
var seriesID *uint
var shopID *uint
var accumulatedRecharge int64
var firstCommissionPaid bool
// 1. 查询资源信息
if resourceType == "iot_card" {
card, err := s.iotCardStore.GetByID(ctx, resourceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
}
seriesID = card.SeriesID
shopID = card.ShopID
accumulatedRecharge = card.AccumulatedRecharge
firstCommissionPaid = card.FirstCommissionPaid
} else if resourceType == "device" {
device, err := s.deviceStore.GetByID(ctx, resourceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "设备不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
}
seriesID = device.SeriesID
shopID = device.ShopID
accumulatedRecharge = device.AccumulatedRecharge
firstCommissionPaid = device.FirstCommissionPaid
}
result.CurrentAccumulated = accumulatedRecharge
result.FirstCommissionPaid = firstCommissionPaid
// 2. 如果没有系列ID或店铺ID无强充要求
if seriesID == nil || shopID == nil {
return result, nil
}
// 3. 查询系列分配配置
allocation, err := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, *shopID, *seriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return result, nil
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询系列分配失败")
}
// 4. 如果未启用一次性佣金,无强充要求
if !allocation.EnableOneTimeCommission {
return result, nil
}
result.Threshold = allocation.OneTimeCommissionThreshold
result.TriggerType = allocation.OneTimeCommissionTrigger
// 5. 如果一次性佣金已发放,无强充要求
if firstCommissionPaid {
result.Message = "一次性佣金已发放,无强充要求"
return result, nil
}
// 6. 根据触发类型判断强充要求
if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerSingleRecharge {
// 首次充值触发:必须充值阈值金额
result.NeedForceRecharge = true
result.ForceRechargeAmount = allocation.OneTimeCommissionThreshold
result.Message = fmt.Sprintf("首次充值必须充值%d分", allocation.OneTimeCommissionThreshold)
} else if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerAccumulatedRecharge {
// 累计充值触发:检查是否启用强充
if allocation.EnableForceRecharge {
result.NeedForceRecharge = true
// 强充金额优先使用配置值,否则使用阈值
if allocation.ForceRechargeAmount > 0 {
result.ForceRechargeAmount = allocation.ForceRechargeAmount
} else {
result.ForceRechargeAmount = allocation.OneTimeCommissionThreshold
}
result.Message = fmt.Sprintf("每次充值必须充值%d分", result.ForceRechargeAmount)
} else {
result.Message = "累计充值模式,可自由充值"
}
}
return result, nil
}
// updateAccumulatedRechargeInTx 更新累计充值(事务内使用)
// 原子操作更新卡或设备的累计充值金额
func (s *Service) updateAccumulatedRechargeInTx(ctx context.Context, tx *gorm.DB, resourceType string, resourceID uint, amount int64) error {
if resourceType == "iot_card" {
result := tx.Model(&model.IotCard{}).
Where("id = ?", resourceID).
Update("accumulated_recharge", gorm.Expr("accumulated_recharge + ?", amount))
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新卡累计充值失败")
}
} else if resourceType == "device" {
result := tx.Model(&model.Device{}).
Where("id = ?", resourceID).
Update("accumulated_recharge", gorm.Expr("accumulated_recharge + ?", amount))
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新设备累计充值失败")
}
}
return nil
}
// triggerOneTimeCommissionIfNeededInTx 触发一次性佣金(事务内使用)
// 检查是否满足一次性佣金触发条件,满足则创建佣金记录并入账
func (s *Service) triggerOneTimeCommissionIfNeededInTx(ctx context.Context, tx *gorm.DB, resourceType string, resourceID uint, rechargeAmount int64, userID uint) error {
var seriesID *uint
var accumulatedRecharge int64
var firstCommissionPaid bool
var shopID *uint
// 1. 查询资源当前状态(需要从数据库重新查询以获取更新后的累计充值)
if resourceType == "iot_card" {
var card model.IotCard
if err := tx.First(&card, resourceID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
}
seriesID = card.SeriesID
accumulatedRecharge = card.AccumulatedRecharge
firstCommissionPaid = card.FirstCommissionPaid
shopID = card.ShopID
} else if resourceType == "device" {
var device model.Device
if err := tx.First(&device, resourceID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
}
seriesID = device.SeriesID
accumulatedRecharge = device.AccumulatedRecharge
firstCommissionPaid = device.FirstCommissionPaid
shopID = device.ShopID
}
// 2. 如果没有系列ID或已发放佣金跳过
if seriesID == nil || firstCommissionPaid {
return nil
}
// 3. 如果没有归属店铺,无法发放佣金
if shopID == nil {
s.logger.Warn("资源未归属店铺,无法发放一次性佣金",
zap.String("resource_type", resourceType),
zap.Uint("resource_id", resourceID),
)
return nil
}
// 4. 查询系列分配配置
allocation, err := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, *shopID, *seriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询系列分配失败")
}
// 5. 如果未启用一次性佣金,跳过
if !allocation.EnableOneTimeCommission {
return nil
}
// 6. 根据触发类型判断是否满足条件
var rechargeAmountToCheck int64
switch allocation.OneTimeCommissionTrigger {
case model.OneTimeCommissionTriggerSingleRecharge:
rechargeAmountToCheck = rechargeAmount
case model.OneTimeCommissionTriggerAccumulatedRecharge:
rechargeAmountToCheck = accumulatedRecharge
default:
return nil
}
// 7. 检查是否达到阈值
if rechargeAmountToCheck < allocation.OneTimeCommissionThreshold {
return nil
}
// 8. 计算佣金金额
commissionAmount := s.calculateOneTimeCommission(allocation, rechargeAmount)
if commissionAmount <= 0 {
return nil
}
// 9. 查询店铺的佣金钱包
var commissionWallet model.Wallet
if err := tx.Where("resource_type = ? AND resource_id = ? AND wallet_type = ?", "shop", *shopID, "commission").
First(&commissionWallet).Error; err != nil {
if err == gorm.ErrRecordNotFound {
s.logger.Warn("店铺佣金钱包不存在,跳过佣金发放",
zap.Uint("shop_id", *shopID),
)
return nil
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询店铺佣金钱包失败")
}
// 10. 创建佣金记录
var iotCardID, deviceID *uint
if resourceType == "iot_card" {
iotCardID = &resourceID
} else {
deviceID = &resourceID
}
commissionRecord := &model.CommissionRecord{
BaseModel: model.BaseModel{
Creator: userID,
Updater: userID,
},
ShopID: *shopID,
IotCardID: iotCardID,
DeviceID: deviceID,
CommissionSource: model.CommissionSourceOneTime,
Amount: commissionAmount,
Status: model.CommissionStatusReleased,
Remark: "钱包充值触发一次性佣金",
}
if err := tx.Create(commissionRecord).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建佣金记录失败")
}
// 11. 佣金入账到店铺佣金钱包
balanceBefore := commissionWallet.Balance
result := tx.Model(&model.Wallet{}).
Where("id = ? AND version = ?", commissionWallet.ID, commissionWallet.Version).
Updates(map[string]any{
"balance": gorm.Expr("balance + ?", commissionAmount),
"version": gorm.Expr("version + 1"),
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新佣金钱包余额失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInternalError, "佣金钱包版本冲突,请重试")
}
// 12. 更新佣金记录的入账后余额
now := time.Now()
if err := tx.Model(commissionRecord).Updates(map[string]any{
"balance_after": balanceBefore + commissionAmount,
"released_at": now,
}).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新佣金记录失败")
}
// 13. 创建佣金钱包交易记录
remark := "一次性佣金入账(充值触发)"
refType := "commission"
commissionTransaction := &model.WalletTransaction{
WalletID: commissionWallet.ID,
UserID: userID,
TransactionType: "commission",
Amount: commissionAmount,
BalanceBefore: balanceBefore,
BalanceAfter: balanceBefore + commissionAmount,
Status: 1,
ReferenceType: &refType,
ReferenceID: &commissionRecord.ID,
Remark: &remark,
Creator: userID,
}
if err := tx.Create(commissionTransaction).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建佣金钱包交易记录失败")
}
// 14. 标记一次性佣金已发放
if resourceType == "iot_card" {
if err := tx.Model(&model.IotCard{}).Where("id = ?", resourceID).
Update("first_commission_paid", true).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡佣金发放状态失败")
}
} else {
if err := tx.Model(&model.Device{}).Where("id = ?", resourceID).
Update("first_commission_paid", true).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新设备佣金发放状态失败")
}
}
s.logger.Info("一次性佣金发放成功",
zap.String("resource_type", resourceType),
zap.Uint("resource_id", resourceID),
zap.Uint("shop_id", *shopID),
zap.Int64("commission_amount", commissionAmount),
)
return nil
}
// calculateOneTimeCommission 计算一次性佣金金额
func (s *Service) calculateOneTimeCommission(allocation *model.ShopSeriesAllocation, orderAmount int64) int64 {
if allocation.OneTimeCommissionType == model.OneTimeCommissionTypeFixed {
// 固定佣金
if allocation.OneTimeCommissionMode == model.CommissionModeFixed {
return allocation.OneTimeCommissionValue
} else if allocation.OneTimeCommissionMode == model.CommissionModePercent {
// 百分比佣金(千分比)
return orderAmount * allocation.OneTimeCommissionValue / 1000
}
}
// 梯度佣金在此不处理,由 commission_calculation 服务处理
return 0
}
// generateRechargeNo 生成充值订单号
// 格式: RCH + 14位时间戳 + 6位随机数
func (s *Service) generateRechargeNo() string {
now := time.Now()
timestamp := now.Format("20060102150405")
randomNum := rand.Intn(1000000)
return fmt.Sprintf("RCH%s%06d", timestamp, randomNum)
}
// buildRechargeResponse 构建充值订单响应
func (s *Service) buildRechargeResponse(recharge *model.RechargeRecord) *dto.RechargeResponse {
statusText := ""
switch recharge.Status {
case constants.RechargeStatusPending:
statusText = "待支付"
case constants.RechargeStatusPaid:
statusText = "已支付"
case constants.RechargeStatusCompleted:
statusText = "已完成"
case constants.RechargeStatusClosed:
statusText = "已关闭"
case constants.RechargeStatusRefunded:
statusText = "已退款"
}
return &dto.RechargeResponse{
ID: recharge.ID,
RechargeNo: recharge.RechargeNo,
UserID: recharge.UserID,
WalletID: recharge.WalletID,
Amount: recharge.Amount,
PaymentMethod: recharge.PaymentMethod,
PaymentChannel: recharge.PaymentChannel,
PaymentTransactionID: recharge.PaymentTransactionID,
Status: recharge.Status,
StatusText: statusText,
PaidAt: recharge.PaidAt,
CompletedAt: recharge.CompletedAt,
CreatedAt: recharge.CreatedAt,
UpdatedAt: recharge.UpdatedAt,
}
}