VirtualAccountNoti.cs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. using Application.Abstractions.Data;
  2. using Domain.Entities.Common.ValueObject;
  3. using Domain.Entities.Payments.Danal;
  4. using Domain.Entities.Payments.Danal.ValueObject;
  5. using Microsoft.EntityFrameworkCore;
  6. using SharedKernel.Extensions;
  7. namespace Web.Api.Endpoints.Payment;
  8. /// <summary>가상계좌 입금 통보 (다날 서버→서버)</summary>
  9. internal sealed class VirtualAccountNoti : IEndpoint
  10. {
  11. public void MapEndpoint(IEndpointRouteBuilder app)
  12. {
  13. /// 다날 NotiUrl 콜백 (IP: 150.242.132.116). 입금 확인 → 포인트 적립
  14. app.MapPost("api/payment/noti/vaccount", async (
  15. HttpContext httpContext,
  16. IAppDbContext db,
  17. CancellationToken ct
  18. ) => {
  19. // IP 검증
  20. var clientIP = httpContext.GetClientIP();
  21. // 요청 파싱
  22. var body = await httpContext.Request.ReadFromJsonAsync<VAccountNotiBody>(ct);
  23. if (body is null)
  24. {
  25. return Results.Json(new { code = "FAIL" }, statusCode: 400);
  26. }
  27. // 주문 조회
  28. var order = await db.PaymentOrder.FirstOrDefaultAsync(o => o.OrderID == body.OrderID, ct);
  29. if (order is null)
  30. {
  31. return Results.Json(new { code = "FAIL" });
  32. }
  33. // 중복 입금 방지
  34. if (order.Status == Domain.Entities.Payments.ValueObject.PaymentStatus.Paid)
  35. {
  36. return Results.Json(new { code = "SUCCESS" });
  37. }
  38. // 금액 확인
  39. if (!int.TryParse(body.Amount, out var amount) || amount != order.Amount)
  40. {
  41. await db.DanalLog.AddAsync(DanalLog.Create(
  42. order.MemberID, DanalLogType.Failed,
  43. "AMOUNT_MISMATCH", $"금액 불일치: 요청={body.Amount}, 주문={order.Amount}",
  44. body.TransactionID, body.OrderID), ct);
  45. await db.SaveChangesAsync(ct);
  46. return Results.Json(new { code = "FAIL" });
  47. }
  48. // 결제 완료 처리
  49. order.MarkPaid(body.TransactionID ?? "");
  50. // 지갑 포인트 충전
  51. var wallet = await db.Wallet.FirstOrDefaultAsync(w => w.MemberID == order.MemberID, ct);
  52. if (wallet is null)
  53. {
  54. await db.DanalLog.AddAsync(DanalLog.Create(
  55. order.MemberID, DanalLogType.Error,
  56. "WALLET_NOT_FOUND", "지갑 정보를 찾을 수 없습니다.",
  57. body.TransactionID, body.OrderID), ct);
  58. await db.SaveChangesAsync(ct);
  59. return Results.Json(new { code = "FAIL" });
  60. }
  61. wallet.CreditPgCharge(
  62. Money.KRW(order.PointAmount),
  63. "가상계좌 입금 완료",
  64. order.OrderID
  65. );
  66. await db.DanalLog.AddAsync(DanalLog.Create(
  67. order.MemberID, DanalLogType.Webhook,
  68. "SUCCESS", "가상계좌 입금 완료",
  69. body.TransactionID, body.OrderID), ct);
  70. await db.SaveChangesAsync(ct);
  71. return Results.Json(new { code = "SUCCESS" });
  72. })
  73. .WithTags("Payment")
  74. .AllowAnonymous();
  75. }
  76. private sealed record VAccountNotiBody(
  77. string? Code,
  78. string? Message,
  79. string? TransactionID,
  80. string? OrderID,
  81. string? Amount
  82. );
  83. }