# 機能設計書 15-Tier（プラン）管理

## 概要

本ドキュメントは、GhostにおけるTier（プラン）管理機能の設計仕様を記載する。本機能は、有料プラン（Free/Paid）の作成・編集・価格設定を行い、メンバーシップの基盤となる料金体系を管理する。

### 本機能の処理概要

**業務上の目的・背景**：
サイト運営者が有料コンテンツビジネスを展開するためには、複数の価格帯や特典を持つ購読プランを設定する必要がある。Tier機能により、月額/年額の価格設定、特典（Benefits）の定義、トライアル期間の設定など、柔軟なプラン設計が可能となる。これにより、読者のニーズに合わせた多様なメンバーシップオプションを提供できる。

**機能の利用シーン**：
- 新しい有料プランを作成する場合
- 既存プランの価格を変更する場合
- プランの特典（Benefits）を編集する場合
- プランをアーカイブ/再公開する場合
- トライアル期間を設定する場合

**主要な処理内容**：
1. Tierの作成（名前、スラッグ、価格、通貨設定）
2. Tierの取得（一覧/個別、フィルタリング）
3. Tierの更新（名前、説明、価格、特典、ステータス）
4. デフォルトTierの取得
5. Stripe商品/価格との同期

**関連システム・外部連携**：
- Stripe API（価格情報の同期）
- メンバー管理（Tier割り当て）
- コンテンツゲーティング（Tierに基づくアクセス制御）

**権限による制御**：
- Tier設定の変更: Administrator以上

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 45 | ティア設定 | 主画面 | 有料プランの作成・価格設定 |
| 79 | サインアップページ | 補助機能 | 利用可能プランの表示 |
| 81 | プラン選択ページ | 主画面 | 購読プランの選択・変更 |

## 機能種別

CRUD操作 / 設定管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | Tier名 | 191文字以内 |
| slug | string | No | URLスラッグ | 191文字以内、ユニーク |
| description | string | No | 説明 | 191文字以内 |
| welcomePageURL | string | No | ウェルカムページURL | URL形式 |
| visibility | string | No | 公開設定 | public/none |
| status | string | No | ステータス | active/archived |
| currency | string | Yes (paid) | 通貨コード | 3文字ISO通貨コード |
| monthlyPrice | number | No | 月額価格 | 0-999999.99（セント単位） |
| yearlyPrice | number | No | 年額価格 | 0-999999.99（セント単位） |
| trialDays | number | No | トライアル日数 | 0以上の整数 |
| benefits | Array<string> | No | 特典リスト | 文字列の配列 |

### 入力データソース

- 管理画面からのAPI呼び出し
- Stripe Webhookからの価格同期

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | Tier ID（ObjectID） |
| slug | string | URLスラッグ |
| name | string | Tier名 |
| description | string | 説明 |
| welcomePageURL | string | ウェルカムページURL |
| status | string | active/archived |
| visibility | string | public/none |
| type | string | paid/free |
| currency | string | 通貨コード（大文字） |
| monthlyPrice | number | 月額価格（セント単位） |
| yearlyPrice | number | 年額価格（セント単位） |
| trialDays | number | トライアル日数 |
| benefits | Array<string> | 特典リスト |
| createdAt | Date | 作成日時 |
| updatedAt | Date | 更新日時 |

### 出力先

- REST API レスポンス（JSON形式）
- DBテーブル（products）

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ TiersAPIでリクエスト処理
2. バリデーション
   └─ Tierドメインモデルでバリデーション
3. スラッグ生成（新規作成時）
   └─ SlugServiceで重複チェック付き生成
4. ドメインモデル操作
   └─ Tier.create() または tier.updatePricing()
5. 永続化
   └─ TierRepositoryでDB保存
6. イベント発火
   └─ TierCreatedEvent / TierPriceChangeEvent等
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト] --> B{操作種別}
    B -->|browse| C[getAll]
    B -->|read| D[getById]
    B -->|add| E[Tier.create]
    B -->|edit| F[tier.updatePricing / setters]
    C --> G[レスポンス返却]
    D --> G
    E --> H[repository.save]
    F --> H
    H --> I[イベント発火]
    I --> G
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-15-01 | Free Tier作成不可 | type='free' のTierは新規作成不可 | add操作時 |
| BR-15-02 | 価格デフォルト | monthlyPrice/yearlyPrice未指定時はデフォルト値（500/5000セント） | 新規作成時 |
| BR-15-03 | 価格上限 | 価格は999999.99以下（9999999999セント以下） | 価格設定時 |
| BR-15-04 | Free Tier価格不可 | Free Tierには価格・通貨・トライアルを設定不可 | 更新時 |
| BR-15-05 | 通貨大文字化 | 通貨コードは自動的に大文字に変換 | 価格設定時 |
| BR-15-06 | イベント発火 | 価格変更時は TierPriceChangeEvent を発火 | 価格更新時 |

### 計算ロジック

- 価格はセント単位で保存（表示時に100で割って表示）

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| Tier作成 | products | INSERT | 新規Tierレコード |
| Tier更新 | products | UPDATE | Tier情報更新 |
| 特典管理 | benefits, products_benefits | INSERT/UPDATE/DELETE | 特典の関連付け |
| Stripe同期 | stripe_products, stripe_prices | INSERT/UPDATE | Stripe情報同期 |

