Handler.cs 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.Payment;
  4. using Domain.Entities.Common.ValueObject;
  5. using Domain.Entities.Payments.Danal;
  6. using Domain.Entities.Payments.Danal.ValueObject;
  7. using Microsoft.EntityFrameworkCore;
  8. namespace Application.Features.Api.Payment.Confirm;
  9. internal sealed class Handler(
  10. IAppDbContext db,
  11. IDanalPayService danalPay
  12. ) : IQueryHandler<Command, Response>
  13. {
  14. public async Task<Response> Handle(Command r, CancellationToken ct)
  15. {
  16. // 주문 조회
  17. var order = await db.PaymentOrder.FirstOrDefaultAsync(o => o.OrderID == r.OrderID, ct);
  18. if (order is null)
  19. {
  20. return new Response(false, 0, "주문을 찾을 수 없습니다.", null);
  21. }
  22. // 이미 결제 완료된 주문인 경우 중복 승인 방지
  23. if (order.Status == Domain.Entities.Payments.ValueObject.PaymentStatus.Paid)
  24. {
  25. return new Response(true, order.PointAmount, "이미 충전이 완료된 주문입니다.", order.PaidAt);
  26. }
  27. // 회원 검증
  28. if (order.MemberID != r.MemberID)
  29. {
  30. order.MarkFailed("회원 불일치");
  31. await db.SaveChangesAsync(ct);
  32. return new Response(false, 0, "주문 정보가 일치하지 않습니다.", null);
  33. }
  34. // 승인 요청 기록 저장
  35. var confirm = DanalConfirm.CreateRequest(r.MemberID, order.ID, order.PaymentMethod, r.TransactionID, r.OrderID, order.Amount, order.MerchantID);
  36. await db.DanalConfirm.AddAsync(confirm, ct);
  37. await db.SaveChangesAsync(ct);
  38. try
  39. {
  40. // 다날 승인 API 호출
  41. var result = await danalPay.ConfirmAsync(r.Method, r.TransactionID, r.OrderID, order.Amount, ct);
  42. if (!result.Success)
  43. {
  44. confirm.SetResponse(result.Code ?? "", result.Message ?? "승인 실패");
  45. order.MarkFailed(result.Message ?? "승인 실패");
  46. await db.DanalLog.AddAsync(DanalLog.Create(r.MemberID, DanalLogType.Failed, result.Code ?? "FAIL", result.Message ?? "승인 실패", r.TransactionID, r.OrderID), ct);
  47. await db.SaveChangesAsync(ct);
  48. return new Response(false, 0, result.Message ?? "결제 승인에 실패했습니다.", null);
  49. }
  50. // 응답 저장
  51. confirm.SetResponse(
  52. result.Code ?? "", result.Message ?? "",
  53. result.OrderName, result.TotalAmount, result.DiscountAmount, result.UserName,
  54. result.TransDate, result.TransTime,
  55. result.CardCode, result.CardName, result.CardNo,
  56. result.InstallmentMonths, result.ApproveNo,
  57. result.ApprovalDateTime, result.AuthKey,
  58. result.AccountNumber, result.BankCode, result.UserId, result.UserEmail,
  59. result.BankName, result.ExpireDate, result.ExpireTime,
  60. result.VirtualAccountNumber, result.UseCashReceipt
  61. );
  62. // 주문 상태 갱신
  63. order.MarkPaid(r.TransactionID);
  64. // 지갑에 포인트 충전
  65. var wallet = await db.Wallet.Include(w => w.Balances).Include(w => w.Transactions).AsSplitQuery().FirstOrDefaultAsync(w => w.MemberID == order.MemberID, ct);
  66. if (wallet is null)
  67. {
  68. return new Response(false, 0, "지갑 정보를 찾을 수 없습니다.", null);
  69. }
  70. wallet.CreditPgCharge(Money.KRW(order.PointAmount), $"포인트 충전 ({order.PaymentMethod})", order.OrderID);
  71. await db.SaveChangesAsync(ct);
  72. return new Response(true, order.PointAmount, "충전이 완료되었습니다.", order.PaidAt);
  73. }
  74. catch (Exception ex)
  75. {
  76. await db.DanalLog.AddAsync(DanalLog.Create(r.MemberID, DanalLogType.Error, "EXCEPTION", ex.Message, r.TransactionID, r.OrderID), ct);
  77. await db.SaveChangesAsync(ct);
  78. return new Response(false, 0, "결제 처리 중 오류가 발생했습니다.", null);
  79. }
  80. }
  81. }