Screen: Paywall (PRO / MAX)
구독 2티어(PRO/MAX) 선택·결제 — App 전용 하단 시트(showAppBottomSheet, 아래에서 위로). 풀페이지가 아니라 진입 맥락(공시 상세·관심종목 등) 위에 시트로 떠서, 구독 후 닫으면 하던 작업으로 복귀. 구매는 로그인 필수(엔타이틀먼트가 userID 에 귀속 — 비로그인 구매 플로우 없음). 도메인 룰: domain-subscription, API: api/subscription.
💡 시각 미리보기: design site / paywall-app
플랫폼별 차이
| 항목 | App (Flutter) | Web (Next.js) |
|---|---|---|
| 화면 존재 | ✅ | ❌ (웹 구매 플로우 없음 — 스토어 결제 기반) |
| 제시 | 하단 시트 showAppBottomSheet (dim 배경 + grabber, 스와이프/배경 탭 dismiss) | — |
진입점
세 곳 모두 같은 시트를 띄운다(단일 전환 surface). source 로 어느 맥락이 전환을 만드는지 측정.
| 진입점 | 트리거 | source 파라미터 |
|---|---|---|
| 더보기 "PRO 구독" 메뉴 | 메뉴 탭 | settings |
| 주간 분석 한도 소진 | 분석 시도/인디케이터의 "구독하고 더 보기" 버튼 (domain-quota / quota-indicator) | quota_soldout |
| 관심종목 상한 초과 추가 시도 | capacity 초과 add 시 토스트/모달 대신 이 시트 노출 (domain-watchlist 409) | watchlist_full |
진입 맥락에 맞춰 기본 선택 티어를 제안할 수 있음(예: 관심종목 초과 시 MAX 강조). v1 은 기본 PRO 선택.
비로그인 진입 시 CTA 탭 →
requireAuth(AuthGateAction.subscribe)게이트 → 로그인 후 결제 진행.
시트 구성 (위→아래)
| 영역 | 내용 | 조건 |
|---|---|---|
| grabber | 상단 핸들(드래그/스와이프 dismiss) | 항상 |
| Hero | "구독" 타이틀 + 한 줄 가치 "새 공시는 알림으로, 분석은 더 넉넉하게" (아이콘 없음) | 항상 |
| 플랜 선택 | PRO / MAX 2개 카드 (세그먼트/카드 토글). 선택 카드에 가격·한도 강조 | S1 |
| 혜택 비교 | 선택 플랜의 한도 2행 (분석/종목, 아래) | 항상 |
| Primary CTA | AppButton.primary 풀너비 | S1="{플랜} 구독" / S2(PRO)="MAX 로 업그레이드"(+보조 "구독 관리") / S2(MAX)="구독 관리" |
| 푸터 | 자동갱신 고지 문구 + 이용약관 · 개인정보처리방침 링크 (심사 필수) | 항상 |
구매 복원은 페이월이 아니라 더보기 > 구독 그룹에 배치 — 사용 빈도 낮고 verify-on-launch 가 평소 자동 동기화하므로 페이월 전환 동선에서 제외. Apple 필수 메커니즘은 설정에서 충족.
플랜 카드 (S1)
| 플랜 | 가격/월 | 주간 분석 | 공시 알림 |
|---|---|---|---|
| PRO | ₩1,000 | 60 | 30종목 |
| MAX | ₩2,000 | 200 | 100종목 |
- 선택된 카드만 강조(브랜드 보더/배경), CTA 라벨이 선택 플랜 반영("PRO 구독" / "MAX 구독").
- 혜택 행 아이콘:
sparkle(분석),bell(공시 알림). 관심종목 혜택은 "몇 개 담는다"가 아니라 "고른 종목의 새 공시를 알림으로 받는다" 로 강조 — 라벨 "공시 알림 N종목".
상태 분기
- S1. 비구독 (
tier=none) — PRO/MAX 카드 선택 + 선택 플랜으로 "구독" CTA. 탭 → 스토어 결제 →POST /api/v1/subscription/verify. - S2. 구독 중 (
tier=pro|max) — Hero 하단 "{PRO|MAX} 이용 중 · {premium_until} 까지".- PRO: Primary CTA "MAX 로 업그레이드"(탭 → MAX 구매로 업그레이드 플로우, 스토어 proration) + 보조 텍스트 "구독 관리"(스토어 deep link). MAX 혜택 안내 동반.
- MAX: Primary CTA "구독 관리"만(이미 최상위).
- S3. 처리 중 — 구매/복원 진행 동안 CTA 로딩(disabled). 완료 후 verify 응답(
tier)으로 갱신.
액션 (API)
| 액션 | 동작 |
|---|---|
| 구독 시작 | in_app_purchase 결제 → 거래 토큰 → POST /api/v1/subscription/verify → 활성이면 닫고 진입 화면 갱신 |
| 구독 관리 | 스토어 구독 관리 화면 deep link (앱 내 해지 불가 — 스토어 정책) |
구매 복원 액션은 더보기 > 구독 그룹에서 수행(스토어 복원 →
verify재호출).
상세 요청/응답: api/subscription (POST /api/v1/subscription/verify).
Analytics
domain-analytics 구독/페이월 이벤트:
paywall_view(source) — 페이월 진입subscribe_attempt(plan: pro|max) — CTA 탭subscribe_result(plan,outcome: success|cancelled|error) — 결제+verify 종결
restore_attempt/restore_result는 더보기의 구매 복원에서 송출.
Edge cases
- 결제 취소:
subscribe_result outcome=cancelled, 화면 유지(S1). - verify 실패(위조/미존재): 토스트 "구매를 확인하지 못했습니다. 잠시 후 다시 시도해주세요." (
ToastType.error), 권한 부여 안 함. - 이미 구독 중 페이월 진입: S2 로 노출(중복 결제 방지). PRO 면 MAX 업그레이드 제시, MAX 면 관리만.
심사 요구
- 페이월에 가격 · 기간 · "자동갱신" 명시 + 이용약관/개인정보 링크.
- "구매 복원" 메커니즘 필수 — 더보기 > 구독 그룹에 배치(페이월 아님, Apple 은 위치 무관 존재만 요구).
관련 docs
- flow-subscription — 구매/복원/업그레이드 시퀀스 (StoreKit↔verify↔finish)
- domain-subscription — 엔타이틀먼트 / 검증 / 다운그레이드 룰
- api/subscription —
POST /api/v1/subscription/verify - screen-settings — "PRO 구독" 진입 메뉴
- button — CTA / 복원 버튼