쿠폰 정의 1건과 발급 쿠폰 1건의 생애를 각각 관리하며, 발급 쿠폰은 7개 상태를 단방향으로 오가고 결제에 적용된 이력이 있으면 자동 삭제되지 않는다.
개요
쿠폰 정의와 발급 쿠폰 두 층의 생애를 관리하는 도메인이다. 제품 관점의 쿠폰 유형과 사용 조건은 「쿠폰·구독권」이 다루며, 두 문서의 유형 4종과 할인 방식은 대조 결과 일치한다. 이 문서는 상태 기계, 발급 한도 검사, 자동 전이 배치, 영구 삭제 조건 같은 구현 규격을 담는다.
적용 범위
| 축 | 적용 / 제약 |
|---|---|
| 환경 | 미리캔버스 백엔드, 백오피스 |
| 테이블 | coupons · issued_coupons · payment_orders_issued_coupon_map |
| 요금제 | 분기가 없다. 이 도메인은 요금제별로 동작이 갈리지 않는다 |
| 통화 | 정액 할인은 결제 통화와 같아야 한다. 정률 할인은 통화에 종속되지 않는다 |
현재 상태 / 정의
도메인 경계
이 도메인이 책임지지 않는 것
- 결제 주문 자체. 결제에 쿠폰이 적용되면 매핑 테이블에 행이 추가되고, 주문 갱신과 환불 시 그 매핑을 결제주문 도메인이 옮기거나 지운다. 쿠폰 도메인은 매핑 행의 존재를 읽기 전용으로만 참조한다.
- 요금제 자체. 적용 가능 요금제와 결제 주기는 외래 enum 값으로 적되, 그 값의 의미와 전이는 요금제 도메인을 따른다.
- 계정과 워크스페이스의 상태 변화, 알림 채널 송신.
식별자
| 대상 | 내부 PK | 외부 노출 식별자 |
|---|---|---|
| 쿠폰 정의 | coupons.idx (외부 노출 금지) |
coupons.uuid 와 coupons.keyword(사용자가 입력해 등록하는 직접 입력 할인 코드) |
| 발급 쿠폰 | issued_coupons.idx (외부 노출 금지) |
issued_coupons.issued_code. 자동 생성 시리얼이며 형식은 ^MC[A-Z0-9]{10}$ 로 접두 MC 에 영대문자와 숫자 10자를 더한 12자다. 같은 정의로 발급된 쿠폰끼리 시리얼은 모두 다르다 |
상세 내용
1. 할인 유형과 금액 산식
DISCOUNT·PROMOTION·UNSUBSCRIPTION 계열에서만 의미를 갖는다.
| 값 | 의미 | 사용 컬럼 | 산식 |
|---|---|---|---|
RATE |
정률 할인 | discount_rate (0~100 백분율) |
원금 × (비율 / 100), 반올림 |
FIXED |
정액 할인 | discount_price |
min(원금, discount_price). 할인 금액이 원금을 넘으면 원금까지만 차감한다 |
통화 일관성
- 정액 할인은 할인 금액의 통화가 결제 통화와 같아야 한다. 다르면 프로모션 쿠폰은 경고 로그만 남기고 처리를 중단하고, 그 밖의 유형은 결제 자체가 거부된다.
- 정률 할인은 통화에 종속되지 않는다. 다만 해지 방어 키워드(
UNSUBSCRIPTION_PREVENT)는 KRW 9,900원 결과에 맞추기 위해 10원 단위 반올림 보정을 추가로 적용한다. 다른 정률 쿠폰은 통화의 일반 반올림 단위를 따른다.
구독권은 할인 금액으로 0을 돌려준다. 혜택 자체가 「구독을 0원에 부여」하는 것이라 결제가 발생하지 않고, 발급 쿠폰의 상태 흐름으로 무료 구독 기간을 표현한다.
2. 쿠폰 제약
| 컬럼 | 의미 |
|---|---|
max_issue_count |
최대 발급 가능 횟수이며 NULL 이면 무제한이다 |
issue_count |
누적 발급 횟수. 동시 발급 상황에서는 별도의 정합성 보강이 필요하다 |
plan_list |
적용 가능한 요금제 배열. 결제 대상 요금제가 이 배열에 있어야 적용된다 |
billing_cycle_list |
적용 가능한 결제 주기 배열 |
payment_method_required |
true 면 결제 수단이 등록된 상태에서만 쓸 수 있다 |
discount_seat_count |
할인이 적용되는 시트 수 상한이다. 5 면 최대 5시트까지만 할인한다 |
seat_count_fixed |
true 면 구독권 적용 후 멤버 추가가 차단되어 시트 수가 고정된다 |
use_period_unit · use_period_value |
사용 가능 기간이며 DAYS·MONTHS·YEARS 단위에 정수를 곱한다 |
opened_at_tz · expired_at_tz |
노출 시작과 만료 시각이며, 그 사이에만 「열려 있다」고 본다 |
발급 한도 검사
다음 세 조건을 모두 만족해야 발급할 수 있다.
- 쿠폰 정의 상태가
ACTIVE다. - 발급 한도에 도달하지 않았다. 무제한이거나 누적 발급 횟수가 최대치보다 작다.
- 만료되지 않았다.
일괄 발급의 사전 검증은 세 조건을 직접 확인한다. 잔여 수가 요청 수보다 적은 경우를 사전에 차단하며, 하나라도 실패하면 발급 자체가 시작되지 않는다. 한 건씩 발급할 때마다 발급 횟수를 올리면서 조건을 다시 검증하고, 위반하면 트랜잭션을 롤백한다.
사용 기간 산식
종료일 = 사용 시작일 + 기간 값 − 1일
1일을 빼는 것은 활성 구독권과 예약 구독권 사이에 종료와 시작이 겹치지 않도록 두는 안전 마진이다. 역연산(사용 시작일 = 종료일 − 기간 값 + 1일)은 다음 구독권 예약 시점을 복원할 때 쓴다.
이 산식은 쿠폰 정의 시점에 잠긴다. 발급 후에 정의가 바뀌어도 이미 발급된 인스턴스의 종료일은 계산식이 함께 잠긴다.
3. 발급 쿠폰 상태 7종
| 값 | 의미 |
|---|---|
UNREGISTERED |
워크스페이스에 아직 등록하지 않은 상태이며 발급 직후의 기본값이다 |
REGISTERED |
시리얼 번호를 입력해 워크스페이스나 계정에 등록한 상태다 |
WAITING_FOR_USE |
사용 대기 등록 상태. 현재 활성 플랜의 종료 시점에 자동으로 활성된다 |
USING |
현재 할인이나 구독 혜택이 적용 중이다 |
USED |
사용을 완료해 혜택이 끝났다 |
EXPIRED |
사용하지 않았지만 만료일이 지났다 |
INACTIVE |
운영이나 시스템 차원에서 비활성화한 상태이며, 쿠폰 정의를 비활성화하면 일괄 전이된다 |
정의되지 않은 전이는 거부된다. 주요 전이는 다음과 같다.
- 발급 →
UNREGISTERED→ 등록 →REGISTERED→ 사용 →USING→ 종료 →USED REGISTERED에서 사용 대기로 등록하면WAITING_FOR_USE가 되고, 다음 주기가 오면USING으로 전환된다.USING에서 사용을 취소하면REGISTERED로 되돌아간다.UNREGISTERED와REGISTERED는 만료 시각이 지나면EXPIRED가 되고, 정의 비활성화 시INACTIVE가 된다. 둘 다 종착 상태다.- 휘발성 쿠폰의 사용 대기를 취소하면 종료 시각을 현재로 두고
USED가 된다.
4. 자동 전이 배치
| 배치 | 주기 | 동작 |
|---|---|---|
| 구독 갱신 배치 | 매일 00:00 | 만료·완료·구독 쿠폰 갱신·다운그레이드를 순서대로 처리한다 |
| 쿠폰 정리 배치 | 매일 02:00 | 청크 1000건으로 영구 삭제를 수행하며 운영 토글로 켜고 끌 수 있다 |
| 프로모션 종료 임박 메일 | 매일 08:05, production 한정 | 프로모션 종료 임박 메일을 발송한다 |
만료 처리: UNREGISTERED 나 REGISTERED 이면서 만료 시각이 지난 행을 EXPIRED 로 바꾼다. 시각 컬럼은 그대로 보존한다.
사용 대기 → 사용 중 전환: 구독권 쿠폰이 자동으로 USING 에 진입하려면 세 조건을 모두 만족해야 한다.
- 워크스페이스에 현재 활성 플랜이 있다.
- 결제 실패 카운터가 모두 0 이다.
- 현재 플랜의 종료일이 지났다.
사용 중 → 사용 완료: 구독권과 할인 쿠폰 모두 종료일이 지나면 USED 로 전이한다. 프로모션 쿠폰 중 종료 한 달 전 구간에 들어선 행은 종료 임박 알림 대상으로 따로 분리하며, 실제 전이는 자동 배치가, 알림은 별도 배치가 담당한다.
영구 삭제 조건
아래를 모두 만족하는 발급 쿠폰은 자동으로 영구 삭제된다.
- 상태가
EXPIRED이면서 만료 시각이 한 달을 넘었거나,INACTIVE이면서 비활성 시각이 한 달을 넘었다. - 결제 매핑 테이블에 해당 발급 쿠폰의 행이 없다.
결제에 적용된 이력이 있는 발급 쿠폰은 절대 자동 삭제되지 않는다. 환불과 영수증 추적을 위해 결제 매핑이 살아 있는 한 보호된다.
5. 적용 거부 사유
| 사유 | HTTP |
|---|---|
| 쿠폰 정의가 비활성화 상태다 | 409 |
| 쿠폰 정의가 만료되었다 | 400 |
| 발급 한도를 초과했거나 잔여 수가 요청 수보다 적다 | 400 |
| 쿠폰을 찾을 수 없다(잘못된 UUID) | 404 |
| 발급 쿠폰을 찾을 수 없다(잘못된 시리얼) | 404 |
키워드 포맷·길이·MC 접두가 잘못되었다 |
400 |
| 키워드가 중복된다 | 409 |
| 발급 쿠폰의 상태 전이가 불가능하다 | 400 |
변경 이력
| 날짜 | 변경 내용 | 관련 문서 |
|---|---|---|
| (확인 필요) | 발행 후 스펙 변경이 아직 기록되지 않았다 | — |
확인 필요
| 항목 | 사유 | 확인 방법 |
|---|---|---|
| 동시 발급의 정합성 | 원문이 「동시 발급 상황에서는 별도 정합성 보강이 필요하다」고 적고 방법을 밝히지 않는다. 발급 한도를 넘겨 발급될 수 있다 | 발급 횟수 증가에 잠금이나 원자적 갱신이 걸려 있는지 확인한다 |
UNSUBSCRIPTION_PREVENT 의 10원 보정 |
KRW 9,900원이라는 특정 결과에 맞춘 보정이 산식에 들어가 있다. 가격이 바뀌면 보정도 함께 바꿔야 한다 | 요금제 가격 변경 시 이 보정을 점검하는 절차가 있는지 확인한다 |
| 쿠폰 정리 배치 토글의 현재 상태 | 운영 토글로 켜고 끌 수 있어 꺼져 있으면 만료 쿠폰이 계속 쌓인다 | 현재 토글 값과 누적 행 수를 확인한다 |
| 제품 문서와의 상태 표기 차이 | 「쿠폰·구독권」 문서가 상태 7종을 같은 이름으로 쓰는지 대조하지 않았다 | 두 문서의 상태 목록을 대조한다 |
관련 문서
| 대상 | 경로 |
|---|---|
| 쿠폰·구독권 | product-service/miricanvas-billing/coupons-subscriptions.md |
| 요금제 백엔드 정책 | technology-data/payment-backend/plan-policy.md |
| 결제주문 백엔드 정책 | technology-data/payment-backend/payment-order-policy.md |
| 캠페인 백엔드 정책 | technology-data/payment-backend/campaign-policy.md |
| 요금제 | 분기가 없다. 이 도메인은 요금제별로 동작이 갈리지 않는다 |