### テーブル別操作詳細

#### products

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | name | Tier名 | 191文字以内 |
| INSERT/UPDATE | slug | URLスラッグ | ユニーク |
| INSERT/UPDATE | description | 説明 | nullable |
| INSERT/UPDATE | welcome_page_url | ウェルカムURL | nullable |
| INSERT/UPDATE | visibility | public/none | デフォルト: none |
| INSERT/UPDATE | active | ステータス | true=active |
| INSERT/UPDATE | type | paid/free | - |
| INSERT/UPDATE | currency | 通貨コード | 3文字 |
| INSERT/UPDATE | monthly_price_id | Stripe月額価格ID | FK |
| INSERT/UPDATE | yearly_price_id | Stripe年額価格ID | FK |
| INSERT/UPDATE | trial_days | トライアル日数 | 0以上 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| BadRequestError | 400 | Free Tier作成試行 | Paidタイプで作成 |
| ValidationError | 400 | 名前が191文字超過 | 文字数を削減 |
| ValidationError | 400 | 不正な通貨コード | 3文字ISO通貨コード |
| ValidationError | 400 | 負の価格 | 0以上の値を設定 |
| ValidationError | 400 | Free Tierに価格設定 | Free Tierは価格不可 |

### リトライ仕様

特になし（同期処理のため）

## トランザクション仕様

- Tier保存とBenefits関連付けを同一トランザクションで実行

## パフォーマンス要件

- Tier一覧取得: 全件取得（通常少数のため）
- ページネーション: 現状は単一ページ

## セキュリティ考慮事項

- 認証必須: Admin API アクセストークン
- 権限チェック: Administrator以上

## 備考

- productsテーブルはTierと同義（歴史的経緯）
- Free Tierはシステムで1つのみ存在

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: ドメインモデルを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | tier.js | `ghost/core/core/server/services/tiers/tier.js` | Tierドメインモデル、バリデーション |

**読解のコツ**:
- プライベートフィールド（#id, #name等）とゲッター/セッターのパターン
- create()静的メソッドでファクトリーパターン
- updatePricing()でのイベント発火

**主要処理フロー**:
- **10-287行目**: Tierクラス定義
- **151-173行目**: updatePricing() - 価格更新とイベント発火
- **232-287行目**: create() - ファクトリーメソッド
- **290-507行目**: バリデーション関数群

#### Step 2: API層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | tiers-api.js | `ghost/core/core/server/services/tiers/tiers-api.js` | APIメソッド定義 |

**主要処理フロー**:
- **49-70行目**: browse() - 一覧取得
- **77-82行目**: read() - 個別取得
- **89-102行目**: readDefaultTier() - デフォルトTier取得
- **109-138行目**: edit() - 更新処理
- **144-170行目**: add() - 新規作成

#### Step 3: リポジトリ層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | tier-repository.js | `ghost/core/core/server/services/tiers/tier-repository.js` | DB操作 |

### プログラム呼び出し階層図

```
TiersAPI
    │
    ├─ browse()
    │      └─ repository.getAll()
    │
    ├─ read()
    │      └─ repository.getById()
    │
    ├─ readDefaultTier()
    │      └─ repository.getAll({ filter, limit: 1 })
    │
    ├─ edit()
    │      ├─ repository.getById()
    │      ├─ tier.updatePricing()
    │      │      └─ TierPriceChangeEvent.create()
    │      └─ repository.save()
    │
    └─ add()
           ├─ slugService.generate()
           ├─ Tier.create()
           │      └─ TierCreatedEvent.create()
           └─ repository.save()
```

### データフロー図

```
[入力]                     [処理]                           [出力]

管理画面              TiersAPI            Tier (Domain)
リクエスト ────▶ (API Layer) ────▶ (Domain Model)
   │                   │                    │
   │                   │                    ├─ バリデーション
   │                   │                    └─ イベント生成
   │                   │
   │                   ▼
   │             TierRepository ──────────▶ products テーブル
   │                   │                    benefits テーブル
   │                   │                    products_benefits
   │                   │
   └── JSON ◀─────── レスポンス
       Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tier.js | `ghost/core/core/server/services/tiers/tier.js` | ソース | ドメインモデル |
| tiers-api.js | `ghost/core/core/server/services/tiers/tiers-api.js` | ソース | API層 |
| tier-repository.js | `ghost/core/core/server/services/tiers/tier-repository.js` | ソース | リポジトリ |
| service.js | `ghost/core/core/server/services/tiers/service.js` | ソース | サービスラッパー |
| index.js | `ghost/core/core/server/services/tiers/index.js` | ソース | エントリーポイント |
| tier-created-event.js | `ghost/core/core/server/services/tiers/tier-created-event.js` | ソース | 作成イベント |
| tier-price-change-event.js | `ghost/core/core/server/services/tiers/tier-price-change-event.js` | ソース | 価格変更イベント |
| tier-activated-event.js | `ghost/core/core/server/services/tiers/tier-activated-event.js` | ソース | 有効化イベント |
| tier-archived-event.js | `ghost/core/core/server/services/tiers/tier-archived-event.js` | ソース | アーカイブイベント |
| product.js | `ghost/core/core/server/models/product.js` | ソース | Bookshelfモデル |
