| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- 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; }
- /// <summary>가맹점 주문번호 (unique, 중복 불가)</summary>
- public string OrderID { get; private set; } = default!;
- /// <summary>다날 CPID (계약 완료 후 발급)</summary>
- public string MerchantID { get; private set; } = default!;
- /// <summary>상품명</summary>
- public string OrderName { get; private set; } = "포인트 충전";
- /// <summary>다날 거래번호 (승인 후 저장)</summary>
- public string? TransactionID { get; private set; }
- /// <summary>PG 결제 총액 (= PointAmount + VatAmount, 부가세 별도 가산)</summary>
- public int Amount { get; private set; }
- /// <summary>충전될 포인트 (사용자 입력값, VAT 제외)</summary>
- public int PointAmount { get; private set; }
- /// <summary>부가세 (= PointAmount × 10%)</summary>
- 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; }
- /// <summary>가상계좌: 계좌번호</summary>
- public string? VirtualAccountNumber { get; private set; }
- /// <summary>가상계좌: 은행명</summary>
- public string? VirtualAccountBank { get; private set; }
- /// <summary>가상계좌: 예금주</summary>
- public string? VirtualAccountHolder { get; private set; }
- /// <summary>가상계좌: 입금 기한</summary>
- public DateTime? VirtualAccountExpireAt { get; private set; }
- public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
- private PaymentOrder() { }
- /// <summary>
- /// 주문 생성. <paramref name="pointAmount"/>는 사용자가 충전하려는 포인트(VAT 제외).
- /// VAT 10%는 별도 가산되며 PG 결제 총액 = pointAmount + vat.
- /// </summary>
- public static PaymentOrder Create(
- int memberID,
- string orderID,
- string merchantID,
- int pointAmount,
- PaymentMethod method,
- string orderName = "포인트 충전"
- ) {
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero(memberID);
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pointAmount);
- if (string.IsNullOrWhiteSpace(orderID)) {
- throw new ArgumentException("orderID required", nameof(orderID));
- }
- if (string.IsNullOrWhiteSpace(merchantID)) {
- throw new ArgumentException("merchantID required", nameof(merchantID));
- }
- // VAT 10% 별도 가산. PG 결제 총액 = pointAmount + vat.
- var vat = (int)Math.Round(pointAmount * 0.1);
- return new PaymentOrder
- {
- MemberID = memberID,
- OrderID = orderID,
- MerchantID = merchantID,
- OrderName = orderName,
- Amount = pointAmount + vat,
- PointAmount = pointAmount,
- VatAmount = vat,
- PaymentMethod = method,
- Status = PaymentStatus.Pending
- };
- }
- public void MarkPaid(string transactionID)
- {
- // NeedsReconciliation 재승인(재확인) 성공 시에도 Paid 로 전이 가능해야 한다. (Phase 1.5)
- if (Status is not PaymentStatus.Pending and not PaymentStatus.WaitingDeposit and not PaymentStatus.NeedsReconciliation)
- {
- 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;
- }
- /// <summary>
- /// PG 승인 응답이 불명(통신 타임아웃 등)일 때 호출. 다날이 실제 승인했을 수 있어 Failed 로 단정하지 않고
- /// 대사 대상으로 표시한다. Pending/WaitingDeposit(첫 시도) 또는 NeedsReconciliation(재확인도 다시 불명)에서만 전이. (Phase 1.5)
- /// </summary>
- public void MarkNeedsReconciliation(string reason)
- {
- if (Status is not PaymentStatus.Pending and not PaymentStatus.WaitingDeposit and not PaymentStatus.NeedsReconciliation)
- {
- throw new InvalidOperationException($"Cannot mark as needs-reconciliation from status {Status}");
- }
- Status = PaymentStatus.NeedsReconciliation;
- 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;
- }
- }
|