파트너 구독 쿠폰 발행 및 적용 통합
ADR-280 파트너 구독 쿠폰 발행 및 적용 통합
1. Metadata
- ADR ID: ADR-280
- Status: draft
- Date: 2026-03-10
- Owner: YSY
- Related ADRs: ADR-220, ADR-250, ADR-270
2. Domain Decision
- 구독 쿠폰은
plan/add-on 카탈로그와 분리된 독립 엔터티로 관리한다. - 쿠폰 혜택은 순차 세그먼트(
order)로 구성하고 각 세그먼트는 plan_code, duration_months, addon_patch를 포함한다. - 쿠폰은 적용 시점에
entitlement_schedule로 고정되며 원본 쿠폰 템플릿 변경의 영향을 받지 않는다. - 쿠폰 적용 중 유효 권한은
coupon_entitlement가 기본 구독 권한보다 우선한다. - 쿠폰 스케줄이 종료되면 권한은 기본 구독(
plan + add-on)으로 자동 복귀한다. - 파트너당 동시 활성 쿠폰은 1개로 제한하고 추가 쿠폰은 대기열(
queued)로 보관한다.
3. Product Decision
- MVP 쿠폰 유형은
구독 기간 부여형(bundle)으로 시작한다. - 예시 번들 쿠폰:
basic 2개월 + plus 1개월. - 쿠폰 적용 단위는 월 주기(
billing_cycle=monthly)로 고정한다. - 쿠폰 번호는
6자리 혜택식별자-8자리 배치일자-2자리 일련번호 형식의 고정 길이 대문자 하이픈 문자열(xxxxxx-xxxxxxxx-xx)로 통일하며 예시는 PLUS1M-20260310-01이다. - QR로 배포하는 쿠폰도 별도 식별체계를 만들지 않고 동일한
coupon_code를 인코딩한다. - 쿠폰은 발급 조건(대상, 만료일, 최대 사용 수)을 명시해야 한다.
- 쿠폰 적용 중 파트너 청구 금액은 쿠폰 권한 범위에서 0원으로 처리한다.
- 쿠폰 종료 후에는 직전 기본 구독 과금이 다음 결제일부터 자동 재개된다.
4. UX Decision
- 쿠폰 적용 화면은 적용 전 미리보기 타임라인(월별 플랜 변동)을 반드시 제공한다.
- 쿠폰 적용 화면은 수동 입력과 QR 스캔 입력을 모두 제공하며, QR 스캔 성공 시 정규화된
coupon_code를 동일 입력창에 채워 즉시 미리보기를 조회한다. - QR 인식 실패 또는 지원하지 않는 형식이면 재시도와 수동 입력 전환을 즉시 제공한다.
- 적용 성공 화면은
적용 시작일, 세그먼트별 종료일, 기본 구독 복귀 예정일을 함께 표시한다. - 이미 활성 쿠폰이 있으면 즉시 적용 대신 대기열 등록 여부를 선택하게 한다.
- 쿠폰 코드 오류는 사유별 메시지(
만료, 대상 아님, 사용 완료, 지원하지 않는 QR 형식)로 분리한다.
5. Tech Decision
- 쿠폰 발행/적용 이벤트는
COUPON_ISSUED, COUPON_REDEEMED, COUPON_SEGMENT_STARTED, COUPON_ENDED로 분리 기록한다. - 쿠폰 적용 API는 멱등키(
idempotency_key)를 필수로 받고 중복 적용을 차단한다. - 권한 판정 함수는
coupon_entitlement_active 여부와 effective_plan_code를 함께 반환한다. - 쿠폰 세그먼트 전환은 배치/스케줄러가 아닌 결제 주기 경계 이벤트 기준으로 처리한다.
- 쿠폰과 기본 구독 간 상태 불일치가 감지되면 권한 판정은 보수적으로 차단(
can_issue=false)한다.
6. Ops Decision
- 쿠폰 템플릿 생성/수정/종료는 운영 승인 이력과 함께 관리한다.
- 쿠폰 악용 방지를 위해 코드 시도 횟수 제한과 비정상 사용 탐지 알람을 운영한다.
- 쿠폰 대기열 적체, 세그먼트 전환 실패, 과금 재개 실패를 일일 점검 항목으로 관리한다.
- 쿠폰 정책 변경 시 ADR-280, ADR-270, BUSINESS 문서를 동시에 갱신한다.
7. Implementation Contract (Optional)
7.1 API Contract
POST /partner/subscription/coupons/redeem는 coupon_code, idempotency_key를 필수로 받는다.- 성공 응답은
coupon_redemption_id, entitlement_timeline, resume_billing_at를 반환한다. - 활성 쿠폰 존재 시
apply_mode=queue|replace를 명시하지 않으면 COUPON_ACTIVE_ALREADY_EXISTS를 반환한다.
7.2 Data Contract
subscription_coupon_templates: coupon_code, segments(jsonb), expires_at, max_redemptions, eligibility_rule.partner_coupon_redemptions: partner_id, coupon_code, status(active|queued|ended|canceled), applied_at.partner_coupon_segments: redemption_id, segment_order, plan_code, addon_patch, starts_at, ends_at.billing_resumption_state: partner_id, resume_plan_code, resume_addon_codes, resume_at.
7.3 Error/Observability Contract
- 표준 코드:
COUPON_INVALID, COUPON_EXPIRED, COUPON_REDEMPTION_LIMIT_REACHED, COUPON_NOT_ELIGIBLE, COUPON_ACTIVE_ALREADY_EXISTS, COUPON_QUEUE_LIMIT_EXCEEDED. - 운영 지표: 쿠폰 적용 성공률, 코드 오류율, 세그먼트 전환 성공률, 과금 재개 성공률.
7.4 Test/Acceptance Contract
basic 2개월 + plus 1개월 쿠폰 적용 시 3개 세그먼트 타임라인이 정확히 생성되어야 한다.- 쿠폰 적용 기간 동안
effective_plan_code가 세그먼트 순서대로 전환되어야 한다. - 쿠폰 종료 후 기본 구독(
plan + add-on)과 과금이 자동 복귀해야 한다. - 동일
idempotency_key 재요청 시 동일 coupon_redemption_id가 반환되어야 한다.
8. Validation