Handler.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Common;
  4. using Domain.Entities.Payments.ValueObject;
  5. using Domain.Entities.Wallets.ValueObject;
  6. using SharedKernel.Results;
  7. using Microsoft.EntityFrameworkCore;
  8. namespace Application.Features.Api.MyPage.ChargeLogs;
  9. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  10. {
  11. // 적립: 보상/후원받음/상점채널보상/잠금해제/Adjusted(+)
  12. private static readonly HashSet<WalletTransactionType> EarnTypes = new()
  13. {
  14. WalletTransactionType.RewardEarned,
  15. WalletTransactionType.DonationIn,
  16. WalletTransactionType.OrderChannelReward,
  17. WalletTransactionType.Unlock,
  18. WalletTransactionType.Adjusted
  19. };
  20. // 사용: 일반사용/후원보냄/상점결제/출금/상점채널보상차감 — Lock 은 제외
  21. private static readonly HashSet<WalletTransactionType> SpendTypes = new()
  22. {
  23. WalletTransactionType.Spend,
  24. WalletTransactionType.DonationOut,
  25. WalletTransactionType.OrderPay,
  26. WalletTransactionType.WithdrawalStoreRevenue,
  27. WalletTransactionType.OrderRefundChannelReward,
  28. WalletTransactionType.Adjusted
  29. };
  30. // 환불
  31. private static readonly HashSet<WalletTransactionType> RefundTypes = new()
  32. {
  33. WalletTransactionType.Refund,
  34. WalletTransactionType.OrderRefund
  35. };
  36. // 전체: Lock 만 제외한 모든 TxType
  37. private static readonly HashSet<WalletTransactionType> AllTypes = new()
  38. {
  39. WalletTransactionType.Charge,
  40. WalletTransactionType.DonationIn,
  41. WalletTransactionType.DonationOut,
  42. WalletTransactionType.RewardEarned,
  43. WalletTransactionType.Spend,
  44. WalletTransactionType.Refund,
  45. WalletTransactionType.Unlock,
  46. WalletTransactionType.Adjusted,
  47. WalletTransactionType.OrderPay,
  48. WalletTransactionType.OrderRefund,
  49. WalletTransactionType.OrderChannelReward,
  50. WalletTransactionType.OrderRefundChannelReward,
  51. WalletTransactionType.WithdrawalStoreRevenue
  52. };
  53. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  54. {
  55. var page = request.Page < 1 ? 1 : request.Page;
  56. var perPage = request.PerPage is < 1 or > 50 ? 20 : request.PerPage;
  57. var now = DateTime.UtcNow;
  58. var startDate = request.Type switch
  59. {
  60. SearchDateType.Week => now.AddDays(-7),
  61. SearchDateType.Month => now.AddMonths(-1),
  62. SearchDateType.QuarterYear => now.AddMonths(-3),
  63. SearchDateType.HalfYear => now.AddMonths(-6),
  64. _ => now.Date
  65. };
  66. if (request.Category == WalletLogCategory.Charge)
  67. {
  68. return await HandleChargeAsync(request, page, perPage, startDate, ct);
  69. }
  70. return await HandleWalletAsync(request, page, perPage, startDate, ct);
  71. }
  72. // 충전 카테고리 (기존 PaymentOrder 기반)
  73. private async Task<Result<Response>> HandleChargeAsync(Query request, int page, int perPage, DateTime startDate, CancellationToken ct)
  74. {
  75. var query = db.PaymentOrder.AsNoTracking().Where(o => o.MemberID == request.MemberID && o.Status == PaymentStatus.Paid && o.PaidAt >= startDate).OrderByDescending(o => o.PaidAt);
  76. var total = await query.CountAsync(ct);
  77. var list = await query
  78. .Skip((page - 1) * perPage)
  79. .Take(perPage)
  80. .Select(o => new ChargeLogItem(
  81. o.ID,
  82. WalletLogCategory.Charge,
  83. o.OrderID,
  84. o.Amount,
  85. o.PointAmount,
  86. o.VatAmount,
  87. o.PaymentMethod.ToString(),
  88. o.Status.ToString(),
  89. null,
  90. null,
  91. null,
  92. null,
  93. null,
  94. o.CreatedAt,
  95. o.PaidAt
  96. ))
  97. .ToListAsync(ct);
  98. return Result.Success(new Response(list, total, page, perPage));
  99. }
  100. // 전체/적립/사용/환불 카테고리 (WalletTransaction 기반)
  101. private async Task<Result<Response>> HandleWalletAsync(Query request, int page, int perPage, DateTime startDate, CancellationToken ct)
  102. {
  103. var wallet = await db.Wallet.AsNoTracking().FirstOrDefaultAsync(w => w.MemberID == request.MemberID, ct);
  104. if (wallet is null)
  105. {
  106. return Result.Success(new Response([], 0, page, perPage));
  107. }
  108. var (typeSet, adjustedDirection) = ResolveTypeFilter(request.Category);
  109. var query = db.WalletTransaction.AsNoTracking()
  110. .Where(t => t.WalletKey == wallet.WalletKey && t.CreatedAt >= startDate && typeSet.Contains(t.TxType));
  111. // Adjusted 는 양방향 — Reason prefix 로 earn/spend 구분
  112. if (adjustedDirection == "earn")
  113. {
  114. query = query.Where(t => t.TxType != WalletTransactionType.Adjusted || !t.Reason.StartsWith("ADJUST_OUT:"));
  115. }
  116. else if (adjustedDirection == "spend")
  117. {
  118. query = query.Where(t => t.TxType != WalletTransactionType.Adjusted || t.Reason.StartsWith("ADJUST_OUT:"));
  119. }
  120. var ordered = query.OrderByDescending(t => t.ID);
  121. var total = await ordered.CountAsync(ct);
  122. var list = await ordered
  123. .Skip((page - 1) * perPage)
  124. .Take(perPage)
  125. .Select(t => new ChargeLogItem(
  126. t.ID,
  127. request.Category,
  128. null,
  129. (long)t.Amount.Value,
  130. null,
  131. null,
  132. null,
  133. null,
  134. t.TxType.ToString(),
  135. t.BalanceType.ToString(),
  136. t.Reason,
  137. t.RefID,
  138. (long)t.BalanceAfter.Value,
  139. t.CreatedAt,
  140. null
  141. ))
  142. .ToListAsync(ct);
  143. return Result.Success(new Response(list, total, page, perPage));
  144. }
  145. private static (HashSet<WalletTransactionType> Types, string? AdjustedDirection) ResolveTypeFilter(string category)
  146. {
  147. return category switch
  148. {
  149. WalletLogCategory.Earn => (EarnTypes, "earn"),
  150. WalletLogCategory.Spend => (SpendTypes, "spend"),
  151. WalletLogCategory.Refund => (RefundTypes, null),
  152. _ => (AllTypes, null) // All
  153. };
  154. }
  155. }