Handler.cs 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Store.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. using SharedKernel.Results;
  6. namespace Application.Features.Api.Store.Inventory.Use;
  7. internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result<Response>>
  8. {
  9. public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
  10. {
  11. var inv = await db.MemberInventory
  12. .Include(i => i.CouponCode).ThenInclude(c => c!.Coupon)
  13. .FirstOrDefaultAsync(i => i.ID == request.InventoryID, ct);
  14. if (inv is null)
  15. {
  16. return Result.Failure<Response>(Error.NotFound("Inventory.NotFound", "보관함 항목을 찾을 수 없습니다."));
  17. }
  18. // 본인 소유 검증 + 소프트 삭제 검증
  19. if (inv.MemberID != request.MemberID || inv.DeletedAt is not null)
  20. {
  21. return Result.Failure<Response>(Error.NotFound("Inventory.NotFound", "보관함 항목을 찾을 수 없습니다."));
  22. }
  23. if (inv.UsedAt is not null)
  24. {
  25. return Result.Failure<Response>(Error.Conflict("Inventory.AlreadyUsed", "이미 사용한 쿠폰입니다."));
  26. }
  27. if (inv.ExpiresAt is not null && inv.ExpiresAt.Value < DateTime.UtcNow)
  28. {
  29. return Result.Failure<Response>(Error.Problem("Inventory.Expired", "만료된 쿠폰입니다."));
  30. }
  31. if (inv.CouponCode is null || inv.CouponCode.Coupon is null)
  32. {
  33. return Result.Failure<Response>(Error.Problem("Inventory.InvalidState", "쿠폰 코드 정보를 찾을 수 없습니다."));
  34. }
  35. // 관리자에 의해 코드 풀에서 만료 처리된 코드는 사용 차단 (Defense in depth — 통상 ExpiresAt 가드에서 걸림)
  36. if (inv.CouponCode.Status == CouponCodeStatus.Expired)
  37. {
  38. return Result.Failure<Response>(Error.Problem("Inventory.Expired", "만료된 쿠폰입니다."));
  39. }
  40. inv.MarkUsed();
  41. inv.CouponCode.MarkUsed();
  42. inv.CouponCode.Coupon.IncreaseUsedCount();
  43. await db.SaveChangesAsync(ct);
  44. return Result.Success(new Response(inv.CouponCode.Code, inv.UsedAt!.Value));
  45. }
  46. }