| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- using System.ComponentModel.DataAnnotations;
- using System.ComponentModel.DataAnnotations.Schema;
- using Domain.Entities.Members;
- using Domain.Entities.Store.ValueObject;
- namespace Domain.Entities.Store;
- /// <summary>
- /// 상점 주문 헤더. 단일 결제 = 단일 Order.
- /// 결제 시점에 수익 배분 (PlatformFee/GameRevenue/ChannelReward) 동결.
- /// 채널 미선택 주문은 ChannelRewardAmount=0, ChannelID=null. ChannelReward 만큼 PlatformFee 가 증가 (dpot 100%).
- /// </summary>
- public class Order
- {
- [ForeignKey(nameof(MemberID))]
- public virtual Member? Member { get; private set; }
- [ForeignKey(nameof(ChannelID))]
- public virtual Channel? Channel { get; private set; }
- private readonly List<OrderItem> _items = [];
- public IReadOnlyCollection<OrderItem> Items => _items;
- private readonly List<OrderRefund> _refunds = [];
- public IReadOnlyCollection<OrderRefund> Refunds => _refunds;
- [Key]
- public int ID { get; private set; }
- /// <summary>주문 번호 (예: "20260522-A1B2C3"). UNIQUE. 사용자 표시용.</summary>
- public string OrderNumber { get; private set; } = default!;
- public int MemberID { get; private set; }
- /// <summary>후원 채널 (선택). null 이면 dpot 100% 수익.</summary>
- public int? ChannelID { get; private set; }
- public int TotalAmount { get; private set; }
- /// <summary>dpot 몫 (= TotalAmount - GameRevenueAmount - ChannelRewardAmount).</summary>
- public int PlatformFeeAmount { get; private set; }
- /// <summary>게임사 정산 큐 적재 금액 (= TotalAmount × Game.CommissionRate / 100).</summary>
- public int GameRevenueAmount { get; private set; }
- /// <summary>채널주 StoreRevenue 입금 금액 (= TotalAmount × Channel.StoreCommissionRate / 100, 채널 선택 시).</summary>
- public int ChannelRewardAmount { get; private set; }
- public OrderStatus Status { get; private set; } = OrderStatus.Pending;
- public string? CancelReason { get; private set; }
- public string? RefundReason { get; private set; }
- public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
- public DateTime? PaidAt { get; private set; }
- [Timestamp]
- public byte[] RowVersion { get; private set; } = default!;
- private Order() { }
- public static Order Create(
- string orderNumber,
- int memberID,
- int totalAmount,
- int? channelID,
- int platformFeeAmount,
- int gameRevenueAmount,
- int channelRewardAmount
- ) {
- if (string.IsNullOrWhiteSpace(orderNumber))
- {
- throw new ArgumentException("OrderNumber is required.", nameof(orderNumber));
- }
- if (orderNumber.Length > 20)
- {
- throw new ArgumentOutOfRangeException(nameof(orderNumber));
- }
- if (memberID <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(memberID));
- }
- if (totalAmount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(totalAmount));
- }
- if (platformFeeAmount < 0 || gameRevenueAmount < 0 || channelRewardAmount < 0)
- {
- throw new ArgumentOutOfRangeException(null, "수익 배분 금액은 음수 불가.");
- }
- if (platformFeeAmount + gameRevenueAmount + channelRewardAmount != totalAmount)
- {
- throw new ArgumentException("PlatformFee + GameRevenue + ChannelReward 의 합이 TotalAmount 와 일치해야 합니다.");
- }
- if (channelID is null && channelRewardAmount > 0)
- {
- throw new ArgumentException("채널 미선택 주문은 ChannelRewardAmount=0 이어야 합니다.");
- }
- return new Order
- {
- OrderNumber = orderNumber,
- MemberID = memberID,
- ChannelID = channelID,
- TotalAmount = totalAmount,
- PlatformFeeAmount = platformFeeAmount,
- GameRevenueAmount = gameRevenueAmount,
- ChannelRewardAmount = channelRewardAmount
- };
- }
- public void AddItem(OrderItem item)
- {
- ArgumentNullException.ThrowIfNull(item);
- _items.Add(item);
- }
- public void MarkPaid()
- {
- if (Status != OrderStatus.Pending)
- {
- throw new InvalidOperationException($"Cannot mark paid from status {Status}.");
- }
- Status = OrderStatus.Paid;
- PaidAt = DateTime.UtcNow;
- }
- /// <summary>Digital 상품 단독 주문 시 Paid → Delivered 점프.</summary>
- public void MarkDelivered()
- {
- if (Status is not OrderStatus.Paid and not OrderStatus.Shipped)
- {
- throw new InvalidOperationException($"Cannot mark delivered from status {Status}.");
- }
- Status = OrderStatus.Delivered;
- }
- public void MarkPreparing()
- {
- if (Status != OrderStatus.Paid)
- {
- throw new InvalidOperationException($"Cannot mark preparing from status {Status}.");
- }
- Status = OrderStatus.Preparing;
- }
- public void MarkShipped()
- {
- if (Status is not OrderStatus.Paid and not OrderStatus.Preparing)
- {
- throw new InvalidOperationException($"Cannot mark shipped from status {Status}.");
- }
- Status = OrderStatus.Shipped;
- }
- public void Cancel(string reason)
- {
- if (Status is not OrderStatus.Pending and not OrderStatus.Paid)
- {
- throw new InvalidOperationException($"Cannot cancel from status {Status}.");
- }
- if (string.IsNullOrWhiteSpace(reason))
- {
- throw new ArgumentException("Cancel reason is required.", nameof(reason));
- }
- Status = OrderStatus.Cancelled;
- CancelReason = reason;
- }
- public void MarkRefunded(string reason)
- {
- if (string.IsNullOrWhiteSpace(reason))
- {
- throw new ArgumentException("Refund reason is required.", nameof(reason));
- }
- Status = OrderStatus.Refunded;
- RefundReason = reason;
- }
- public void MarkExchanged()
- {
- Status = OrderStatus.Exchanged;
- }
- }
|