결제 없이 멤버십을 발급하는 단일 사용(single-use) 토큰. 어드민이 상품 유형(STANDARD/PREMIUM)을
지정해 일괄 발급하고, 사용자가 코드를 입력하면 즉시 해당 티어의 멤버십이 생성된다.
쿠폰은 한 번 사용되면 영구히 USED 상태로 고정되며, 만료일·할인율 같은 부가 속성은 없다.
sequenceDiagram
autonumber
participant ADMIN as 어드민
participant FE as 어드민 콘솔
participant API as Backend (AdminController)
participant SVC as AdminService
participant DB as MySQL
ADMIN->>FE: 쿠폰 발급 (productType=PREMIUM, amount=50)
FE->>API: POST /admin/coupons { productType, amount }
API->>SVC: generateCoupon(productType, amount)
loop amount회 반복
SVC->>SVC: Coupon.createRandom(productType) (UUID 앞 5자리, 대문자)
end
SVC->>DB: couponRepository.saveAll(coupons)
API-->>FE: 200 OK
Note over ADMIN: 발급된 코드는 GET /admin/coupons로 조회 (사용 여부 + 사용한 계정 정보 포함)
sequenceDiagram
autonumber
participant U as 사용자
participant FE as 프론트엔드
participant API as Backend (AccountController)
participant SVC as AccountService
participant DB as MySQL
U->>FE: 쿠폰 코드 입력 (예: "A3F9C")
FE->>API: POST /accounts/coupons { code }
API->>SVC: useCoupon(accountId, code)
SVC->>DB: couponRepository.findByIdOrNull(code)
alt 코드 없음
SVC-->>FE: { result: INVALID }
else 이미 사용됨
SVC-->>FE: { result: USED }
else 정상
SVC->>SVC: account.initMembershipByCoupon(coupon)
Note over SVC: 1) Coupon.use() — status=USED, usedTime=now 2) Membership.byCoupon — 오늘부터 30일
SVC->>DB: INSERT membership (productType→membershipType)
SVC->>DB: UPDATE coupon SET status=USED
SVC-->>FE: { result: OK }
end
sequenceDiagram
autonumber
participant ADMIN as 어드민
participant API as Backend (AdminController)
participant SVC as AdminService
participant DB as MySQL
ADMIN->>API: DELETE /admin/coupons/{code}
API->>SVC: deleteCoupon(code)
SVC->>DB: couponRepository.findByIdOrThrow(code)
alt 이미 사용됨
SVC-->>ADMIN: AlreadyUsedCouponException (4xx)
else 미사용
SVC->>DB: couponRepository.delete(coupon)
SVC-->>ADMIN: 200 OK
end
PK 충돌 가능성: Coupon.createRandom()이 UUID hex 앞 5자리만 사용 — 16^5 ≈ 1M개 공간으로 birthday paradox 임계점(약 ~1200건)을 일반 마케팅 캠페인 수준에서 쉽게 넘김. couponRepository.saveAll()이 단일 트랜잭션이라 1건 PK 충돌 시 전체 rollback. 6-8자리 확장 또는 retry 로직 필요
만료일 부재: 발급된 쿠폰은 영구 유효. 마케팅 캠페인 종료 후 사용 차단 메커니즘 없음 — 별도 admin DELETE 일괄 호출 필요
할인율·금액쿠폰 미지원: 쿠폰은 1개 = 1개월 멤버십 발급. "10% 할인" 같은 결제 보조 쿠폰 도메인 없음
TOKEN_PACK 쿠폰 미테스트: Coupon.productType이 TOKEN_PACK일 때 Membership.byCoupon이 MembershipType.valueOf("TOKEN_PACK")을 시도하지만 MembershipType enum엔 TOKEN_PACK이 없음 → 런타임 IllegalArgumentException. 어드민 발급 폼에서 TOKEN_PACK 선택 가능하지만 사용 시 깨짐
사용자 응답이 enum 결과값 (OK/USED/INVALID): HTTP status는 200으로 통일 — 클라이언트가 body를 파싱해 분기해야 함. RESTful하게 4xx로 응답하는 편이 일관성 있음
어드민 발급 이벤트 로그 부재: 누가 언제 몇 개 발급했는지 별도 audit log 없음 (AdminAuditLogController는 다른 엔드포인트용)