# 機能設計書 18-サブスクリプション管理

## 概要

本ドキュメントは、Ghostにおけるサブスクリプション管理機能の設計仕様を記載する。本機能は、メンバーのサブスクリプション状態・支払い履歴を管理する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
有料コンテンツビジネスを展開するサイト運営者にとって、メンバーのサブスクリプション状態を正確に把握・管理することは収益管理の基盤である。本機能により、各メンバーの購読状態、支払い状況、契約期間、解約予定などを一元管理し、適切なコンテンツアクセス制御とカスタマーサポートを実現する。

**機能の利用シーン**：
- メンバーのサブスクリプション状態を確認する場合
- サブスクリプションをキャンセル/再開する場合
- 新規サブスクリプションを管理者が手動作成する場合
- MRR（月次経常収益）を確認する場合

**主要な処理内容**：
1. サブスクリプション情報の取得
2. サブスクリプションのキャンセル（期末/即時）
3. サブスクリプションの更新
4. 新規サブスクリプションの作成
5. Stripe Webhookによる同期

**関連システム・外部連携**：
- Stripe API（サブスクリプション操作）
- メンバー管理（サブスク所有者の紐付け）
- Tier管理（購読プランとの紐付け）

**権限による制御**：
- サブスクリプション操作: Administrator以上
- 自身のサブスクリプション確認: メンバー本人

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 18 | メンバー詳細画面 | 補助機能 | メンバーの購読状態管理 |
| 80 | アカウントホームページ | 補助機能 | 購読状態の表示 |

## 機能種別

CRUD操作 / 外部API連携

## 入力仕様

### 入力パラメータ（サブスクリプション更新）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | メンバーID | 存在チェック |
| subscription_id | string | Yes | サブスクリプションID | 存在チェック |
| cancel_at_period_end | boolean | No | 期末キャンセルフラグ | - |
| status | string | No | ステータス変更 | canceled のみ |

### 入力パラメータ（サブスクリプション作成）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | メンバーID | 存在チェック |
| stripe_price_id | string | Yes | Stripe価格ID | 存在チェック |

### 入力データソース

- 管理画面からのAPI呼び出し
- Portal（メンバー用インターフェース）
- Stripe Webhook

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | サブスクリプションID（Stripe） |
| customer | object | Stripe顧客情報 |
| plan | object | 旧形式プラン情報 |
| price | object | 価格情報 |
| status | string | サブスクリプションステータス |
| start_date | Date | 開始日 |
| current_period_end | Date | 現在期間終了日 |
| cancel_at_period_end | boolean | 期末キャンセルフラグ |
| cancellation_reason | string | キャンセル理由 |
| trial_start_at | Date | トライアル開始日 |
| trial_end_at | Date | トライアル終了日 |
| discount_start | Date | 割引開始日 |
| discount_end | Date | 割引終了日 |
| default_payment_card_last4 | string | カード下4桁 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ members エンドポイントで処理
2. メンバー・サブスクリプション検証
   └─ 存在チェック、権限チェック
3. Stripe API呼び出し
   └─ サブスクリプション更新/キャンセル
4. ローカルDB同期
   └─ Webhookまたは即時更新
