package commission_withdrawal import ( "context" "encoding/json" "fmt" "time" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store" "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 accountStore *postgres.AccountStore walletStore *postgres.WalletStore walletTransactionStore *postgres.WalletTransactionStore commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore } func New( db *gorm.DB, shopStore *postgres.ShopStore, accountStore *postgres.AccountStore, walletStore *postgres.WalletStore, walletTransactionStore *postgres.WalletTransactionStore, commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore, ) *Service { return &Service{ db: db, shopStore: shopStore, accountStore: accountStore, walletStore: walletStore, walletTransactionStore: walletTransactionStore, commissionWithdrawalReqStore: commissionWithdrawalReqStore, } } func (s *Service) ListWithdrawalRequests(ctx context.Context, req *model.WithdrawalRequestListReq) (*model.WithdrawalRequestPageResult, error) { opts := &store.QueryOptions{ Page: req.Page, PageSize: req.PageSize, OrderBy: "created_at DESC", } if opts.Page == 0 { opts.Page = 1 } if opts.PageSize == 0 { opts.PageSize = constants.DefaultPageSize } filters := &postgres.WithdrawalRequestListFilters{ WithdrawalNo: req.WithdrawalNo, Status: req.Status, } if req.StartTime != "" { t, err := time.Parse("2006-01-02 15:04:05", req.StartTime) if err == nil { filters.StartTime = &t } } if req.EndTime != "" { t, err := time.Parse("2006-01-02 15:04:05", req.EndTime) if err == nil { filters.EndTime = &t } } requests, total, err := s.commissionWithdrawalReqStore.List(ctx, opts, filters) if err != nil { return nil, fmt.Errorf("查询提现申请列表失败: %w", err) } shopIDs := make([]uint, 0) applicantIDs := make([]uint, 0) processorIDs := make([]uint, 0) for _, r := range requests { if r.ShopID > 0 { shopIDs = append(shopIDs, r.ShopID) } if r.ApplicantID > 0 { applicantIDs = append(applicantIDs, r.ApplicantID) } if r.ProcessorID > 0 { processorIDs = append(processorIDs, r.ProcessorID) } } shopMap := make(map[uint]*model.Shop) for _, id := range shopIDs { shop, err := s.shopStore.GetByID(ctx, id) if err == nil { shopMap[id] = shop } } applicantMap := make(map[uint]string) processorMap := make(map[uint]string) if len(applicantIDs) > 0 { accounts, _ := s.accountStore.GetByIDs(ctx, applicantIDs) for _, acc := range accounts { applicantMap[acc.ID] = acc.Username } } if len(processorIDs) > 0 { accounts, _ := s.accountStore.GetByIDs(ctx, processorIDs) for _, acc := range accounts { processorMap[acc.ID] = acc.Username } } items := make([]model.WithdrawalRequestItem, 0, len(requests)) for _, r := range requests { shop := shopMap[r.ShopID] shopName := "" shopHierarchy := "" if shop != nil { shopName = shop.ShopName shopHierarchy = s.buildShopHierarchyPath(ctx, shop) if req.ShopName != "" && !containsSubstring(shopName, req.ShopName) { total-- continue } } item := s.buildWithdrawalRequestItem(r, shopName, shopHierarchy, applicantMap, processorMap) items = append(items, item) } return &model.WithdrawalRequestPageResult{ Items: items, Total: total, Page: opts.Page, Size: opts.PageSize, }, nil } func (s *Service) Approve(ctx context.Context, id uint, req *model.ApproveWithdrawalReq) (*model.WithdrawalApprovalResp, error) { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } withdrawal, err := s.commissionWithdrawalReqStore.GetByID(ctx, id) if err != nil { return nil, errors.New(errors.CodeNotFound, "提现申请不存在") } if withdrawal.Status != constants.WithdrawalStatusPending { return nil, errors.New(errors.CodeInvalidStatus, "申请状态不允许此操作") } wallet, err := s.walletStore.GetShopCommissionWallet(ctx, withdrawal.ShopID) if err != nil { return nil, errors.New(errors.CodeNotFound, "店铺佣金钱包不存在") } amount := withdrawal.Amount if req.Amount != nil { amount = *req.Amount } if wallet.FrozenBalance < amount { return nil, errors.New(errors.CodeInsufficientBalance, "钱包冻结余额不足") } now := time.Now() err = s.db.Transaction(func(tx *gorm.DB) error { if err := s.walletStore.DeductFrozenBalanceWithTx(ctx, tx, wallet.ID, amount); err != nil { return fmt.Errorf("扣除冻结余额失败: %w", err) } refType := "withdrawal" refID := withdrawal.ID transaction := &model.WalletTransaction{ WalletID: wallet.ID, UserID: currentUserID, TransactionType: "withdrawal", Amount: -amount, BalanceBefore: wallet.Balance, BalanceAfter: wallet.Balance, Status: 1, ReferenceType: &refType, ReferenceID: &refID, Creator: currentUserID, } if err := s.walletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil { return fmt.Errorf("创建交易流水失败: %w", err) } updates := map[string]interface{}{ "status": constants.WithdrawalStatusApproved, "processor_id": currentUserID, "processed_at": now, "payment_type": req.PaymentType, "remark": req.Remark, } if req.Amount != nil { feeRate := withdrawal.FeeRate fee := amount * feeRate / 10000 actualAmount := amount - fee updates["amount"] = amount updates["fee"] = fee updates["actual_amount"] = actualAmount } if req.WithdrawalMethod != nil { updates["withdrawal_method"] = *req.WithdrawalMethod } if req.AccountName != nil || req.AccountNumber != nil { accountInfo := make(map[string]interface{}) if withdrawal.AccountInfo != nil { _ = json.Unmarshal(withdrawal.AccountInfo, &accountInfo) } if req.AccountName != nil { accountInfo["account_name"] = *req.AccountName } if req.AccountNumber != nil { accountInfo["account_number"] = *req.AccountNumber } infoBytes, _ := json.Marshal(accountInfo) updates["account_info"] = infoBytes } if err := s.commissionWithdrawalReqStore.UpdateStatusWithTx(ctx, tx, id, updates); err != nil { return fmt.Errorf("更新提现申请状态失败: %w", err) } return nil }) if err != nil { return nil, err } return &model.WithdrawalApprovalResp{ ID: withdrawal.ID, WithdrawalNo: withdrawal.WithdrawalNo, Status: constants.WithdrawalStatusApproved, StatusName: "已通过", ProcessedAt: now.Format("2006-01-02 15:04:05"), }, nil } func (s *Service) Reject(ctx context.Context, id uint, req *model.RejectWithdrawalReq) (*model.WithdrawalApprovalResp, error) { currentUserID := middleware.GetUserIDFromContext(ctx) if currentUserID == 0 { return nil, errors.New(errors.CodeUnauthorized, "未授权访问") } withdrawal, err := s.commissionWithdrawalReqStore.GetByID(ctx, id) if err != nil { return nil, errors.New(errors.CodeNotFound, "提现申请不存在") } if withdrawal.Status != constants.WithdrawalStatusPending { return nil, errors.New(errors.CodeInvalidStatus, "申请状态不允许此操作") } wallet, err := s.walletStore.GetShopCommissionWallet(ctx, withdrawal.ShopID) if err != nil { return nil, errors.New(errors.CodeNotFound, "店铺佣金钱包不存在") } now := time.Now() err = s.db.Transaction(func(tx *gorm.DB) error { if err := s.walletStore.UnfreezeBalanceWithTx(ctx, tx, wallet.ID, withdrawal.Amount); err != nil { return fmt.Errorf("解冻余额失败: %w", err) } refType := "withdrawal" refID := withdrawal.ID transaction := &model.WalletTransaction{ WalletID: wallet.ID, UserID: currentUserID, TransactionType: "refund", Amount: withdrawal.Amount, BalanceBefore: wallet.Balance, BalanceAfter: wallet.Balance + withdrawal.Amount, Status: 1, ReferenceType: &refType, ReferenceID: &refID, Creator: currentUserID, } if err := s.walletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil { return fmt.Errorf("创建交易流水失败: %w", err) } updates := map[string]interface{}{ "status": constants.WithdrawalStatusRejected, "processor_id": currentUserID, "processed_at": now, "reject_reason": req.Remark, "remark": req.Remark, } if err := s.commissionWithdrawalReqStore.UpdateStatusWithTx(ctx, tx, id, updates); err != nil { return fmt.Errorf("更新提现申请状态失败: %w", err) } return nil }) if err != nil { return nil, err } return &model.WithdrawalApprovalResp{ ID: withdrawal.ID, WithdrawalNo: withdrawal.WithdrawalNo, Status: constants.WithdrawalStatusRejected, StatusName: "已拒绝", ProcessedAt: now.Format("2006-01-02 15:04:05"), }, nil } func (s *Service) buildShopHierarchyPath(ctx context.Context, shop *model.Shop) string { if shop == nil { return "" } path := shop.ShopName current := shop depth := 0 for current.ParentID != nil && depth < 2 { parent, err := s.shopStore.GetByID(ctx, *current.ParentID) if err != nil { break } path = parent.ShopName + "_" + path current = parent depth++ } return path } func (s *Service) buildWithdrawalRequestItem(r *model.CommissionWithdrawalRequest, shopName, shopHierarchy string, applicantMap, processorMap map[uint]string) model.WithdrawalRequestItem { var processorID *uint if r.ProcessorID > 0 { processorID = &r.ProcessorID } var accountName, accountNumber, bankName string if len(r.AccountInfo) > 0 { var info map[string]interface{} if err := json.Unmarshal(r.AccountInfo, &info); err == nil { if v, ok := info["account_name"].(string); ok { accountName = v } if v, ok := info["account_number"].(string); ok { accountNumber = v } if v, ok := info["bank_name"].(string); ok { bankName = v } } } var processedAt string if r.ProcessedAt != nil { processedAt = r.ProcessedAt.Format("2006-01-02 15:04:05") } return model.WithdrawalRequestItem{ ID: r.ID, WithdrawalNo: r.WithdrawalNo, Amount: r.Amount, FeeRate: r.FeeRate, Fee: r.Fee, ActualAmount: r.ActualAmount, Status: r.Status, StatusName: getWithdrawalStatusName(r.Status), ShopID: r.ShopID, ShopName: shopName, ShopHierarchy: shopHierarchy, ApplicantID: r.ApplicantID, ApplicantName: applicantMap[r.ApplicantID], ProcessorID: processorID, ProcessorName: processorMap[r.ProcessorID], WithdrawalMethod: r.WithdrawalMethod, PaymentType: r.PaymentType, AccountName: accountName, AccountNumber: accountNumber, BankName: bankName, RejectReason: r.RejectReason, Remark: r.Remark, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), ProcessedAt: processedAt, } } func getWithdrawalStatusName(status int) string { switch status { case constants.WithdrawalStatusPending: return "待审核" case constants.WithdrawalStatusApproved: return "已通过" case constants.WithdrawalStatusRejected: return "已拒绝" case constants.WithdrawalStatusPaid: return "已到账" default: return "未知" } } func containsSubstring(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }