fix(content): 무료 콘텐츠 포인트 사용 조건을 보정한다
This commit is contained in:
171
docs/20260731_무료_콘텐츠_포인트_결제_불가/prd.md
Normal file
171
docs/20260731_무료_콘텐츠_포인트_결제_불가/prd.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# PRD: 무료 콘텐츠 포인트 결제 불가
|
||||
|
||||
## 문서 정보
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 문서 상태 | 구현 기준 확정 |
|
||||
| 작성일 | 2026-07-31 |
|
||||
| 최종 수정일 | 2026-07-31 |
|
||||
| 대상 제품 | 소비자용 오디오 콘텐츠 조회 API |
|
||||
| 작성자·결정권자 | 사용자 |
|
||||
| 관련 API Contract | 별도 문서 없음. 이 문서의 `8. API 계약`을 기준으로 사용 |
|
||||
| 관련 구현 계획 | `docs/20260731_무료_콘텐츠_포인트_결제_불가/plan-task.md` |
|
||||
| 관련 review | `reviews/phase-1-review.md`, `reviews/phase-2-review.md` |
|
||||
|
||||
## 1. Overview
|
||||
|
||||
무료 오디오 콘텐츠와 포인트 결제 가능 상태가 소비자 화면에서 동시에 노출되지 않도록 조회 계약을 보정한다.
|
||||
저장된 포인트 결제 가능 설정은 유지하되, 소비자용 응답과 포인트 전용 목록에서는 가격을 함께 반영한 실질 상태를 사용한다.
|
||||
|
||||
## 2. Problem Statement
|
||||
|
||||
- 현재 일부 조회 응답은 `price == 0`이면서 저장된 `isPointAvailable == true`인 콘텐츠를 그대로 포인트 결제 가능으로 노출한다.
|
||||
- 포인트 추천과 전체 탭 POINT 조회는 저장된 `isPointAvailable`만 필터링해 무료 콘텐츠가 포함될 수 있다.
|
||||
- 무료와 포인트 결제 가능 상태가 함께 노출되면 클라이언트의 가격 표시와 결제 진입 판단이 서로 모순될 수 있다.
|
||||
|
||||
문제를 해결했다는 판단은 소비자용 모든 대상 응답에서 무료 콘텐츠의 포인트 결제 가능 여부가 `false`이고,
|
||||
POINT 전용 목록과 개수에서 무료 콘텐츠가 제외되는 것으로 한다.
|
||||
|
||||
## 3. Goals
|
||||
|
||||
- 무료 콘텐츠의 소비자용 포인트 결제 가능 여부를 항상 `false`로 응답한다.
|
||||
- 유료이면서 저장된 포인트 결제 가능 설정이 `true`인 콘텐츠는 기존처럼 `true`로 응답한다.
|
||||
- 포인트 전용 목록, 전체 개수와 페이징 판단에서 무료 콘텐츠를 제외한다.
|
||||
- 기존 공개 API 필드명, 응답 구조와 DB 저장값을 변경하지 않는다.
|
||||
|
||||
## 4. Non-Goals
|
||||
|
||||
- 콘텐츠 생성·수정 시 `isPointAvailable` 저장값을 강제로 변경하지 않는다.
|
||||
- 기존 데이터의 일괄 수정이나 DB migration을 수행하지 않는다.
|
||||
- `/api/v2/admin/ai-characters/**/audio-contents` 관리자 목록·상세의 저장값 표현을 변경하지 않는다.
|
||||
- `/audio-content/{id}` 상세를 제외한 legacy 목록·추천·랭킹 API는 변경하지 않는다.
|
||||
- 콘텐츠 구매·대여·소장·포인트 차감 로직은 변경하지 않는다.
|
||||
- 공개 DTO의 필드 추가·삭제·이름 변경을 수행하지 않는다.
|
||||
|
||||
## 5. Target Users and Permissions
|
||||
|
||||
| 사용자 | 목표 | 주요 작업 | 적용 범위 |
|
||||
|---|---|---|---|
|
||||
| 소비자 | 무료 콘텐츠를 포인트 결제 대상으로 오인하지 않는다 | 콘텐츠 상세·목록·추천 조회 | 대상 소비자용 API |
|
||||
| 관리자 | 저장된 콘텐츠 설정을 그대로 확인한다 | AI 캐릭터 콘텐츠 목록·상세 조회 | 변경 제외 |
|
||||
|
||||
기존 endpoint별 인증·성인 노출·차단 관계·공개 상태 정책은 변경하지 않는다.
|
||||
|
||||
## 6. 핵심 정책
|
||||
|
||||
### 6.1 가격과 포인트 결제 가능 여부
|
||||
|
||||
- 무료 콘텐츠는 `price == 0`으로 정의한다.
|
||||
- 소비자에게 노출하는 실질 포인트 결제 가능 여부는 다음 식으로 정의한다.
|
||||
|
||||
```text
|
||||
effectivePointAvailable = storedIsPointAvailable && price > 0
|
||||
```
|
||||
|
||||
- `price == 0`이고 저장값이 `true`이면 소비자 응답은 `false`다.
|
||||
- `price > 0`이고 저장값이 `true`이면 소비자 응답은 `true`다.
|
||||
- 저장값이 `false`이면 가격과 관계없이 소비자 응답은 `false`다.
|
||||
- 이 정책은 응답 조립 시 적용하며 엔티티의 저장값은 변경하지 않는다.
|
||||
|
||||
### 6.2 포인트 전용 조회
|
||||
|
||||
- 포인트 전용 콘텐츠는 `isPointAvailable == true && price > 0`인 공개 오디오로 정의한다.
|
||||
- `GET /api/v2/audio/recommendations`의 `pointAudios`는 이 조건을 사용한다.
|
||||
- `GET /api/v2/audio/contents?type=POINT`의 목록과 `totalCount`는 동일한 조건을 사용한다.
|
||||
- `hasNext`는 보정된 목록 조건으로 조회한 `size + 1` 결과를 기준으로 기존 방식대로 계산한다.
|
||||
- 무료 콘텐츠는 `isPointAvailable == true`로 저장되어 있어도 POINT 목록, 개수와 페이징 후보에서 제외한다.
|
||||
|
||||
## 7. 기능 요구사항
|
||||
|
||||
| ID | 상태 | 요구사항 | 수용 기준 | 계획 연결 |
|
||||
|---|---|---|---|---|
|
||||
| `POINT-001` | 확정 | 무료 기준은 `price == 0`이다 | 무료 fixture가 가격 0으로 판정된다 | `P1-T1`, `P2-T1` |
|
||||
| `POINT-002` | 확정 | 소비자용 실질 포인트 가능 여부는 `storedIsPointAvailable && price > 0`이다 | 무료·저장값 true 응답이 false이고 유료·저장값 true 응답이 true다 | `P1-T1` |
|
||||
| `POINT-003` | 확정 | legacy 콘텐츠 상세의 `isAvailableUsePoint`에 실질 상태를 적용한다 | `GET /audio-content/{id}` 응답 회귀 테스트가 통과한다 | `P1-T1` |
|
||||
| `POINT-004` | 확정 | 대상 v2 소비자 응답의 `isPointAvailable`에 실질 상태를 적용한다 | 각 응답 변환 테스트가 무료 true 저장값을 false로 보정한다 | `P1-T1` |
|
||||
| `POINT-005` | 확정 | 추천 `pointAudios`에서 무료 콘텐츠를 제외한다 | 추천 repository·E2E 테스트에서 가격 0 항목이 없다 | `P2-T1` |
|
||||
| `POINT-006` | 확정 | 전체 탭 POINT 목록·`totalCount`·`hasNext`가 같은 유료 포인트 조건을 사용한다 | repository·E2E 테스트의 목록과 페이징 메타데이터가 일치한다 | `P2-T1` |
|
||||
| `POINT-007` | 확정 | AI 캐릭터 관리자 콘텐츠 조회는 저장값을 그대로 반환한다 | 무료·저장값 true인 관리자 상세가 true를 유지한다 | `P1-T1` |
|
||||
| `POINT-008` | 확정 | 공개 API 스키마와 DB 저장값을 유지한다 | DTO 필드 집합과 관리자 저장값 회귀 테스트가 통과한다 | `P1-GATE`, `P2-GATE` |
|
||||
|
||||
## 8. API 계약
|
||||
|
||||
### 8.1 응답 보정 대상
|
||||
|
||||
| Method | Path | 응답 경계 | 보정 필드 |
|
||||
|---|---|---|---|
|
||||
| GET | `/audio-content/{id}` | `GetAudioContentDetailResponse` | `isAvailableUsePoint` |
|
||||
| GET | `/api/v2/contents` | `ContentOverviewItemResponse` | `isPointAvailable` |
|
||||
| GET | `/api/v2/audio/contents` | `MainContentAudioResponse` | `isPointAvailable` |
|
||||
| GET | `/api/v2/audio/recommendations` | `AudioCardResponse` | `isPointAvailable` |
|
||||
| GET | `/api/v2/home/recommendations` | `HomeFirstAudioContentItem` | `isPointAvailable` |
|
||||
| GET | `/api/v2/creator-channels/{creatorId}/home` | `CreatorChannelAudioContentResponse` | `isPointAvailable` |
|
||||
| GET | `/api/v2/creator-channels/{creatorId}/audio` | `CreatorChannelAudioContentResponse` | `isPointAvailable` |
|
||||
| GET | `/api/v2/creator-channels/{creatorId}/live` | `CreatorChannelAudioContentResponse` | `isPointAvailable` |
|
||||
|
||||
- `GET /api/v2/creator-channels/{creatorId}/series`는 콘텐츠 가격과 포인트 가능 필드를 반환하지 않아 코드 변경 대상이 아니다.
|
||||
- 현재 v2 소비자용 API에 동일 필드를 반환하는 새 경로가 발견되면 같은 식을 적용하고 계획 범위를 먼저 갱신한다.
|
||||
|
||||
### 8.2 포인트 전용 조회 조건 보정 대상
|
||||
|
||||
| Method | Path/section | 변경 전 | 변경 후 |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/v2/audio/recommendations`의 `pointAudios` | `isPointAvailable == true` | `isPointAvailable == true && price > 0` |
|
||||
| GET | `/api/v2/audio/contents?type=POINT` | `isPointAvailable == true` | `isPointAvailable == true && price > 0` |
|
||||
|
||||
### 8.3 변경 제외 관리자 계약
|
||||
|
||||
| Method | Path | 정책 |
|
||||
|---|---|---|
|
||||
| GET | `/api/v2/admin/ai-characters/{characterId}/audio-contents` | 저장된 `isPointAvailable`을 그대로 반환 |
|
||||
| GET | `/api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}` | 저장된 값을 `isAvailableUsePoint`에 그대로 반환 |
|
||||
|
||||
## 9. 기술적 제약
|
||||
|
||||
- Kotlin, Java 17, Spring Boot 2.7.14와 현재 QueryDSL/JPA 구조를 유지한다.
|
||||
- 새 dependency, DB schema, API endpoint와 DTO를 추가하지 않는다.
|
||||
- 응답 변환 경계에서는 `configured && price > 0` 식을 직접 사용해 현재 파일 책임 안에서 최소 변경한다.
|
||||
- POINT 조회 조건은 기존 repository 조건 함수에 `price > 0`을 결합해 목록과 count가 같은 조건을 공유하게 한다.
|
||||
- 관리자 mapper와 관리자 조회 service는 변경하지 않는다.
|
||||
- 관련 없는 콘텐츠 가격·결제·추천 점수·정렬·성인·차단 정책은 변경하지 않는다.
|
||||
|
||||
## 10. 테스트와 품질 요구사항
|
||||
|
||||
- TDD 순서로 무료·저장값 true fixture의 실패 테스트를 먼저 작성하고 실패 원인이 기존 원본 전달임을 확인한다.
|
||||
- 소비자 응답 경계별로 무료 true → false와 유료 true → true를 검증한다.
|
||||
- 추천 POINT와 전체 탭 POINT에 무료 true fixture를 추가해 목록 제외를 검증한다.
|
||||
- 전체 탭은 POINT `totalCount`와 `hasNext`가 목록 조건과 일치하는지 검증한다.
|
||||
- 관리자 상세는 무료 true 저장값을 그대로 true로 응답하는 회귀 테스트를 유지한다.
|
||||
- focused test 후 직접 영향받는 v2 콘텐츠·홈·크리에이터 채널 회귀와 `ktlintCheck`를 실행한다.
|
||||
- 여러 API 경계를 변경하므로 최종 Gate에서 전체 `test`를 실행한다.
|
||||
|
||||
## 11. 성공 기준
|
||||
|
||||
- [ ] `price == 0`, 저장값 `true`인 콘텐츠가 모든 대상 소비자 응답에서 `false`다. (`POINT-002~004`)
|
||||
- [ ] `price > 0`, 저장값 `true`인 콘텐츠가 대상 소비자 응답에서 `true`다. (`POINT-002`)
|
||||
- [ ] 저장값 `false`인 콘텐츠는 가격과 관계없이 `false`다. (`POINT-002`)
|
||||
- [ ] 무료·저장값 true 콘텐츠가 추천 `pointAudios`에서 제외된다. (`POINT-005`)
|
||||
- [ ] 무료·저장값 true 콘텐츠가 전체 탭 POINT 목록·`totalCount`·`hasNext` 후보에서 제외된다. (`POINT-006`)
|
||||
- [ ] 관리자 목록·상세와 DB 저장값은 변경되지 않는다. (`POINT-007~008`)
|
||||
- [ ] 공개 응답 필드명과 구조가 변경되지 않는다. (`POINT-008`)
|
||||
|
||||
## 12. Open Questions
|
||||
|
||||
없음.
|
||||
|
||||
## 13. 요구사항 추적표
|
||||
|
||||
| 요구사항 | 계획 Phase | Goal | 자동 검증 |
|
||||
|---|---:|---|---|
|
||||
| `POINT-001~004`, `POINT-007~008` | 1 | `P1-T1`, `P1-GATE` | legacy 상세·v2 응답 mapper·관리자 회귀 테스트 |
|
||||
| `POINT-005~006`, `POINT-008` | 2 | `P2-T1`, `P2-GATE` | 추천/전체 탭 repository·E2E 테스트 |
|
||||
|
||||
## 14. Decision Log
|
||||
|
||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·Goal |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-07-31 | `DEC-001` | 확정 | 무료 기준을 `price == 0`으로 고정하고 소비자용 포인트 가능 여부를 `storedIsPointAvailable && price > 0`으로 계산한다 | 사용자 인터뷰 | `POINT-001~004`, `P1-T1` |
|
||||
| 2026-07-31 | `DEC-002` | 확정 | 추천과 전체 탭 POINT 조회에서 무료 콘텐츠를 제외한다 | 사용자 선택 A | `POINT-005~006`, `P2-T1` |
|
||||
| 2026-07-31 | `DEC-003` | 확정 | AI 캐릭터 관리자 조회와 DB 저장값은 변경하지 않는다 | 사용자 선택 A | `POINT-007~008`, `P1-T1` |
|
||||
| 2026-07-31 | `DEC-004` | 확정 | 새 통합 문서를 기준으로 만들고 충돌하는 기존 추천·전체 탭 문서에는 정정 기록을 누적한다 | 사용자 승인 | 관련 문서 전체 |
|
||||
Reference in New Issue
Block a user