5. レスポンス返却
   └─ 更新後のメンバー情報を返却
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト] --> B{操作種別}
    B -->|editSubscription| C{status=canceled?}
    C -->|Yes| D[cancelSubscription]
    C -->|No| E[updateSubscription]
    B -->|createSubscription| F[createSubscription]
    D --> G[Stripe API キャンセル]
    E --> H[Stripe API 更新]
    F --> I[Stripe API 作成]
    G --> J[DB更新]
    H --> J
    I --> J
    J --> K[メンバー情報再取得]
    K --> L[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-18-01 | 即時キャンセル | status=canceledで即時キャンセル | editSubscription時 |
| BR-18-02 | 期末キャンセル | cancel_at_period_end=trueで期末キャンセル | editSubscription時 |
| BR-18-03 | MRR更新 | サブスク変更時にMRRを再計算 | 状態変更時 |
| BR-18-04 | Webhook同期 | StripeからのWebhookで状態を同期 | Webhook受信時 |
| BR-18-05 | カスタマー必須 | サブスク作成にはStripeカスタマー必須 | createSubscription時 |

### 計算ロジック

- MRR計算: サブスクリプション金額を月額に正規化（年額は12で割る）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| サブスク作成 | members_stripe_customers_subscriptions | INSERT | 新規レコード |
| サブスク更新 | members_stripe_customers_subscriptions | UPDATE | 状態更新 |
| UPSERT | members_stripe_customers_subscriptions | UPSERT | Webhook経由 |

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

#### members_stripe_customers_subscriptions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | subscription_id | StripeサブスクリプションID | PK |
| INSERT/UPDATE | customer_id | Stripe顧客ID | FK |
| INSERT/UPDATE | status | active/canceled/past_due等 | Stripe準拠 |
| INSERT/UPDATE | cancel_at_period_end | 期末キャンセルフラグ | boolean |
| INSERT/UPDATE | cancellation_reason | キャンセル理由 | nullable |
| INSERT/UPDATE | current_period_end | 現在期間終了日 | - |
| INSERT/UPDATE | plan_id | プランID | - |
| INSERT/UPDATE | plan_amount | プラン金額 | - |
| INSERT/UPDATE | plan_interval | 課金間隔 | month/year |
| INSERT/UPDATE | plan_currency | 通貨 | - |
| INSERT/UPDATE | mrr | 月次経常収益 | 計算値 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NotFoundError | 404 | メンバーが見つからない | 正しいIDを指定 |
| NotFoundError | 404 | サブスクが見つからない | 正しいサブスクIDを指定 |
| ValidationError | 400 | subscription_id未指定 | 必須パラメータを指定 |
| ValidationError | 400 | cancel_at_period_end未指定 | 必須パラメータを指定 |

### リトライ仕様

- Stripe API障害時: Webhookで後から同期

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

- Stripe APIとDBの整合性: Webhookによる最終整合性

## パフォーマンス要件

- サブスクリプション更新: Stripe API応答に依存

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

- 認証必須: Admin APIまたはメンバー認証
- 他人のサブスク操作は管理者のみ

## 備考

- StripeCustomerSubscriptionモデルにserialize()でJSON形式を定義

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stripe-customer-subscription.js | `ghost/core/core/server/models/stripe-customer-subscription.js` | モデル定義、serialize() |

**読解のコツ**:
- tableName で DBテーブル名を確認
- serialize() でJSON出力形式を確認
- upsert() で Webhook 更新ロジックを確認

**主要処理フロー**:
- **4-9行目**: モデル基本定義、デフォルトMRR
- **19-70行目**: serialize() - 出力形式の定義
- **74-83行目**: upsert() - 静的メソッドでINSERT/UPDATE判定

#### Step 2: APIエンドポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | members.js | `ghost/core/core/server/api/endpoints/members.js` | editSubscription, createSubscription |

**主要処理フロー**:
- **178-238行目**: editSubscription - キャンセル/更新処理
- **240-282行目**: createSubscription - 新規作成

#### Step 3: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | member-repository.js | `ghost/core/core/server/services/members/members-api/repositories/member-repository.js` | サブスク操作メソッド |

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

```
members.js (API Endpoint)
    │
    ├─ editSubscription()
    │      ├─ membersService.api.members.cancelSubscription()
    │      └─ membersService.api.members.updateSubscription()
    │
    └─ createSubscription()
           └─ membersService.api.members.createSubscription()
                  │
                  └─ member-repository.js
                         │
                         └─ Stripe API
                                │
                                └─ members_stripe_customers_subscriptions
```

### データフロー図

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

管理画面/Portal       members.js          member-repository
リクエスト ────▶ (API Endpoint) ────▶ (Service Layer)
   │                   │                    │
   │                   │                    ▼
   │                   │               Stripe API
   │                   │                    │
   │                   │                    ▼
   │                   │         members_stripe_customers_subscriptions
   │                   │
   └── JSON ◀─────── レスポンス (memberBREADService.read)

[Webhook]

Stripe ─────────────▶ subscription-event-service
                              │
                              ▼
                    members_stripe_customers_subscriptions (UPSERT)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| stripe-customer-subscription.js | `ghost/core/core/server/models/stripe-customer-subscription.js` | ソース | Bookshelfモデル |
| members.js | `ghost/core/core/server/api/endpoints/members.js` | ソース | APIエンドポイント |
| member-repository.js | `ghost/core/core/server/services/members/members-api/repositories/member-repository.js` | ソース | リポジトリ |
| subscription-event-service.js | `ghost/core/core/server/services/stripe/services/webhook/subscription-event-service.js` | ソース | Webhookハンドラ |
