package my_commission import ( "context" "encoding/json" "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" "github.com/break/junhong_cmp_fiber/pkg/middleware" "gorm.io/gorm" ) type Service struct { db *gorm.DB shopStore *postgres.ShopStore walletStore *postgres.WalletStore commissionWithdrawalRequestStore *postgres.CommissionWithdrawalRequestStore commissionWithdrawalSettingStore *postgres.CommissionWithdrawalSettingStore commissionRecordStore *postgres.CommissionRecordStore walletTransactionStore *postgres.WalletTransactionStore } func New( db *gorm.DB, shopStore *postgres.ShopStore, walletStore *postgres.WalletStore, commissionWithdrawalRequestStore *postgres.CommissionWithdrawalRequestStore, commissionWithdrawalSettingStore *postgres.CommissionWithdrawalSettingStore, commissionRecordStore *postgres.CommissionRecordStore, walletTransactionStore *postgres.WalletTransactionStore, ) *Service { return &Service{ db: db, shopStore: shopStore, walletStore: walletStore, commissionWithdrawalRequestStore: commissionWithdrawalRequestStore, commissionWithdrawalSettingStore: commissionWithdrawalSettingStore, commissionRecordStore: commissionRecordStore, walletTransactionStore: walletTransactionStore, } } // GetCommissionSummary 获取我的佣金概览 func (s *Service) GetCommissionSummary(ctx context.Context) (*dto.MyCommissionSummaryResp, error) { userType := middleware.GetUserTypeFromContext(ctx) if userType != constants.UserTypeAgent { return nil, errors.New(errors.CodeForbidden, "仅代理商用户可访问") } shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeForbidden, "无法获取店铺信息") } shop, err := s.shopStore.GetByID(ctx, shopID) if err != nil { return nil, errors.New(errors.CodeShopNotFound, "店铺不存在") } // 使用 GetShopCommissionWallet 获取店铺佣金钱包 wallet, err := s.walletStore.GetShopCommissionWallet(ctx, shopID) if err != nil { // 钱包不存在时返回空数据 return &dto.MyCommissionSummaryResp{ ShopID: shopID, ShopName: shop.ShopName, }, nil } // 计算累计佣金(当前余额 + 冻结余额 + 已提现金额) // 由于 Wallet 模型没有 TotalIncome、TotalWithdrawn 字段, // 我们需要从 WalletTransaction 表计算或简化处理 var totalWithdrawn int64 s.db.WithContext(ctx).Model(&model.CommissionWithdrawalRequest{}). Where("shop_id = ? AND status IN ?", shopID, []int{2, 4}). // 已通过或已到账 Select("COALESCE(SUM(actual_amount), 0)").Scan(&totalWithdrawn) totalCommission := wallet.Balance + wallet.FrozenBalance + totalWithdrawn return &dto.MyCommissionSummaryResp{ ShopID: shopID, ShopName: shop.ShopName, TotalCommission: totalCommission, WithdrawnCommission: totalWithdrawn, UnwithdrawCommission: wallet.Balance + wallet.FrozenBalance, FrozenCommission: wallet.FrozenBalance, WithdrawingCommission: wallet.FrozenBalance, // 提现中的金额等于冻结金额 AvailableCommission: wallet.Balance, }, nil } // CreateWithdrawalRequest 发起提现申请 func (s *Service) CreateWithdrawalRequest(ctx context.Context, req *dto.CreateMyWithdrawalReq) (*dto.CreateMyWithdrawalResp, error) { userType := middleware.GetUserTypeFromContext(ctx) if userType != constants.UserTypeAgent { return nil, errors.New(errors.CodeForbidden, "仅代理商用户可访问") } shopID := middleware.GetShopIDFromContext(ctx) currentUserID := middleware.GetUserIDFromContext(ctx) if shopID == 0 || currentUserID == 0 { return nil, errors.New(errors.CodeForbidden, "无法获取用户信息") } // 获取提现配置 setting, err := s.commissionWithdrawalSettingStore.GetCurrent(ctx) if err != nil { return nil, errors.New(errors.CodeInvalidParam, "暂未开放提现功能") } // 验证最低提现金额 if req.Amount < setting.MinWithdrawalAmount { return nil, errors.New(errors.CodeInvalidParam, fmt.Sprintf("提现金额不能低于 %.2f 元", float64(setting.MinWithdrawalAmount)/100)) } // 获取钱包 wallet, err := s.walletStore.GetShopCommissionWallet(ctx, shopID) if err != nil { return nil, errors.New(errors.CodeInsufficientBalance, "钱包不存在") } // 验证余额 if req.Amount > wallet.Balance { return nil, errors.New(errors.CodeInsufficientBalance, "可提现余额不足") } // 验证今日提现次数 today := time.Now().Format("2006-01-02") todayStart := today + " 00:00:00" todayEnd := today + " 23:59:59" var todayCount int64 s.db.WithContext(ctx).Model(&model.CommissionWithdrawalRequest{}). Where("shop_id = ? AND created_at >= ? AND created_at <= ?", shopID, todayStart, todayEnd). Count(&todayCount) if int(todayCount) >= setting.DailyWithdrawalLimit { return nil, errors.New(errors.CodeInvalidParam, "今日提现次数已达上限") } // 计算手续费 fee := req.Amount * setting.FeeRate / 10000 actualAmount := req.Amount - fee // 生成提现单号 withdrawalNo := generateWithdrawalNo() // 构建账户信息 JSON accountInfo := map[string]string{ "account_name": req.AccountName, "account_number": req.AccountNumber, } accountInfoJSON, _ := json.Marshal(accountInfo) var withdrawalRequest *model.CommissionWithdrawalRequest err = s.db.Transaction(func(tx *gorm.DB) error { // 冻结余额 if err := tx.WithContext(ctx).Model(&model.Wallet{}). Where("id = ? AND balance >= ?", wallet.ID, req.Amount). Updates(map[string]interface{}{ "balance": gorm.Expr("balance - ?", req.Amount), "frozen_balance": gorm.Expr("frozen_balance + ?", req.Amount), }).Error; err != nil { return fmt.Errorf("冻结余额失败: %w", err) } // 创建提现申请 withdrawalRequest = &model.CommissionWithdrawalRequest{ WithdrawalNo: withdrawalNo, ShopID: shopID, ApplicantID: currentUserID, Amount: req.Amount, FeeRate: setting.FeeRate, Fee: fee, ActualAmount: actualAmount, WithdrawalMethod: req.WithdrawalMethod, AccountInfo: accountInfoJSON, Status: 1, // 待审核 } withdrawalRequest.Creator = currentUserID withdrawalRequest.Updater = currentUserID if err := tx.WithContext(ctx).Create(withdrawalRequest).Error; err != nil { return fmt.Errorf("创建提现申请失败: %w", err) } // 创建钱包流水记录 remark := fmt.Sprintf("提现冻结,单号:%s", withdrawalNo) refType := constants.ReferenceTypeWithdrawal transaction := &model.WalletTransaction{ WalletID: wallet.ID, UserID: currentUserID, TransactionType: constants.TransactionTypeWithdrawal, Amount: -req.Amount, // 冻结为负数 BalanceBefore: wallet.Balance, BalanceAfter: wallet.Balance - req.Amount, Status: constants.TransactionStatusProcessing, // 处理中 ReferenceType: &refType, ReferenceID: &withdrawalRequest.ID, Remark: &remark, Creator: currentUserID, } if err := tx.WithContext(ctx).Create(transaction).Error; err != nil { return fmt.Errorf("创建钱包流水失败: %w", err) } return nil }) if err != nil { return nil, err } return &dto.CreateMyWithdrawalResp{ ID: withdrawalRequest.ID, WithdrawalNo: withdrawalRequest.WithdrawalNo, Amount: withdrawalRequest.Amount, FeeRate: withdrawalRequest.FeeRate, Fee: withdrawalRequest.Fee, ActualAmount: withdrawalRequest.ActualAmount, Status: withdrawalRequest.Status, StatusName: getWithdrawalStatusName(withdrawalRequest.Status), CreatedAt: withdrawalRequest.CreatedAt.Format("2006-01-02 15:04:05"), }, nil } // ListMyWithdrawalRequests 查询我的提现记录 func (s *Service) ListMyWithdrawalRequests(ctx context.Context, req *dto.MyWithdrawalListReq) (*dto.WithdrawalRequestPageResult, error) { userType := middleware.GetUserTypeFromContext(ctx) if userType != constants.UserTypeAgent { return nil, errors.New(errors.CodeForbidden, "仅代理商用户可访问") } shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeForbidden, "无法获取店铺信息") } page := req.Page pageSize := req.PageSize if page == 0 { page = 1 } if pageSize == 0 { pageSize = constants.DefaultPageSize } query := s.db.WithContext(ctx).Model(&model.CommissionWithdrawalRequest{}). Where("shop_id = ?", shopID) if req.Status != nil { query = query.Where("status = ?", *req.Status) } if req.StartTime != "" { query = query.Where("created_at >= ?", req.StartTime) } if req.EndTime != "" { query = query.Where("created_at <= ?", req.EndTime) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, fmt.Errorf("统计提现记录失败: %w", err) } var requests []model.CommissionWithdrawalRequest offset := (page - 1) * pageSize if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&requests).Error; err != nil { return nil, fmt.Errorf("查询提现记录失败: %w", err) } items := make([]dto.WithdrawalRequestItem, 0, len(requests)) for _, r := range requests { // 解析账户信息 accountName, accountNumber := parseAccountInfo(r.AccountInfo) items = append(items, dto.WithdrawalRequestItem{ ID: r.ID, WithdrawalNo: r.WithdrawalNo, ShopID: r.ShopID, Amount: r.Amount, FeeRate: r.FeeRate, Fee: r.Fee, ActualAmount: r.ActualAmount, WithdrawalMethod: r.WithdrawalMethod, AccountName: accountName, AccountNumber: accountNumber, Status: r.Status, StatusName: getWithdrawalStatusName(r.Status), CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), }) } return &dto.WithdrawalRequestPageResult{ Items: items, Total: total, Page: page, Size: pageSize, }, nil } // ListMyCommissionRecords 查询我的佣金明细 func (s *Service) ListMyCommissionRecords(ctx context.Context, req *dto.MyCommissionRecordListReq) (*dto.MyCommissionRecordPageResult, error) { userType := middleware.GetUserTypeFromContext(ctx) if userType != constants.UserTypeAgent { return nil, errors.New(errors.CodeForbidden, "仅代理商用户可访问") } shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeForbidden, "无法获取店铺信息") } page := req.Page pageSize := req.PageSize if page == 0 { page = 1 } if pageSize == 0 { pageSize = constants.DefaultPageSize } query := s.db.WithContext(ctx).Model(&model.CommissionRecord{}). Where("shop_id = ?", shopID) if req.CommissionType != nil { query = query.Where("commission_type = ?", *req.CommissionType) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, fmt.Errorf("统计佣金记录失败: %w", err) } var records []model.CommissionRecord offset := (page - 1) * pageSize if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil { return nil, fmt.Errorf("查询佣金记录失败: %w", err) } items := make([]dto.MyCommissionRecordItem, 0, len(records)) for _, r := range records { items = append(items, dto.MyCommissionRecordItem{ ID: r.ID, ShopID: r.ShopID, OrderID: r.OrderID, CommissionType: r.CommissionType, Amount: r.Amount, Status: r.Status, StatusName: getCommissionStatusName(r.Status), CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), }) } return &dto.MyCommissionRecordPageResult{ Items: items, Total: total, Page: page, Size: pageSize, }, nil } // generateWithdrawalNo 生成提现单号 func generateWithdrawalNo() string { now := time.Now() return fmt.Sprintf("W%s%04d", now.Format("20060102150405"), rand.Intn(10000)) } // getWithdrawalStatusName 获取提现状态名称 func getWithdrawalStatusName(status int) string { switch status { case 1: return "待审核" case 2: return "已通过" case 3: return "已拒绝" case 4: return "已到账" default: return "未知" } } // getCommissionStatusName 获取佣金状态名称 func getCommissionStatusName(status int) string { switch status { case 1: return "已冻结" case 2: return "解冻中" case 3: return "已发放" case 4: return "已失效" default: return "未知" } } // parseAccountInfo 解析账户信息 JSON func parseAccountInfo(data []byte) (accountName, accountNumber string) { if len(data) == 0 { return "", "" } var info map[string]string if err := json.Unmarshal(data, &info); err != nil { return "", "" } return info["account_name"], info["account_number"] }