using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.Payment; using Domain.Entities.Common.ValueObject; using Domain.Entities.Payments.Danal; using Domain.Entities.Payments.Danal.ValueObject; using Domain.Entities.Payments.ValueObject; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Payment.Cancel; internal sealed class Handler( IAppDbContext db, IDanalPayService danalPay ) : ICommandHandler { public async Task Handle(Command r, CancellationToken ct) { var order = await db.PaymentOrder.FirstOrDefaultAsync(o => o.OrderID == r.OrderID && o.MemberID == r.MemberID, ct); if (order is null) { throw new InvalidOperationException("주문을 찾을 수 없습니다."); } if (order.Status != PaymentStatus.Paid) { throw new InvalidOperationException("취소할 수 없는 주문 상태입니다."); } // 다날 결제수단 코드 매핑 var method = order.PaymentMethod switch { PaymentMethod.Integrated => "INTEGRATED", PaymentMethod.Card => "CARD", PaymentMethod.VirtualAccount => "VACCOUNT", PaymentMethod.Mobile => "MOBILE", PaymentMethod.Transfer => "TRANSFER", PaymentMethod.NaverPay => "NAVERPAY", PaymentMethod.KakaoPay => "KAKAOPAY", PaymentMethod.Payco => "PAYCO", _ => "CARD" }; // 취소 요청 기록 저장 var cancel = DanalCancel.CreateRequest(r.MemberID, order.ID, order.PaymentMethod, order.TransactionID!, order.OrderID, order.Amount, order.MerchantID, DanalCancelType.Full); await db.DanalCancel.AddAsync(cancel, ct); await db.SaveChangesAsync(ct); var result = await danalPay.CancelAsync(method, order.TransactionID!, order.Amount, "C", ct); if (!result.Success) { cancel.SetResponse(result.Code ?? "", result.Message ?? "취소 실패"); await db.DanalLog.AddAsync(DanalLog.Create(r.MemberID, DanalLogType.Failed, result.Code ?? "FAIL", result.Message ?? "취소 실패", order.TransactionID, order.OrderID), ct); await db.SaveChangesAsync(ct); throw new InvalidOperationException(result.Message ?? "결제 취소에 실패했습니다."); } // 취소 응답 저장 (Balance, RemainedAmount는 string? → int? 변환) cancel.SetResponse( result.Code ?? "", result.Message ?? "", result.OriginalTransactionID, result.CancelledAmount, result.TransDate, result.TransTime, int.TryParse(result.Balance, out var balanceVal) ? balanceVal : null, int.TryParse(result.RemainedAmount, out var remainedVal) ? remainedVal : null, result.ApprovalDateTime ); order.MarkCancelled(); // 지갑에서 PgCharged 차감 var wallet = await db.Wallet.FirstOrDefaultAsync(w => w.MemberID == order.MemberID, ct); if (wallet is not null) { wallet.AdjustDecrease(Money.KRW(order.PointAmount), "결제 취소 환불", order.OrderID); } await db.SaveChangesAsync(ct); } }