22 KiB
22 KiB
PRD: 본인인증 후 콘텐츠 보기 설정
문서 정보
| 항목 | 내용 |
|---|---|
| 문서 상태 | 코드 구현·빌드 검증 완료 / 실제 인증 통합 QA 대기 |
| 작성일 | 2026-10-12 |
| 최종 수정일 | 2026-10-12 |
| 대상 제품 | SodaLive iOS 본인인증 완료 흐름 |
| 작성자·결정권자 | 사용자 승인 완료 (2026-10-12) |
| API Contract | 본 문서 §11 |
| 구현 계획 | docs/20261012_본인인증_후_콘텐츠_보기_설정/plan-task.md |
| 구현 승인 | 2026-10-12 사용자 “승인”으로 구현 허가. 현재 main 브랜치, worktree 미사용. |
요구사항 상태
| 상태 | 의미 | 구현 처리 |
|---|---|---|
| 사용자 확정 | 사용자에게 직접 확인된 제품 흐름 | 별도 문서 승인 후 계획 Task의 기준으로 사용 |
| 채택 관례 | 기존 코드 동작을 기본값으로 채택 | 명시된 한정 내 적용, 사용자 결정으로 표기하지 않음 |
| 구현 근거 | 저장소 소스에서 확인한 현재 동작 | 설계 근거이며 런타임 성공 증거는 아님 |
| 위험/검증 | 추가 제품 결정을 요구하지 않는 미검증 런타임 조건 | 구현 후 실제 앱 QA로 판정 |
1. Overview
Bootpay 본인인증 완료 직후 인증 요청을 보내는 현재 흐름을, 사용자가 콘텐츠 보기 설정을 확인한 뒤 제출하는 흐름으로 바꾼다. 모든 인증 진입점에서 동일한 확인 sheet와 인증·설정 제출·회원정보 동기화 순서를 사용하며, 사용자의 선호가 인증 성별 값에 의해 덮어써지지 않게 한다.
2. Problem Statement
- 현재
MyPageViewModel.authVerify는 인증 완료 직후POST /auth를 보내고, 응답 gender로 콘텐츠 선호를 저장한다. - callback 진입점에서는 성공 callback으로 이어지는 동작이 있지만,
MyPage단독 진입은 앱 재시작 경로를 사용한다. - 기존
ContentSettingsViewModel은 설정 변경 즉시 로컬 저장과 debounce PATCH를 수행하고 재시작 상태도 설정한다. 미확정 draft에 재사용할 수 없다. AppViewModel및HomeViewModel의 회원정보 조회는 완료 결과를 반환하지 않고 알림·분석 부수효과도 수행한다. 요청 직후 pending action을 실행하면 동기화 순서가 보장되지 않는다.
3. Goals
3.1 제품 목표
- 인증 완료 7개 진입점 모두에서 사용자가 설정을 확인하기 전까지
POST /auth를 보내지 않는다. - 사용자가 확인한 인증 영수증과 성인 콘텐츠 설정을 한 요청으로 제출한다.
- callback 진입점은 인증 성공 후 회원정보를 받아 로컬 인증·콘텐츠 상태를 갱신한 뒤 기존 pending action을 한 번만 실행한다. 마이페이지는 기존 재시작 경로를 유지한다.
3.2 UX 목표
- 인증 직후 ON 상태와 전체 콘텐츠를 기본 선택한 설정 sheet를 표시한다.
- 바깥 탭, drag, 뒤로/닫기로 확인 단계를 우회해 dismiss하지 못하게 한다.
- 실패한 단계별 복구를 구분하고 재요청 또는 action 실행을 잘못 반복하지 않는다.
4. Non-Goals
- 독립 콘텐츠 설정 화면의 기존 자동 저장/debounce PATCH 동작 변경.
- 일반 회원정보 조회 helper 전체의 추상화·리팩터링.
- Bootpay 자체 동작, 인증 서버 response schema, 외부 인증 계약의 변경.
- 승인 전 앱 코드, UI, 스크립트 또는 프로젝트 설정 변경.
- 재시도 idempotency 또는 영수증 만료 정책 보장.
5. Target Users and Permissions
- 사용자: Bootpay 본인인증을 완료한 로그인 사용자.
- 인증 receipt와 앱 token은 외부 입력/민감정보로 취급한다. 원문을 로그나 문서에 남기지 않는다.
6. 핵심 사용자 흐름
- 사용자 또는 기존 기능이 Bootpay 본인인증을 시작한다.
- Bootpay
onDone에서 receipt를 보존하고 Bootpay full-screen cover를 닫는다. - cover dismiss가 완료된 뒤 사용자 확인을 위한 콘텐츠 설정 sheet를 연다. callback과 pending action도 이 전환 중 보존한다.
- sheet에서 ON/OFF와 ON일 때 선호를 선택한다. 이 단계에서는 네트워크 요청과 설정 영속화를 하지 않는다.
- 사용자가
설정 완료를 누르면 sheet를 먼저 닫고, 이어POST /auth를 한 번 보낸다. - 인증·설정 제출 성공 후 마이페이지는 확인한 콘텐츠 설정을 로컬에 반영하고 기존 앱 재시작 경로로 이동한다. callback 진입점만
UserRepository.getMemberInfo()응답을 기다려 기존 member response를 decode하고 로컬 상태를 갱신한다. - callback 진입점은 동기화 성공 뒤 보존된 성공 callback으로 pending action을 한 번 소비한다. 동기화 실패 시 callback을 실행하지 않고 마이페이지와 동일한 앱 재시작 경로로 이동한다.
7. 정보 구조와 라우팅
- 앱 route를 추가하지 않는다. 인증 callback 상태를 보존하는 화면 위 modal sheet로 제공한다.
- 시작 전에 다른 full-screen cover가 열려 있으면 이를 먼저 dismiss 완료한 뒤 sheet를 표시한다.
- sheet는 바깥 탭, swipe/drag, back 또는 close 제스처로 닫히지 않는다. 명시적 확인만 다음 단계로 진행한다.
8. 기능 요구사항
8.1 진입점과 확인 순서
| ID | 상태 | 요구사항 | 수용 기준 |
|---|---|---|---|
| AUTH-001 | 사용자 확정 | 모든 7개 Bootpay 인증 완료 진입점은 확인 전 auth POST를 미룬다. | MyPageView.swift, Main/Home/HomeView.swift, Home/HomeTabView.swift, Chat/ChatTabView.swift, Live/Now/All/LiveNowAllView.swift, V2/Main/MainView.swift, V2/CreatorChannel/CreatorChannelView.swift에서 같은 순서를 확인한다. 확인 전 request·설정 저장 모두 0회다. |
| AUTH-002 | 사용자 확정 | receipt, draft, action/callback을 cover-dismiss-sheet 표시 순서 동안 보존한다. | onDone이 sheet 또는 callback을 잃지 않으며 cover dismiss 완료 전에 sheet가 겹쳐 뜨지 않는다. onClose 기존 닫기 동작을 유지한다. |
| AUTH-003 | 사용자 확정 | sheet는 확인 외 경로로 dismiss되지 않는다. | 배경 탭, drag, back 및 close 시트 dismiss 0회, 요청 0회다. |
| AUTH-004 | 사용자 확정 | 초기 draft는 isAdultContentVisible=true, contentType=ALL이다. |
초기 렌더 상태와 확인 request가 이를 따른다. |
| AUTH-005 | 채택 관례 | OFF 선택 시 contentType=ALL로 normalize한다. |
비활성 선호 값은 제출되지 않는다. ALL normalize는 기존 코드의 채택 관례이며 별도 사용자 결정은 아니다. |
8.2 제출, 동기화, 실패
| ID | 상태 | 요구사항 | 수용 기준 |
|---|---|---|---|
| AUTH-006 | 사용자 확정 | 확인 시 sheet를 닫은 뒤 combined JSON payload로 POST /auth한다. |
§11 필드와 enum만 포함하고 중복 탭으로 동시 요청이 발생하지 않는다. |
| AUTH-007 | 사용자 확정 | 인증 및 설정 제출 실패 시 오류를 보이고 같은 draft로 sheet를 다시 연다. | receipt와 pending action을 보존하고 사용자가 다시 누를 때만 재전송한다. 자동 제출은 없다. |
| AUTH-008 | 사용자 확정 | 인증 성공 후 회원정보 동기화 실패 시 인증을 재전송하지 않는다. | sheet를 다시 열지 않고 pending action도 실행하지 않는다. My Page와 동일하게 isRestartApp=true, setAppStep(.splash)로 앱 재시작 경로에 보낸다. sync-only retry UI/정책은 추가하지 않는다. |
| AUTH-009 | 사용자 확정 | 여섯 callback route는 auth 성공 후 GET /member/info?container=ios와 로컬 상태 갱신을 완료하고 callback action을 한 번 소비한다. |
동기화보다 callback이 앞서지 않는다. 일반 fire-and-forget member helper를 부른 직후 진행하지 않는다. |
| AUTH-010 | 사용자 확정 | 기존 My Page 재시작 route를 유지하고 auth 응답 gender가 사용자의 content choice를 다시 덮어쓰지 않는다. | gender 기반 선호 저장 제거, callback 없음은 기존 재시작 흐름을 유지한다. |
| AUTH-011 | 구현 근거 | shared auth flow에서 기존 UserRepository.getMemberInfo() 및 GetMemberInfoResponse를 재사용한다. |
성공 response의 기존 필드와 로컬 키를 사용하며 별도 response schema를 만들지 않는다. |
9. 반응형 기능 범위
- iPhone SwiftUI sheet만 포함한다. 웹/tablet 반응형 범위는 해당 없음.
- bottom safe area 포함 Figma frame: ON 402x372, OFF 402x287.
10. UI/UX Expectations
10.0 표시 방식 변경 승인 (2026-10-12)
- 사용자가 투명
fullScreenCover를 표시 컨테이너로 사용하는 커스텀 BottomSheet 전환을 승인했다. 아래 내용은 이전 native.sheet/고정 높이 기록보다 우선한다. - 화면 전체에는 기존 UI가 비치는 반투명 검정 배경을 두고 터치를 차단한다. 실제 패널은 좌우·하단에 붙으며 위쪽 모서리만 둥글고, 내용과 safe area에 필요한 높이만 차지한다.
- ON/OFF
338/253고정 높이 및 접근성 크기에서.large로 전환하는 규칙을 제거한다. 텍스트 줄바꿈과 선호 영역 유무에 따른 자연 높이를 사용한다. - 내용이 화면의 사용 가능한 높이를 초과할 때에만 본문을 스크롤하고 확인 버튼은 접근 가능하게 유지한다.
- 확인으로 컨테이너가 닫힌 뒤 기존
onDismiss전송을 실행한다. API·ViewModel·7개 진입점·오류 복구 정책은 변경하지 않는다. - 구현·검증 후속: 커스텀 패널 전환 완료. iOS26 실제 측정은 한국어 ON372pt/OFF287pt, 영어 ON383.67pt이며 고정값이 아닌 내용 높이다. 화면 초과 시 본문 스크롤, 뒤 화면 노출/입력 차단, 두 앱 빌드와 P4 독립 검토를 통과했다. 기존 실제 인증 통합 미검증은 유지한다.
- Phase6 승인: 사용자가 배경의 올라오는 애니메이션 제거 방안을 확인하고 적용을 승인했다. fullScreenCover 기본 전환은 끄고 dim 배경은 제자리에 즉시 표시하며, 콘텐츠 패널에만 진입/퇴장 모션을 적용한다.
- 패널 퇴장 완료 후 컨테이너를 닫고 기존 onDismiss 제출을 유지한다. ViewModel/API/개발용 mock 정책은 바꾸지 않는다. 동작 줄이기 환경에서는 수직 이동을 줄이고, 같은 닫힘 완료/1회 제출 계약을 유지한다.
- 2026-10-13 Phase6 후속: 배경 고정·패널 단독 0.25초 진입/퇴장을 구현했다. 실제 native 중간 프레임, 빠른 확인/재표시/Reduce Motion 검사, 두 앱 빌드와 독립 기능·시각 검토가 통과했다. 제출은 host onDismiss에 유지된다.
10.1 시안과 문구
- Figma file
HmN1yNdJ3EIpqknFL0Hkab, ON2489:32150, OFF2489:32163을 기준으로 한다. - 시안 링크: ON, OFF.
- 고정 dark background
#202020, accent#00bdf7, 흰색 제목, muted copy#b5b5b5, 좌우 padding 14pt. - 제목 18pt medium, 본문 16pt, 안내 caption 12pt. 기존 앱 font/assets와
I18n.swift규칙을 따른다. - 화면 문구는 다음과 같이 고정한다.
콘텐츠 보기 설정민감한 콘텐츠 보기- ON 설명:
성인인증 완료 후 성인 콘텐츠를 볼 수 있어요 - OFF 설명:
성인 콘텐츠가 목록과 검색에 노출되지 않아요 - ON일 때만
선호 콘텐츠및전체/남성향/여성향radio row 표시 마이 > 설정 > 콘텐츠 보기 설정에서 언제든 바꿀 수 있어요- 확인 버튼
설정 완료
10.2 화면 상태
- sheet는 제출 전 draft만 보유한다. 확인 전 기존 설정 값을 변경하거나 PATCH하지 않는다.
- auth+settings POST 실패는 sheet 재개, member info sync 실패는 sheet 재개 없이 restart fallback으로 분리한다.
11. API 계약
11.0 개발 타깃 성공 시뮬레이션 승인 (Phase 5)
- 사용자가 앱 흐름 테스트 목적의 모의 인증을 승인했다. 실제 서버 인증 검사 API가 거절될 수 있다는 한계를 수용했다.
SodaLive-devDebug/Release에만AUTH_VERIFICATION_MOCK컴파일 조건을 적용한다. 일반SodaLive는 두 구성 모두 실제 Bootpay/인증 API를 유지하며 모의 실행 코드가 제외된다.- 7개 본인인증 UI에서 개발 모드는 Bootpay를 열지 않고 약 0.5초 로딩 후 기존 ON/ALL 설정 패널로 진행한다. 설정 완료 후 닫힘 완료를 기다리는 기존 순서를 유지한다.
- 모의 인증 전송은 실제 POST 없이 정상 성공 응답을 제공해 기존 공통 성공 흐름을 재사용한다. 가짜 receipt를 서버로 보내거나 서버 인증 상태를 바꾸지 않는다.
- 후속 회원정보·마이페이지 조회는 정상 서버 응답에서
isAuth,isAdultContentVisible,contentType만 모의 상태로 보정한다. 잔액·역할·개인정보·알림 등 다른 필드는 원본을 유지하고, 실제 조회의 HTTP/API/디코딩 실패를 성공으로 바꾸지 않는다. - 모의 상태는 현재 로그인 세션에 한정한다. 앱 내부 Splash 재시작에는 유지하고, 로그아웃/401 초기화 및 로그인 세션 변경 때 해제한다. 실제 앱 프로세스 종료 후 모의 인증을 영구 유지하는 기능은 만들지 않는다.
- 일반 콘텐츠 설정에서 이후 변경한 값을 오래된 모의 선택값으로 덮어쓰지 않도록 상태를 맞춘다. 새 설정 UI/모의 제어 메뉴/서버 권한 우회는 범위 밖이다.
- 구현 후속: Phase5 완료. dev Debug/Release만 모의 flag 적용, 표준 Debug 바이너리 mock 심볼 제외, native 로딩/패널 4개 검사, 실제 helper/repository/VM 격리 검사와 두 Debug 앱 빌드 통과. 같은 로그인 세션의 인증 전 GET 지연 응답 회귀를 수정했다. 실제 회원 GET·일반 설정 PATCH는 서버 연결을 유지하며 서버 권한 부여는 하지 않는다.
별도 api-contract.md는 만들지 않는다. 아래 계약은 사용자 확인 사항이며 backend 실행/응답은 독립 검증하지 않았다.
11.1 인증과 설정 제출
| 항목 | 계약 |
|---|---|
| 요청 | POST /auth |
| JSON | receipt_id: String, isAdultContentVisible: Bool, contentType: ALL | MALE | FEMALE |
| 의미 | Bootpay receipt와 확인된 콘텐츠 설정을 한 번에 제출 |
| 성공 decode | 기존 ApiResponse<AuthResponse> 사용, gender 외 새 response field/schema를 가정하지 않음 |
| 실패 | transport/API/decode/비성공 envelope를 auth+settings 제출 실패로 처리, 오류 표시와 보존 draft로 사용자 재시도 |
| 기존 재시작 진입점 | 성공 시 확인된 설정을 로컬 반영하고 기존 isRestartApp=true, .splash 전환. 재시작 전에 회원정보 GET을 추가하지 않음 |
11.2 callback 진입점의 회원정보 동기화
| 항목 | 계약 |
|---|---|
| 요청 | GET /member/info?container=ios |
| response | 기존 ApiResponse<GetMemberInfoResponse> |
| 성공 처리 | can, point, auth, role, isAuditionNotification, countryCode, isAdultContentVisible, contentPreference 로컬 값 갱신 후 pending action 소비 |
| 실패 처리 | sheet 재개 및 auth 재전송 금지. pending action 미실행. isRestartApp=true 설정 후 .splash 전환 |
| 재시작 조회 근거 | ContentView가 isRestartApp=true일 때 MainView를 제거하고, SplashView가 해당 플래그를 해제하면 새 MainView의 hasLoadedMainData=false 초기화 경로가 member GET을 호출한다. 실제 조회/로컬 반영은 앱 QA로 확인한다. |
| 범위 제약 | Splash 자체에는 member GET이 없다. 앱 active 조회도 alreadyUpdatedMarketingInfo 조건에 묶여 있어 단독 동기화 근거로 삼지 않는다. 이번 실패 처리에서 자동 재시작 루프를 추가하지 않는다. |
12. 보안과 데이터 취급
- receipt와 token 원문을 문서, 로그, analytics에 기록하지 않는다.
- 본 auth flow의
onDone에서 receipt payload를 dump하는 기존DEBUG_LOG를 제거한다. 무관한 전체 repo 로그 정리는 하지 않는다. - draft는 확인 전 네트워크와 영속화로 보내지 않는다.
13. 성능과 품질 요구사항
- 인증 완료 7 entrypoint, dismiss 차단, sheet 닫힘 후 POST, 중복 확인 방지와 실패 단계별 복구는 수동 앱 QA로 검증한다.
- XCTest bundle이 없으므로 test target/framework를 추가하지 않는다.
- Foundation 기반 assert self-check는 실제
AuthVerifyRequest와ContentType소스를 compile/run해 serializer payload를 확인한다. 흐름 테스트를 대체하지 않는다. SodaLive및SodaLive-dev양쪽 Debug build를 검증한다.- 재시도 idempotency, receipt 유효기간/만료 동작은 미검증 위험으로 기록하며 보장하지 않는다.
14. 성공 기준
14.1 기능 수용 기준
- 일곱 진입점 모두 확인 전 auth POST와 설정 영속화가 0회다. (
AUTH-001~AUTH-005) - 확인 시 sheet가 먼저 닫히고 정확한 combined payload POST는 한 번만 발생한다. (
AUTH-006) - POST 실패 뒤 receipt/draft/action을 보존한 sheet를 재개하고 자동 제출이 없다. (
AUTH-007) - member info 실패 시 auth 재전송 0회, sheet 재개 0회, pending action 실행 0회이며 restart fallback이 동작한다. (
AUTH-008~AUTH-010) - restart fallback이 실제 member refresh를 발생시키고 auth/content 설정을 반영함을 QA에서 확인한다. 소스상 refresh trigger는 런타임 성공 증거가 아니다.
14.2 UI/UX 수용 기준
- Figma ON/OFF, 고정 문구, font scale, radio 표시, safe area가 실제 화면과 일치한다.
- 외부 탭·drag·back·close가 confirmation을 우회하지 않는다.
14.3 승인 및 추적성
- PRD와 plan-task 별도 문서 승인을 받기 전 구현을 시작하지 않는다.
- 승인 후 요구사항이 Task와 수동/자동 검증 증거에 연결된다.
15. Open Questions
- 제품 결정 미결 항목 없음. 요구사항은 사용자 확인 사항이다.
- backend combined payload 지원은 사용자 확인에 근거하며 직접 호출하거나 독립 검증한 사실이 아니다.
- retry idempotency, receipt 만료, restart 통한 member refresh 실동작은 제품 결정이 아닌 구현/운영 검증 위험이다.
16. 요구사항 추적표
| 범위 | 계획 Phase | Goal | 자동 검증 | 수동 검증 |
|---|---|---|---|---|
AUTH-001~AUTH-005 |
2 | P2-T1 |
실제 request serializer assert, 정적 caller 대조 | 일곱 entrypoint, dismiss, 미확인 network/persistence 0회 |
AUTH-006~AUTH-008 |
2 | P2-T1 |
combined payload serializer assert | duplicate confirm, POST 실패 뒤 값 보존·수동 retry |
AUTH-009~AUTH-011 |
2 | P2-T1 |
source review, response decode 경로 | GET 실패 때 restart, auth 재전송 0회, action 0회; restart member refresh 성공 확인 |
| 전체 | 3 | P3-GATE |
serializer, 두 Debug builds | 최종 흐름 및 두 시안 QA |
17. Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 |
|---|---|---|---|---|---|
| 2026-10-12 | DEC-001 | 사용자 확정 | 모든 일곱 auth completion을 confirm sheet 뒤로 이동하고 최초 ON/ALL로 시작 | 사용자 확인 | AUTH-001~AUTH-005, P2-T1 |
| 2026-10-12 | DEC-002 | 사용자 확정 | 확인으로 닫은 뒤 combined auth/settings 제출, 여섯 callback은 회원정보 동기화 후 action 1회 소비 | 사용자 확인 | §11, AUTH-006, AUTH-009 |
| 2026-10-12 | DEC-003 | 사용자 확정 | POST 실패는 sheet/draft 재개, 회원정보 실패는 재전송 없이 restart fallback | 최신 사용자 확인 | AUTH-007~AUTH-008 |
| 2026-10-12 | DEC-004 | 채택 관례 | OFF일 때 contentType=ALL |
기존 ContentSettingsViewModel normalize | AUTH-005 |
18. 변경 관리
- 요구사항 변경은 Decision Log와 요구사항 표, §11 계약, plan-task를 함께 갱신한다.
- 승인 전 앱 코드는 변경하지 않는다. 사용자가 이 PRD와 계획 문서를 명시적으로 승인하면 구현 시작 허가로 처리하고, 승인 기록만 갱신한 뒤 현재 브랜치에서 계획을 실행한다. 별도 추가 승인을 요구하지 않는다.
인터뷰 요약
- 사용자 확정 흐름 및 제약을 기준으로 정리했다. 승인되지 않은 제안은 없다.
- 범위/목표/제약/성공 기준 명확도 1.0, 기존 코드 문맥 0.9.
- 모호도는 휴리스틱 추정
0.015 ≈ 0.02(1 - (목표×0.30 + 범위×0.20 + 제약×0.15 + 성공×0.20 + 문맥×0.15))이며 측정값이 아니다. 문서/구현 승인은 아직 부여되지 않았다.
문서 작성 검증
- 2026-10-12: PRD와 계획 샘플, 정책 및 지정 소스 경로를 읽고 요구사항/구현 근거를 분리해 기록했다. backend 호출, 빌드, 앱 실행은 하지 않았다.
- 2026-10-12: 마이페이지의 기존 재시작과 callback 경로의 GET 동기화를 분리하고, 문서 승인 한 번을 구현 시작 허가로 처리하도록 사용자 지시와 대조했다. 변경 범위와 공백 오류를 확인했으며 구현 승인은 여전히 대기 중이다.
- 2026-10-12 후속: 사용자가 문서를 승인했다. 이전 승인 대기 기록은 이력으로 보존하며, 승인된 범위의 구현·검증을 시작한다.
- 2026-10-12 구현 후속: 7개 인증 진입점과 결합 전송·단계별 복구를 구현했다. 실제 DTO/VM 격리 실행, 네이티브 sheet UI 검사, 두 앱 Debug 시뮬레이터 빌드가 통과했다. 실제 인증 7개 경로와 실패 재시작 후 실서버 회원정보 갱신은 QA 환경이 없어 미검증이며 최종 수용 기준을 일괄 완료 처리하지 않는다. 상세 증거와 한계는 plan-task의 Progress 및
reviews/phase2-auth-content-settings.md에 기록한다.