using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Developers.ValueObject; namespace Domain.Entities.Developers; /// /// NICE 통합인증 1회 진행 세션. StartKyc 시 생성 → callback 에서 매칭 → Completed/Failed/Expired. /// /// 가이드: .claude/plan/nice-auth-integration.md §5 /// public class NiceAuthSession { /// 유효기간: 10분 (NICE transaction_id 의 효력과 동일). public static readonly TimeSpan DefaultLifetime = TimeSpan.FromMinutes(10); [ForeignKey(nameof(DeveloperID))] public virtual Developer? Developer { get; private set; } [Key] public int ID { get; private set; } public int DeveloperID { get; private set; } /// /// 가맹점 측 세션 식별자. NICE return_url path segment 로 embed → callback 매칭. /// URL-safe (Base64Url no-padding 22 char). /// public string SessionToken { get; private set; } = default!; /// NICE auth/url request 의 request_no. public string RequestNo { get; private set; } = default!; /// NICE auth/url 응답의 transaction_id. public string TransactionID { get; private set; } = default!; /// NICE 표준창이 return_url 로 넘긴 web_transaction_id (callback 시점에 set). public string? WebTransactionID { get; private set; } /// auth/url 호출 시점 token 의 ticket. result 복호화 KDF 입력. public string Ticket { get; private set; } = default!; /// auth/url 호출 시점 token 의 iterators. result 복호화 KDF 입력. public int Iterators { get; private set; } public NiceAuthSessionStatus Status { get; private set; } = NiceAuthSessionStatus.Pending; public string? FailReason { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime ExpiresAt { get; private set; } public DateTime? CompletedAt { get; private set; } private NiceAuthSession() { } public static NiceAuthSession Create( int developerID, string sessionToken, string requestNo, string transactionID, string ticket, int iterators ) { if (string.IsNullOrWhiteSpace(sessionToken)) { throw new ArgumentException("Session token is required.", nameof(sessionToken)); } if (string.IsNullOrWhiteSpace(requestNo)) { throw new ArgumentException("Request no is required.", nameof(requestNo)); } if (string.IsNullOrWhiteSpace(transactionID)) { throw new ArgumentException("Transaction ID is required.", nameof(transactionID)); } if (string.IsNullOrWhiteSpace(ticket)) { throw new ArgumentException("Ticket is required.", nameof(ticket)); } if (iterators <= 0) { throw new ArgumentOutOfRangeException(nameof(iterators), iterators, "iterators must be positive."); } var now = DateTime.UtcNow; return new NiceAuthSession { DeveloperID = developerID, SessionToken = sessionToken, RequestNo = requestNo, TransactionID = transactionID, Ticket = ticket, Iterators = iterators, Status = NiceAuthSessionStatus.Pending, CreatedAt = now, ExpiresAt = now + DefaultLifetime }; } /// callback 에서 web_transaction_id 받아 기록 — result 호출 직전 단계. public void MarkWebTransactionReceived(string webTransactionID) { if (string.IsNullOrWhiteSpace(webTransactionID)) { throw new ArgumentException("Web transaction ID is required.", nameof(webTransactionID)); } WebTransactionID = webTransactionID; } public void MarkCompleted() { Status = NiceAuthSessionStatus.Completed; CompletedAt = DateTime.UtcNow; } public void MarkFailed(string reason) { Status = NiceAuthSessionStatus.Failed; FailReason = reason; CompletedAt = DateTime.UtcNow; } public void MarkExpired() { Status = NiceAuthSessionStatus.Expired; FailReason = "유효시간이 만료되었습니다."; CompletedAt = DateTime.UtcNow; } public bool IsExpired(DateTime utcNow) => utcNow >= ExpiresAt; public bool CanProcessCallback() => Status == NiceAuthSessionStatus.Pending; }