도메인: Subscription (PRO / MAX)
개요
자동갱신 구독 2티어 — PRO(₩1,000)·MAX(₩2,000). 결제 시 주간 AI 분석 한도와 관심종목 슬롯 상한이 티어별로 올라간다. 권한 판정은 서버가 한다(영수증/거래 검증). iOS · Android 동시 제공.
| 항목 | PRO | MAX |
|---|---|---|
| 상품명(앱 내 표시) | PRO | MAX |
| 가격 | ₩1,000 / 월 | ₩2,000 / 월 |
| product ID | dartbrief.pro.monthly | dartbrief.max.monthly |
- 과금: 둘 다 자동갱신 구독(auto-renewable). 무료 체험 없음(v1, 추후 intro offer 토글). 플랫폼 iOS(App Store)+Android(Play). product ID 는 iOS/Android 공통 네이밍.
티어별 한도
| 무료 | PRO (₩1,000) | MAX (₩2,000) | |
|---|---|---|---|
| 주간 AI 분석(reveal) | 10회/주 (+광고 보상 +1) | 60회/주 | 200회/주 |
| 관심종목 슬롯 | 3개 | 30개 | 100개 |
| 광고 | 한도 소진 시 +1 | (paid — 한도 도달 시 상위 티어/리셋 안내) | 동일 |
한도는 domain-quota 의 주간 데이터(종목당 ~1.3건/주)로 책정 — 평상시 거의 안 걸리고 결산 피크/헤비유저에서 도달해 상위 티어 유도 + 어뷰즈 천장. reveal 원가≈0(캐시 조회)이라 한도는 비용 통제가 아니라 펀넬·어뷰즈 목적(policies/monetization).
엔타이틀먼트 모델
권한의 단일 진실 = 서버. 클라이언트 판정은 신뢰하지 않는다(reveal result 는 백엔드가 게이트).
dartbrief.users 컬럼(또는 동등 저장):
| 컬럼 | 설명 |
|---|---|
premium_tier | none / pro / max — 티어 결정 컬럼. 한도·상한이 이 값으로 갈림 |
premium_until | TIMESTAMPTZ NULL — 구독 만료 시각. 활성 = premium_until > now() |
premium_platform | ios / android |
premium_product_id | dartbrief.pro.monthly / dartbrief.max.monthly |
premium_tx_ref | App Store originalTransactionId / Play purchaseToken — 재검증 키 |
activeTier(userID)=premium_until > now()이면premium_tier, 아니면none. 모든 게이트가 이 헬퍼로 한도를 정한다.- Grace period(결제 재시도 유예)도 활성으로 취급 — 스토어가 주는 만료시각에 grace 가 포함됨(
premium_until갱신). - 구독은 로그인 필요(엔타이틀먼트가 userID 에 귀속). 비로그인 구매 플로우 없음.
권한 적용 (티어별 한도)
게이트는 우회가 아니라 티어에 따라 한도 값만 바뀐다 (무료도 paid 도 같은 체크, 상수만 다름).
| 게이트 | 무료 | PRO | MAX |
|---|---|---|---|
| AI 분석 reveal — 주간 quota (domain-quota) | WeeklyLimit=10 | 60 | 200 |
| 관심종목 add — capacity (domain-watchlist) | 3 | 30 | 100 |
weeklyLimit(tier) / slotCapacity(tier) 단일 매핑으로 결정. used_count 는 티어 무관 계속 기록.
검증 — 웹훅 + verify-on-launch (둘 다, v1)
스토어 서버 API 가 구독 활성의 진실. 백엔드는 premium_until 로 캐시하고, (1) 웹훅으로 즉시 반영 + (2) 구매/앱실행 시 verify 로 보정 한다.
앱(in_app_purchase) ──구매/복원──▶ 스토어 결제
└ 거래 토큰 → POST /api/v1/subscription/verify
백엔드 ──검증──▶ App Store Server API / Play Developer API (Get Subscription Status)
→ 활성/만료시각 파싱 → premium_until 갱신 → 상태 반환
앱 실행 시 1회 verify refresh → 만료/환불 보정
[웹훅] 스토어 라이프사이클(갱신/만료/환불) → 백엔드 즉시 갱신
- Apple: ASSN V2 가 POST /internal/appstore-notifications (JWS 서명 검증)
- Google: Play RTDN → GCP Pub/Sub 토픽 → push 구독 → POST /internal/play-rtdn- 웹훅(즉시 반영, v1 포함): 환불/갱신/만료를 스토어가 push →
premium_until즉시 갱신. Apple 은 URL 직접 POST, Google 은 Pub/Sub push 구독(기존 public 백엔드 엔드포인트로 라우팅 — 어렵지 않음). - verify-on-launch(보정): 웹훅 누락/지연 대비. 앱 실행 시 재검증으로 상태 동기화.
- 검증/키: App Store Server API(.p8) + ASSN V2 서명, Play Developer API(서비스 계정) + RTDN. GCP 토픽/권한·Pub/Sub push 구독 설정은 사용자 수동. 시크릿은
~/.config/cloud-tokens/. - 거래 소유권 바인딩(어뷰즈 방지): 스토어 검증은 "거래가 유효함"만 보장하지 "이 사용자 것"은 보장하지 않는다. 타 계정의 유효한 거래 토큰 재사용으로 권한 탈취를 막기 위해, verify 는 거래를 요청 사용자에 귀속시켜야 한다 —
appAccountToken(Apple)/obfuscatedExternalAccountId(Google) 대조 또는premium_tx_ref최초 소유자 고정(다른 userID 재사용 거부). 실제 verifier 주입(Phase 2) 시 enforce — 골격 단계의StoreVerifier.Verify는 이미userID를 인자로 받는다.
다운그레이드 (티어 하락 — 만료/환불/MAX→PRO)
티어가 내려가면(만료→none, 환불, MAX→PRO 갱신) 관심종목 상한이 새 티어 값(none=3 / pro=30)으로 내려간다. 현재 등록이 새 상한 초과면:
- 즉시 — 초과분은 열람·알림 유지, 단 등록 수가 새 상한 미만 될 때까지 새 종목 add 차단. 하락 후 첫 접속 시 alert: "구독이 변경되어 관심종목을 N개로 정리해야 해요" (사용자가 직접 정리 유도).
- 하락 7일 후에도 초과면 — 서버가 자동 정리: 가장 최근 등록 N개만 남기고 오래된 것부터 제거 (V6 "가장 오래된 종목 자동 제거" 규칙과 일치). 정리 잡이
(만료/하락 시각)+7d경과 + 초과 조건으로 실행.
reveal 주간 한도는 다음 주 reset 부터 새 티어 한도 적용(소급 차감 없음). domain-watchlist 의 capacity 체크 + 자동정리가 이 규칙을 적용. 자동 삭제 전(7일 내)엔 데이터 보존.
구매/복원/환불 플로우 요약
| 플로우 | 동작 |
|---|---|
| 구매 | 페이월에서 PRO/MAX 선택 → 스토어 결제 → verify → premium_tier+premium_until set |
| 업/다운그레이드 | 페이월에서 다른 티어 선택 → 스토어가 proration 처리 → verify 가 premium_tier 갱신 |
| 복원(Restore) | 더보기 > 구독의 "구매 복원"(Apple 필수) → verify 재호출 → 활성이면 티어/만료 복원 |
| 갱신 | 다음 주기 자동결제 → verify refresh 에서 만료시각 연장 |
| 만료/취소 | verify refresh 에서 expired → premium_until 과거 → none 으로 강등(다운그레이드 규칙) |
| 환불 | verify refresh 에서 refunded → 엔타이틀먼트 회수 |
심사 요구 (구독)
- 페이월에 가격·기간·"자동갱신" 명시 + 이용약관/개인정보 링크.
- "구매 복원" 메커니즘 필수 — 더보기 > 구독에 배치(페이월 아님; Apple 은 존재만 요구).
- 첫 출시 시 구독 상품을 앱 버전과 함께 심사 제출.
관련 문서
- API: api/subscription.md (
POST /api/v1/subscription/verify) - domain-quota (티어별 주간 한도 10/60/200)
- domain-watchlist (티어별 capacity 3/30/100 + 다운그레이드 규칙)
- 정책: policies/monetization (PRO/MAX 2티어 = 유일 IAP, 가격·한도 책정 근거)
- 플로우: flow-subscription (구매/복원/업그레이드 시퀀스 + 트랜잭션 finish)
- 화면: screen-paywall (구독 결제 화면 — design 카탈로그)
- domain-analytics (구독/페이월 이벤트 +
auth_gatesubscribe)