Handler.cs 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.Payment;
  4. using Domain.Entities.Payments;
  5. using Domain.Entities.Payments.ValueObject;
  6. using Microsoft.EntityFrameworkCore;
  7. using SharedKernel.Results;
  8. namespace Application.Features.Api.Payment.Toss.CreateOrder;
  9. internal sealed class Handler(
  10. IAppDbContext db,
  11. ITossPaymentService tossPay
  12. ) : ICommandHandler<Command, Result<Response>>
  13. {
  14. /// <summary>토스에서 사용할 MerchantID 표기 (PaymentOrder.MerchantID 필수 컬럼)</summary>
  15. private const string TossMerchantMark = "TOSS";
  16. public async Task<Result<Response>> Handle(Command r, CancellationToken ct)
  17. {
  18. if (r.Amount <= 0)
  19. {
  20. return Result.Failure<Response>(new Error("Payment.InvalidAmount", "충전 금액이 올바르지 않습니다.", ErrorType.Validation));
  21. }
  22. var member = await db.Member.FirstOrDefaultAsync(c => c.ID == r.MemberID, ct);
  23. if (member is null)
  24. {
  25. return Result.Failure<Response>(Error.NotFound("Payment.MemberNotFound", "회원 정보를 찾을 수 없습니다."));
  26. }
  27. // customerKey — 예측 가능 값(SID/이메일/PK) 금지, 회원별 UUID 발급·저장 (Guid 문자열은 '-' 포함 = 특수문자 조건 충족)
  28. var customerKey = member.EnsurePaymentCustomerKey();
  29. // 주문번호 생성 — 6~64자, 영숫자와 -_ 만 (기존 prefix 방식 미러링)
  30. var orderID = $"ANTOOZA-{DateTime.UtcNow:yyyyMMddHHmmss}-{r.MemberID}-{Random.Shared.Next(1000, 9999)}";
  31. if (orderID.Length is < 6 or > 64 || orderID.Any(c => !char.IsAsciiLetterOrDigit(c) && c != '-' && c != '_'))
  32. {
  33. return Result.Failure<Response>(new Error("Payment.InvalidOrderID", "주문번호 생성에 실패했습니다.", ErrorType.Validation));
  34. }
  35. // 토스 클라이언트 설정 (ClientKey 획득 — 설정 미비 시 예외로 조기 실패)
  36. var config = await tossPay.GetClientConfigAsync(ct);
  37. // 서버가 금액·orderName 확정 저장 — Confirm 시 클라이언트 금액과 대조해 변조 차단
  38. var order = PaymentOrder.Create(r.MemberID, orderID, TossMerchantMark, r.Amount, PaymentMethod.Integrated, "개미투자 캐시 충전");
  39. await db.PaymentOrder.AddAsync(order, ct);
  40. await db.SaveChangesAsync(ct);
  41. return new Response(
  42. orderID,
  43. order.OrderName,
  44. order.Amount,
  45. order.PointAmount,
  46. order.VatAmount,
  47. config.ClientKey,
  48. customerKey.ToString()
  49. );
  50. }
  51. }