| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- using System.Security.Cryptography;
- using Application.Abstractions.Data;
- using Application.Abstractions.Kyc;
- using Application.Abstractions.Messaging;
- using Domain.Entities.Developers;
- using Domain.Entities.Developers.ValueObject;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Api.Developers.Profile.StartKyc;
- internal sealed class Handler(IAppDbContext db, INiceAuthClient nice) : ICommandHandler<Command, Result<Response>>
- {
- public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
- {
- var dev = await db.Developer
- .AsTracking()
- .FirstOrDefaultAsync(c => c.MemberID == request.MemberID, ct);
- if (dev is null)
- {
- return Result.Failure<Response>(Error.NotFound(
- "Developer.NotFound",
- "회사 정보를 먼저 입력해 주세요."));
- }
- if (dev.KycStatus == KycStatus.Verified)
- {
- return Result.Failure<Response>(Error.Conflict(
- "Developer.AlreadyVerified",
- "이미 본인인증이 완료되었어요."));
- }
- // 기존 Pending 중 만료된 세션은 Expired 로 정리 (DB 위생)
- var now = DateTime.UtcNow;
- var stale = await db.NiceAuthSession
- .AsTracking()
- .Where(c => c.DeveloperID == dev.ID && c.Status == NiceAuthSessionStatus.Pending && c.ExpiresAt <= now)
- .ToListAsync(ct);
- foreach (var s in stale)
- {
- s.MarkExpired();
- }
- var sessionToken = GenerateSessionToken();
- var urlResult = await nice.RequestAuthUrlAsync(sessionToken, ct);
- if (urlResult.IsFailure)
- {
- return Result.Failure<Response>(urlResult.Error);
- }
- var info = urlResult.Value;
- var session = NiceAuthSession.Create(
- dev.ID,
- sessionToken,
- info.RequestNo,
- info.TransactionId,
- info.Ticket,
- info.Iterators);
- await db.NiceAuthSession.AddAsync(session, ct);
- dev.MarkKycPending();
- await db.SaveChangesAsync(ct);
- return Result.Success(new Response(info.AuthUrl, sessionToken));
- }
- /// <summary>
- /// SessionToken: 16 byte CSPRNG → URL-safe Base64 no-padding (22 char).
- /// NICE return_url path segment 로 embed 되므로 URL-safe 문자만 사용.
- /// </summary>
- private static string GenerateSessionToken()
- {
- var bytes = RandomNumberGenerator.GetBytes(16);
- return Convert.ToBase64String(bytes)
- .Replace('+', '-')
- .Replace('/', '_')
- .TrimEnd('=');
- }
- }
|