using Infrastructure.Kyc.Nice; using MediatR; using Microsoft.Extensions.Options; using Web.Api.Common; namespace Web.Api.Endpoints.Developers.Profile; internal sealed class KycCallback : IEndpoint { // NICE 표준창 → return_url 콜백. NICE 가 query string 으로 web_transaction_id 전달. // 이 endpoint 는 cookie 없는 외부 호출이라 anonymous. 검증은 NICE result API 가 web_transaction_id 의 유효성을 보장. // 처리 후 Frontend 의 결과 페이지로 302 redirect. public void MapEndpoint(IEndpointRouteBuilder app) { app.MapGet("api/developers/profile/kyc/callback/{sessionToken}", async ( string sessionToken, HttpContext httpContext, ISender sender, IOptions options, CancellationToken ct ) => { var frontUrl = options.Value.FrontendCallbackUrl; if (string.IsNullOrWhiteSpace(frontUrl)) { // Frontend URL 미설정 — 운영자 안내 return Results.Problem( detail: "FrontendCallbackUrl 이 설정되지 않았습니다.", statusCode: 500); } var webTransactionId = httpContext.Request.Query["web_transaction_id"].ToString(); if (string.IsNullOrEmpty(webTransactionId)) { return Results.Redirect(BuildRedirect(frontUrl, ok: false, "인증이 취소되었습니다.")); } var command = new Application.Features.Api.Developers.Profile.CompleteKyc.Command(sessionToken, webTransactionId); var result = await sender.Send(command, ct); return Results.Redirect(result.IsSuccess ? BuildRedirect(frontUrl, ok: true, null) : BuildRedirect(frontUrl, ok: false, result.Error.Description)); }) .WithTags("Developers") .AllowAnonymous(); } private static string BuildRedirect(string frontUrl, bool ok, string? message) { var separator = frontUrl.Contains('?') ? '&' : '?'; var ms = string.IsNullOrEmpty(message) ? string.Empty : "&message=" + Uri.EscapeDataString(message); return $"{frontUrl}{separator}ok={(ok ? 1 : 0)}{ms}"; } }