using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Common.ValueObject; using Domain.Entities.Donations; using Domain.Entities.Donations.ValueObject; using Domain.Entities.Wallets; using Domain.Entities.Wallets.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Studio.Wallet.RequestWithdraw; internal sealed class Handler(IAppDbContext db) : ICommandHandler> { private const int MinWithdrawAmount = 40000; public async Task> Handle(Command request, CancellationToken ct) { // 금액 검증 if (request.Amount < MinWithdrawAmount) { return Result.Failure(Error.Problem("Amount", $"최소 출금 금액은 {MinWithdrawAmount:N0}원입니다.")); } // 채널 확인 var channel = await db.Channel.AsNoTracking().FirstOrDefaultAsync(c => c.MemberID == request.MemberID && c.IsActive, ct); if (channel is null) { return Result.Failure(Error.NotFound("Channel", "채널 정보를 찾을 수 없습니다.")); } // 계좌 확인 var account = await db.SettlementAccount.AsNoTracking().FirstOrDefaultAsync(a => a.ID == request.AccountID && a.MemberID == request.MemberID, ct); if (account is null) { return Result.Failure(Error.Problem("Account", "정산 계좌를 선택해 주세요.")); } // Wallet 조회 (Tracked — 잔액 변경 필요) var wallet = await db.Wallet .Include(w => w.Balances) .FirstOrDefaultAsync(w => w.MemberID == request.MemberID, ct); if (wallet is null) { return Result.Failure(Error.Problem("Wallet", "지갑 정보를 찾을 수 없습니다.")); } var donationBal = wallet.Balances.FirstOrDefault(b => b.Type == WalletBalanceType.Donation); if (donationBal is null || (int)donationBal.Amount.Value < request.Amount) { return Result.Failure(Error.Problem("Amount", "출금 가능 잔액이 부족합니다.")); } // 이미 대기 중인 출금이 있는지 확인 var hasPending = await db.WithdrawalRequest.AnyAsync( w => w.MemberID == request.MemberID && (w.Status == WithdrawalStatus.Pending || w.Status == WithdrawalStatus.Processing), ct); if (hasPending) { return Result.Failure(Error.Problem("Withdrawal", "이미 처리 중인 출금 요청이 있습니다.")); } // Wallet 잔액 Lock wallet.LockForWithdrawal(Money.KRW(request.Amount)); // 출금 요청 생성 var withdrawal = WithdrawalRequest.Create( channel.ID, request.MemberID, request.Amount, account.BankCode, account.BankName, account.AccountNumber, account.AccountHolder ); db.WithdrawalRequest.Add(withdrawal); await db.SaveChangesAsync(ct); return Result.Success(new Response("출금 신청이 완료되었습니다.")); } }