using Application.Abstractions.Data;
using Domain.Entities.Common.ValueObject;
using Domain.Entities.Payments.Danal;
using Domain.Entities.Payments.Danal.ValueObject;
using Microsoft.EntityFrameworkCore;
using SharedKernel.Extensions;
namespace Web.Api.Endpoints.Payment;
/// 가상계좌 입금 통보 (다날 서버→서버)
internal sealed class VirtualAccountNoti : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
/// 다날 NotiUrl 콜백 (IP: 150.242.132.116). 입금 확인 → 포인트 적립
app.MapPost("api/payment/noti/vaccount", async (
HttpContext httpContext,
IAppDbContext db,
CancellationToken ct
) => {
// IP 검증
var clientIP = httpContext.GetClientIP();
// 요청 파싱
var body = await httpContext.Request.ReadFromJsonAsync(ct);
if (body is null)
{
return Results.Json(new { code = "FAIL" }, statusCode: 400);
}
// 주문 조회
var order = await db.PaymentOrder.FirstOrDefaultAsync(o => o.OrderID == body.OrderID, ct);
if (order is null)
{
return Results.Json(new { code = "FAIL" });
}
// 중복 입금 방지
if (order.Status == Domain.Entities.Payments.ValueObject.PaymentStatus.Paid)
{
return Results.Json(new { code = "SUCCESS" });
}
// 금액 확인
if (!int.TryParse(body.Amount, out var amount) || amount != order.Amount)
{
await db.DanalLog.AddAsync(DanalLog.Create(
order.MemberID, DanalLogType.Failed,
"AMOUNT_MISMATCH", $"금액 불일치: 요청={body.Amount}, 주문={order.Amount}",
body.TransactionID, body.OrderID), ct);
await db.SaveChangesAsync(ct);
return Results.Json(new { code = "FAIL" });
}
// 결제 완료 처리
order.MarkPaid(body.TransactionID ?? "");
// 지갑 포인트 충전
var wallet = await db.Wallet.FirstOrDefaultAsync(w => w.MemberID == order.MemberID, ct);
if (wallet is null)
{
await db.DanalLog.AddAsync(DanalLog.Create(
order.MemberID, DanalLogType.Error,
"WALLET_NOT_FOUND", "지갑 정보를 찾을 수 없습니다.",
body.TransactionID, body.OrderID), ct);
await db.SaveChangesAsync(ct);
return Results.Json(new { code = "FAIL" });
}
wallet.CreditPgCharge(
Money.KRW(order.PointAmount),
"가상계좌 입금 완료",
order.OrderID
);
await db.DanalLog.AddAsync(DanalLog.Create(
order.MemberID, DanalLogType.Webhook,
"SUCCESS", "가상계좌 입금 완료",
body.TransactionID, body.OrderID), ct);
await db.SaveChangesAsync(ct);
return Results.Json(new { code = "SUCCESS" });
})
.WithTags("Payment")
.AllowAnonymous();
}
private sealed record VAccountNotiBody(
string? Code,
string? Message,
string? TransactionID,
string? OrderID,
string? Amount
);
}