using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Domain.Entities.Members; using Domain.Entities.Payments.ValueObject; namespace Domain.Entities.Payments; public class PaymentOrder { [ForeignKey(nameof(MemberID))] public virtual Member? Member { get; private set; } [Key] public int ID { get; private set; } public int MemberID { get; private set; } /// 가맹점 주문번호 (unique, 중복 불가) public string OrderID { get; private set; } = default!; /// 다날 CPID (계약 완료 후 발급) public string MerchantID { get; private set; } = default!; /// 상품명 public string OrderName { get; private set; } = "포인트 충전"; /// 다날 거래번호 (승인 후 저장) public string? TransactionID { get; private set; } /// 결제 금액 (부가세 포함 총액) public int Amount { get; private set; } /// 충전될 포인트 (= 결제금액, 1:1) public int PointAmount { get; private set; } /// 부가세 (표시용) public int VatAmount { get; private set; } public PaymentMethod PaymentMethod { get; private set; } public PaymentStatus Status { get; private set; } public DateTime? PaidAt { get; private set; } public DateTime? CancelledAt { get; private set; } public string? FailReason { get; private set; } /// 가상계좌: 계좌번호 public string? VirtualAccountNumber { get; private set; } /// 가상계좌: 은행명 public string? VirtualAccountBank { get; private set; } /// 가상계좌: 예금주 public string? VirtualAccountHolder { get; private set; } /// 가상계좌: 입금 기한 public DateTime? VirtualAccountExpireAt { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private PaymentOrder() { } public static PaymentOrder Create( int memberID, string orderID, string merchantID, int amount, PaymentMethod method, string orderName = "포인트 충전" ) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(memberID); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount); if (string.IsNullOrWhiteSpace(orderID)) { throw new ArgumentException("orderID required", nameof(orderID)); } if (string.IsNullOrWhiteSpace(merchantID)) { throw new ArgumentException("merchantID required", nameof(merchantID)); } var vat = (int)Math.Round(amount / 11.0); return new PaymentOrder { MemberID = memberID, OrderID = orderID, MerchantID = merchantID, OrderName = orderName, Amount = amount, PointAmount = amount, VatAmount = vat, PaymentMethod = method, Status = PaymentStatus.Pending }; } public void MarkPaid(string transactionID) { if (Status is not PaymentStatus.Pending and not PaymentStatus.WaitingDeposit) { throw new InvalidOperationException($"Cannot mark as paid from status {Status}"); } TransactionID = transactionID; Status = PaymentStatus.Paid; PaidAt = DateTime.UtcNow; } public void MarkFailed(string reason) { Status = PaymentStatus.Failed; FailReason = reason; } public void MarkCancelled() { if (Status != PaymentStatus.Paid) { throw new InvalidOperationException($"Cannot cancel from status {Status}"); } Status = PaymentStatus.Cancelled; CancelledAt = DateTime.UtcNow; } public void MarkWaitingDeposit(string accountNumber, string bank, string holder, DateTime expireAt) { Status = PaymentStatus.WaitingDeposit; VirtualAccountNumber = accountNumber; VirtualAccountBank = bank; VirtualAccountHolder = holder; VirtualAccountExpireAt = expireAt; } }