package withdrawal import ( "context" "errors" "fmt" "time" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/modules/wallet" "gorm.io/gorm" "gorm.io/gorm/clause" ) // 管理员查询提现列表 func (r *Repository) AdminList(ctx context.Context, query AdminListQuery) (*AdminPaginatedResult, error) { db := r.db.WithContext(ctx).Model(&model.WithdrawalRequest{}) if query.Status != "" { db = db.Where("status = ?", query.Status) } if query.UserID > 0 { db = db.Where("user_id = ?", query.UserID) } var total int64 if err := db.Count(&total).Error; err != nil { return nil, err } offset := (query.Page - 1) * query.Size var withdrawals []model.WithdrawalRequest if err := db.Order("created_at DESC"). Offset(offset).Limit(query.Size). Find(&withdrawals).Error; err != nil { return nil, err } items := make([]WithdrawalDetailDTO, 0, len(withdrawals)) for _, w := range withdrawals { dto, err := r.toDetailDTO(ctx, w) if err != nil { continue } items = append(items, *dto) } return &AdminPaginatedResult{ Items: items, Total: total, Page: query.Page, PageSize: query.Size, }, nil } // 管理员查询提现详情 // 管理员查询提现详情 func (r *Repository) AdminFindByID(ctx context.Context, id uint64) (*WithdrawalDetailDTO, error) { var withdrawal model.WithdrawalRequest if err := r.db.WithContext(ctx).First(&withdrawal, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrWithdrawalNotFound } return nil, err } return r.toDetailDTO(ctx, withdrawal) } // 管理员审核提现 // 管理员审核提现 func (r *Repository) Review(ctx context.Context, adminID, id uint64, req ReviewWithdrawalRequest) (*WithdrawalDetailDTO, error) { err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var withdrawal model.WithdrawalRequest if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&withdrawal, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrWithdrawalNotFound } return err } // 事务内加行锁后重新校验,只有待审核状态可以审核,避免并发重复处理 if withdrawal.Status != "pending" { return ErrWithdrawalLocked } now := time.Now() if req.Approved { // 审核通过,进入处理中状态 withdrawal.Status = "processing" } else { // 审核拒绝,解冻余额 withdrawal.Status = "rejected" } withdrawal.ReviewedBy = &adminID withdrawal.ReviewedAt = &now withdrawal.ReviewRemark = req.Remark if err := tx.Save(&withdrawal).Error; err != nil { return err } // 如果拒绝,解冻余额 if !req.Approved { if err := wallet.AppendEntries(tx, wallet.Entry{ UserID: withdrawal.UserID, Direction: "out", AmountCent: withdrawal.AmountCent, BalanceType: "frozen", BizType: "withdraw_reject", BizNo: withdrawal.WithdrawNo, Remark: fmt.Sprintf("提现被拒绝: %s", req.Remark), }, wallet.Entry{ UserID: withdrawal.UserID, Direction: "in", AmountCent: withdrawal.AmountCent, BalanceType: "available", BizType: "withdraw_reject", BizNo: withdrawal.WithdrawNo, Remark: fmt.Sprintf("提现被拒绝: %s", req.Remark), }); err != nil { return err } } return nil }) if err != nil { return nil, err } return r.AdminFindByID(ctx, id) } // 管理员确认打款 // 管理员确认打款 func (r *Repository) ConfirmPayment(ctx context.Context, adminID, id uint64, req ConfirmPaymentRequest) (*WithdrawalDetailDTO, error) { err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var withdrawal model.WithdrawalRequest if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&withdrawal, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrWithdrawalNotFound } return err } // 事务内加行锁后重新校验,只有处理中状态可以确认打款,避免并发重复扣减冻结余额 if withdrawal.Status != "processing" { return ErrWithdrawalLocked } now := time.Now() withdrawal.Status = "completed" withdrawal.PaidBy = &adminID withdrawal.PaidAt = &now withdrawal.PaymentProofURL = req.PaymentProofURL withdrawal.PaymentRemark = req.Remark if err := tx.Save(&withdrawal).Error; err != nil { return err } // 扣除冻结余额 if err := wallet.AppendEntries(tx, wallet.Entry{ UserID: withdrawal.UserID, Direction: "out", AmountCent: withdrawal.AmountCent, BalanceType: "frozen", BizType: "withdraw_complete", BizNo: withdrawal.WithdrawNo, Remark: "提现完成", }); err != nil { return err } return nil }) if err != nil { return nil, err } return r.AdminFindByID(ctx, id) } // 转换为用户DTO