PostCheer.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Members;
  4. namespace Domain.Entities.Forum.Posts;
  5. /// <summary>
  6. /// 분석글 응원(Cheer) — D3 M4 (Twitch Bits 모델).
  7. /// 후원자(From)가 글 작성자(To)에게 캐시/토큰을 팁으로 보내며 플랫폼이 수수료를 취한다.
  8. /// 순액(NetAmount = Amount − FeeAmount)만 작성자 지갑 Reward 파티션으로 적립되고(출금 불가),
  9. /// 수수료(FeeAmount)는 어디에도 적립되지 않고 소각(=유통에서 제거)된다.
  10. ///
  11. /// 규제 가드라인(§⑧): 응원은 사후·자율·무대가이며 글 열람 조건이 아니다(글은 항상 무료).
  12. /// 본인 글 응원 금지·일일 발신 캡·수신 적립분 출금 불가는 자전 수수료 소각 어뷰징을 차단한다.
  13. /// </summary>
  14. public class PostCheer
  15. {
  16. [ForeignKey(nameof(PostID))]
  17. public virtual Post Post { get; private set; } = null!;
  18. [ForeignKey(nameof(FromMemberID))]
  19. public virtual Member? FromMember { get; private set; }
  20. [ForeignKey(nameof(ToMemberID))]
  21. public virtual Member? ToMember { get; private set; }
  22. [Key]
  23. public int ID { get; private set; }
  24. /// <summary>응원 대상 게시글</summary>
  25. public int PostID { get; private set; }
  26. /// <summary>응원 발신자 (후원자)</summary>
  27. public int FromMemberID { get; private set; }
  28. /// <summary>응원 수신자 (글 작성자 스냅샷)</summary>
  29. public int ToMemberID { get; private set; }
  30. /// <summary>총 응원 금액 (발신자 차감액)</summary>
  31. public int Amount { get; private set; }
  32. /// <summary>플랫폼 수수료 (소각분, 어디에도 적립되지 않음)</summary>
  33. public int FeeAmount { get; private set; }
  34. /// <summary>작성자 수령 순액 (Amount − FeeAmount, Reward 파티션 적립)</summary>
  35. public int NetAmount { get; private set; }
  36. /// <summary>응원 한마디 (선택)</summary>
  37. public string? Message { get; private set; }
  38. public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
  39. private PostCheer() { }
  40. private PostCheer(int postID, int fromMemberID, int toMemberID, int amount, int feeAmount, int netAmount, string? message)
  41. {
  42. PostID = postID;
  43. FromMemberID = fromMemberID;
  44. ToMemberID = toMemberID;
  45. Amount = amount;
  46. FeeAmount = feeAmount;
  47. NetAmount = netAmount;
  48. Message = message;
  49. }
  50. public static PostCheer Create(int postID, int fromMemberID, int toMemberID, int amount, int feeAmount, int netAmount, string? message)
  51. {
  52. if (postID <= 0)
  53. {
  54. throw new ArgumentOutOfRangeException(nameof(postID));
  55. }
  56. if (fromMemberID <= 0)
  57. {
  58. throw new ArgumentOutOfRangeException(nameof(fromMemberID));
  59. }
  60. if (toMemberID <= 0)
  61. {
  62. throw new ArgumentOutOfRangeException(nameof(toMemberID));
  63. }
  64. if (fromMemberID == toMemberID)
  65. {
  66. throw new ArgumentException("본인 글에는 응원할 수 없습니다.", nameof(toMemberID));
  67. }
  68. if (amount <= 0)
  69. {
  70. throw new ArgumentOutOfRangeException(nameof(amount));
  71. }
  72. if (feeAmount < 0 || netAmount <= 0 || feeAmount + netAmount != amount)
  73. {
  74. throw new ArgumentException("수수료/순액 계산이 올바르지 않습니다.", nameof(netAmount));
  75. }
  76. return new PostCheer(postID, fromMemberID, toMemberID, amount, feeAmount, netAmount, message);
  77. }
  78. }