> ## Documentation Index
> Fetch the complete documentation index at: https://docs.one.fim.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe Billing

> ユーザーごとのStripe サブスクリプション（Free + Pro）を有効にし、トークンクォータとセルフサービスプラン管理を実現します。

FIM One は、インスタンスアクセスモデルの背後に完全な Stripe 請求パイプラインを搭載しています。プライベートデプロイメントでは **No subscriptions** のままで、支払い UI は表示されません。カタログが必要なオペレーターは **Included + paid** または **Paid only** を選択すると、ホストされた Checkout、カスタマーポータル、webhook 駆動のサブスクリプションライフサイクル、およびクォータ強制が利用できます。

<Note>
  ソフトウェアのデフォルトは **No subscriptions**（`access_model = off`、`default_token_quota = 0` は無制限を意味します）です。新規インストールと既存のセルフホストはここから始まります。管理者が Stripe の設定を選択するまで、請求 UI は表示されません。
</Note>

## 何が得られるか

* **3つのアクセスポスチャー**: サブスクリプションなし、含まれている + 有料、または有料のみ
* **Stripe ホストチェックアウト** — ユーザーはアップグレード時にカード情報があなたのコードに触れることなく完了
* **カスタマーポータル** — ユーザーは支払い方法の更新、請求書のダウンロード、キャンセルをすべて Stripe の UI で実行
* **Webhook 駆動ライフサイクル** — サブスクリプションは自動的にプロビジョニングおよび更新され、キャンセルされた有料ユーザーは期間終了時に含まれているティアに戻るか（または有料のみの場合は権利がなくなる）
* **クォータ強制** — トークン使用量は期間ごとに追跡され、ストリーム中のカットオフと構造化されたアップグレードプロンプト
* **管理ページ** — プラン CRUD とサブスクリプション監視用

## 前提条件

1. **Stripe アカウント**（ライブモード有効化済み）。シンガポール法人の場合は KYC（事業 UEN、取締役 ID、銀行口座）の完了が必須です。承認には通常 1～3 日かかります。
2. **Stripe ライブ API キー**（制限付きタイプ推奨。標準の `sk_live_***` より取り消しやすく、権限がスコープされています）。
3. **Webhook エンドポイント**（`<your-domain>/api/webhooks/stripe` で公開アクセス可能）。
4. **銀行口座**（支払い用）。USD 以外がデフォルトの Stripe アカウントの場合、マルチ通貨決済（例：USD 口座への USD 支払い）を推奨します。これにより、トランザクションあたり 1.5～2% の為替損失を回避できます。

## セットアップ

### 1. Stripe ダッシュボード

#### Pro製品を作成する

1. **Catalog → Products → + Add product**
2. Name: `Pro`、description: `5M tokens / month, priority support`
3. Pricing: Recurring、monthly、\$20.00 USD（ご自身の価格戦略に合わせて調整してください）
4. Save → 結果の `price_***` IDをコピーします（アクティベーション後、ローカルの `billing_plans` テーブルをこの値で更新します）

#### 制限付きAPIキーを作成する

1. **Developers → API keys → + Create restricted key**
2. Name: `fim-one production`
3. Permissions (minimum):
   * **Customers**: Write
   * **Subscriptions**: Write
   * **Checkout Sessions**: Write
   * **Customer portal**: Write
   * **Prices**: Read
   * **Products**: Read
4. Save → copy `rk_live_***`

#### Webhookエンドポイントを登録する

1. **Developers → Webhooks → + Add endpoint**
2. URL: `https://<your-domain>/api/webhooks/stripe`
3. 受け取るイベント：
   * `checkout.session.completed`
   * `customer.subscription.created`
   * `customer.subscription.updated`
   * `customer.subscription.deleted`
   * `invoice.payment_succeeded`
   * `invoice.payment_failed`
4. 保存後、「Reveal signing secret」をクリック → `whsec_***` をコピー

#### 複数通貨決済の設定（推奨）

Stripeアカウントのデフォルト通貨が、請求する価格通貨と異なる場合（一般的な例：SGDアカウントでUSD請求）：

1. **Settings → Bank accounts and currencies → Add a settlement currency**
2. 価格通貨を選択（例：USD）
3. 対応する銀行口座を接続（例：Aspire USD仮想口座）
4. 保存 — StripeはUSD請求をUSD支払いに直接ルーティングし、為替変換は行いません

### 2. Backend `.env`

本番環境の`.env`で以下の3つのキーを設定します：

```bash theme={null}
STRIPE_SECRET_KEY=rk_live_***
STRIPE_WEBHOOK_SECRET=whsec_***
STRIPE_BILLING_RETURN_URL=https://<your-domain>/settings?tab=billing
```

詳細は[環境変数](/configuration/environment-variables)を参照してください。

`.env`を編集した後、バックエンドを再起動してキーが読み込まれるようにします：

```bash theme={null}
./deploy.sh   # or: docker compose restart fim-one
```

### 3. 管理員で有効化

1. 管理者としてログイン
2. **Admin → System Settings → Access and billing**
3. 1つのポスチャーを選択（インスタンスは常に正確に1つの状態）:
   * **No subscriptions**（ソフトウェアデフォルト）— Stripe未使用。トークンアクセスはデフォルト月間クォータ（`0` = 無制限）。
   * **Included + paid** — Stripe必須。新規ユーザーはインクルード層にランディング。アップグレード可能。キャンセルされた有料ユーザーは期間終了時にインクルード層に戻る。
   * **Paid only** — Stripe必須。インクルード層なし。ユーザーはモデルを呼び出す前にサブスクライブする必要がある。Included + paidから切り替える場合、既存のインクルード層ユーザーはクォータを保持。
4. バックエンドは、Stripeポスチャーを選択する際に `STRIPE_SECRET_KEY` と `STRIPE_WEBHOOK_SECRET` の両方が存在することを検証します — どちらかが欠落している場合は400を返す
5. Stripeポスチャーの初回有効化時、バックエンドは**べき等セットアップ**を実行:
   * Included + paid: インクルードプラン（`slug=free`）+ 空のPrice IDを持つProテンプレートをシード。`default_plan_id`を設定。プランのないユーザーをバックフィル
   * Paid only: Proテンプレートのみをシード。バックフィルなし
   * そのプランがシードされるときに `default_token_quota` をインクルードプランにコピー（`0`は`0`のまま、無制限）
6. **No subscriptions**に戻すことは**純粋なフラグ反転**で、データ副作用なし。Included + paidがアクティブな間、インクルードデフォルトプランは削除できない。

### 4. Pro プランをライブ価格で更新する

アクティベーション後、シードされた Pro プランをライブ Stripe 価格にポイントするように更新します：

* **Admin → Billing → Plans → Pro → Edit**
* ステップ 1 から取得した `price_1***` を `Stripe Price ID` に貼り付けます
* 保存します

または SQL 経由で（直接 DB アクセスを希望する場合）：

```sql theme={null}
UPDATE billing_plans
SET stripe_price_id = 'price_1***'
WHERE slug = 'pro';
```

### 5. スモークテスト

1. 通常ユーザーとして `/settings?tab=billing` を開く
2. **Switch to Pro** をクリック
3. Stripe Checkout が開く。少額の実カードで完了する（その後払い戻す）
4. Webhook が発火する — Stripe Dashboard → Webhooks → 最近のイベントで 2xx レスポンスが表示されることを確認
5. `subscriptions` テーブルにサブスクリプション行が表示される。`users.plan_id` が `pro` に切り替わる
6. UI に Pro プランと「Manage subscription」ボタンが表示される

## 請求の無効化

Admin → System Settings → Billing で **Enable Stripe Billing** スイッチを OFF に切り替えます。

無効化すると：

* すべての `/api/billing/*` エンドポイントが 503 を返す
* webhook エンドポイントが 503 を返す（Stripe は再試行し、Dashboard に失敗として表示されます — これは問題ありません。請求が永続的に無効な場合は、Stripe Dashboard で webhook を無効にすることもできます）
* `Plan & Billing` ユーザー向けタブが消える
* Admin → Billing ナビゲーショングループが非表示になる
* クォータチェーンがプランティアをスキップし、`default_token_quota` に直接フォールバックする

**データは保持されます**：既存の `subscriptions`、`billing_plans`、`users.plan_id` 行は変更されません。再度有効化すると、マイグレーションなしで同じ状態から再開されます。

## 計算リファレンス — クォータ & トークン計算

これは、ユーザーが消費できる内容、カウンターがリセットされるタイミング、および解決チェーンがどのように構成されるかを決定するすべての数値ルールの信頼できるリファレンスです。価格設定の変更、クォータの調整、使用状況ダッシュボードの構築、またはv2/v3作業の計画を行う前に、これをお読みください。まだ出荷されていない将来のルールは、予約されたスロットに記載されているため、貢献者は新しいロジックがどこに組み込まれるかを知ることができます。

### 用語集

| 変数                                    | ストレージ           | セマンティクス                                           | 範囲                    |
| ------------------------------------- | --------------- | ------------------------------------------------- | --------------------- |
| `users.token_quota`                   | ユーザーごと（オーバーライド） | 3状態オーバーライド。以下のセマンティクスを参照                          | `NULL`、`0`、または正の整数    |
| `users.tokens_used_this_period`       | ユーザーごと（カウンター）   | 最後のリセット以降の累積トークン                                  | 非負の整数                 |
| `users.quota_reset_at`                | ユーザーごと（アンカー）    | 有料ユーザーの `Subscription.current_period_end` をミラーリング | タイムスタンプ               |
| `users.plan_id`                       | ユーザーごと（FK）      | アクティブなプラン                                         | FK `billing_plans.id` |
| `billing_plans.monthly_token_quota`   | プランごと           | このプランのユーザーのハードキャップ                                | 非負の整数                 |
| `system_settings.default_token_quota` | シングルトン          | プランが適用されない場合の防御的フォールバック                           | 非負の整数                 |
| `system_settings.default_plan_id`     | シングルトン          | 新規/未割り当てユーザーの無料プランポインター                           | FK または `NULL`         |
| `system_settings.billing_enabled`     | シングルトン          | マスタースイッチ——チェーンのステップ2をゲート                          | ブール値                  |

### トークンとしてカウントされるもの

トークン消費はLLMコール層で記録され、すべての完了時にLiteLLMの`usage`オブジェクトから取得されます。

* **カウント対象**: すべてのモデルコールにおけるプロンプトトークン + 完了トークン
* **カウント対象**: マルチステップ/ツール使用エージェントフロー内のすべてのラウンドトリップ（各モデルコールは個別の請求）
* **カウント対象**: 埋め込みリクエスト（KB取り込み、検索スコアリング）
* **カウント対象外**: モデルに送信されないがステージングされた入力（ユーザーが破棄したアップロードファイルなど）
* **カウント対象外**: プロバイダーに到達する前に失敗するリクエスト（認証エラー、レート制限事前チェック）
* **キャッシュされた入力**: v1では全額でカウント（プロバイダーキャッシュ割引は表示されません）。v2ではキャッシュされたプロンプトトークンを別途クレジットする可能性があります。

### Three-state override semantics

`users.token_quota` はユーザーごとの管理者オーバーライドです。1つのカラムに3つの意味を持ちます：

| Value   | Meaning              | Use case                                            |
| ------- | -------------------- | --------------------------------------------------- |
| `NULL`  | 未設定 — プランまたはデフォルトに従う | すべての通常ユーザーのデフォルト状態                                  |
| `0`     | 無制限                  | 管理者 / 内部アカウント；「VIP ギフト」                             |
| `N > 0` | `N` でハードキャップ         | 不正使用者をブロック（有料サブスクリプションはキャンセルしない）；プリペイド型エンタープライズ割り当て |

オーバーライドは常にプランとデフォルトより優先されます。管理者が Stripe に触れることなく、個別ユーザーをプラン階層の上下に固定できるよう存在します。

### クォータ解決チェーン — v1（現在）

認証されたリクエストの場合、上限は上から下へ計算されます——**最初にマッチしたものが優先**されます：

```
1. users.token_quota        ── NULL? skip. 0? unlimited. N>0? cap at N.
2. users.plan.monthly_token_quota   ── only when access_model is freemium or paid_only
3. paid_only + no plan      ── unentitled (stop; do not fall through)
4. system_settings.default_token_quota  ── used when access_model is off
5. unlimited                ── last resort if everything above is unset
```

ステップ4は**サブスクリプションなし**の場合のインスタンス全体の上限です。**有料のみ**の場合、プランのないユーザーは`default_token_quota`が0であっても権限がなく（チャットは402を返します）。

### 期間リセット

* 有料ユーザーの場合、`quota_reset_at` は `Subscription.current_period_end` と同期します。`invoice.payment_succeeded` ウェブフック ハンドラーは、各更新成功時に `tokens_used_this_period = 0` を設定し、`quota_reset_at` を新しい期間終了日に進めます。
* 無料ユーザー（Stripe サブスクリプションなし）の場合、時間単位の cron がプラン割り当て日を基準とした暦月の境界で `tokens_used_this_period` を 0 にリセットします。
* 期間中のプラン変更はカウンターをリセット**しません**——更新時のみリセットされます。これにより、クォータ サイクリング エクスプロイト（「サブスクライブ → Pro クォータを使用 → キャンセル → 再度サブスクライブ」）を防ぎます。

### ミッドストリーム強制

* チャット呼び出しエントリでのプリフライトチェック：最も安価なパス、ユーザーが開始できない要求をブロックします。
* ストリーミング中、実行中のトークンカウントはチャンクごとに再評価されます。上限を超えると、ネットワークエラーではなく**構造化ターミネータフレーム**でストリームを閉じます。
* フロントエンドはターミネータを解釈し、`/settings?tab=billing`へのディープリンク付きで`<QuotaExceededDialog>`を表示します。
* ストリーミング以外のレスポンスはHTTP `402`を返し、本文は`{ code: "QUOTA_EXCEEDED", reset_at, upgrade_url }`です。

### 請求機能無効時のフォールバック

`system_settings.billing_enabled = FALSE` の場合：

* チェーンのステップ 2 がスキップされます — チェーンは `override → default → unlimited` に縮小されます。
* `/api/billing/*` と `/api/webhooks/stripe` は `503` を返します。
* `Plan & Billing` ユーザータブと Admin → Billing ナビゲーショングループは非表示になります。
* すべての請求データ（サブスクリプション、プラン、`users.plan_id`）は保持されます — 再度有効にすると、マイグレーションなしで同じ状態から再開されます。

### 予約済み: クォータチェーン v2 — Team シート

<Note>まだリリースされていません。v2 作業の既知のランディングスポットとしてここに記載されています。</Note>

Team プランがリリースされる場合：

* `Subscription.quantity` はシート数を保持します（Stripe ネイティブ）。
* ユーザーの有効なプランは、個人プランにフォールバックする前に Team メンバーシップを通じて解決されます：
  ```
  effective_plan = team.plan if team_member(user) else user.plan
  ```
* クォータは**シートごと**です（各 Team メンバーは完全な `monthly_token_quota` を取得します）。プール化されたバケットではありません。プール化されたバケットは先着順の枯渇を引き起こし、顧客にとって悪いものです。
* オーバーライドセマンティクスは変わりません — Team 管理者は `users.token_quota = N` を使用して個別メンバーをハードキャップできます。これはチームプランチェーンの上に位置します。

### 予約済み: クォータチェーン v3 — ネイティブ Org アロケーション (Stripe なし)

<Note>まだリリースされていません。Stripe を通じてユーザーごとに支払うことなく、内部的にクォータを割り当てるオンプレミス / エンタープライズデプロイメント向けに予約されています。</Note>

* 新しいテーブル `org_quota_allocations(user_id, monthly_token_quota, org_id)` は親予算をメンバー全体に配分します。
* アロケーションは**共有プール ではなく、ユーザーごと** — すべてのメンバーが明確な個別 SLA を持ちます。
* 更新されたチェーン:
  ```
  override → max(plan_quota, org_allocation) → default → unlimited
  ```
* `max()`、`sum()` ではありません。有料 Pro ユーザーは、Org 管理者が低いアロケーションを設定した場合でも、支払った額より少なくなることはありません。Stripe で支払われたクォータは神聖です。

### 予約済み：従量課金クレジット残高（v3 別次元）

<Note>未出荷。上記のチェーンとは別の軸——クレジットは1回限りのトップアップであり、サブスクリプション階層ではありません。</Note>

* 新しいテーブル `user_credits(user_id, balance_cents, currency)` — Stripe Checkout `mode='payment'` 経由で資金提供。
* 消費順序：**サブスクリプション割当量優先**、その後クレジット残高（サブスクリプションが枯渇した後のみクレジット減少が開始）。
* クレジット残高は払い戻し不可（プリペイドの業界標準）。
* UI は両方のバーを公開：`Subscription quota: 4.2M / 5M used` + `Credits: $7.40 remaining`。

### デフォルト値

出荷時のデフォルト — すべてインストール後に調整可能（記載がある場合を除く）。

| 変数                                                 | デフォルト                                  | 調整方法                                                  |
| -------------------------------------------------- | -------------------------------------- | ----------------------------------------------------- |
| `system_settings.access_model`                     | `off`                                  | Admin → System Settings → Access and billing          |
| `billing_plans.monthly_token_quota` (included)     | `default_token_quota` からコピー（`0` = 無制限） | Admin → System Settings → Default Monthly Token Quota |
| `billing_plans.monthly_token_quota` (Pro template) | `5,000,000`                            | Admin → Billing → Plans → Pro → Edit                  |
| `system_settings.default_token_quota`              | `0`（無制限；フリーミアムの included プラン下で同期）      | Admin → System Settings → Default Monthly Token Quota |
| `system_settings.billing_enabled`                  | `FALSE`（`access_model != off` から導出）    | Admin → System Settings → Access and billing          |
| Pro list price                                     | `$20.00 USD / month`                   | Stripe Dashboard (price object)                       |
| Stripe webhook events subscribed                   | 6                                      | Stripe Dashboard → Webhooks                           |
| Stripe price cache TTL                             | `5 minutes`                            | `stripe_client.py` にハードコード                            |
| Subscription lifecycle cron                        | 毎時                                     | `web/main.py` の APScheduler                           |
| Free-tier reset cron                               | 毎時（暦月の境界）                              | `web/main.py` の APScheduler                           |

## 料金モデル

V1はフラットサブスクリプションです。Free + Pro、月次請求、USD のみ。

**V1 の対象外** (意図的に、ロードマップに延期):

* Team プラン (Stripe seats / `subscription.quantity`)
* 年間請求
* 複数通貨の表示
* クーポン / プロモコード
* 税務処理 (Stripe Tax 統合 — 別途コンプライアンスレビューが必要)
* 使用量ベースのメータリング / 超過料金
* 従量課金クレジット残高 (ワンタイムトップアップ)

次に予定されている内容については、[ロードマップ](/roadmap)を参照してください。

## トラブルシューティング

<Accordion title="トグルが有効化されない — 400 エラー">
  有効化エンドポイントには `STRIPE_SECRET_KEY` と `STRIPE_WEBHOOK_SECRET` の両方が設定されている必要があります。`.env` に存在することを確認し、編集後にバックエンドが再起動されていることを確認してください。
</Accordion>

<Accordion title="Webhook が 503 を返す">
  課金が無効化されているか（トグルが OFF）、またはリクエスト署名の検証に失敗しています（`STRIPE_WEBHOOK_SECRET` が一致していない）。Stripe ダッシュボード → Webhooks → 最近のイベントで実際のエラー本文を確認してください。
</Accordion>

<Accordion title="ユーザーがサブスクライブしたが、まだ Free プランが表示される">
  `checkout.session.completed` webhook がバックエンドに到達しませんでした。Stripe ダッシュボードのエンドポイント URL が `<your-domain>/api/webhooks/stripe` と完全に一致していることを確認してください（末尾のパスを含む）。Webhook の最近の配信でエラーを確認してください。
</Accordion>

<Accordion title="Pro ユーザーがテストモードの価格 ID を見ている">
  シード マイグレーションはテストモードの価格 ID を書き込みます。本番課金を有効化した後、Admin → Billing → Plans → Pro → Edit で Pro プランを更新して、本番環境の `price_1***` を使用するか、直接 SQL UPDATE で更新してください。
</Accordion>

<Accordion title="領収書が Stripe のデフォルト ブランディングで表示される">
  Stripe ダッシュボード → Settings → Branding でビジネス ブランディングを設定してください。ロゴ、ビジネス名（例：「FIM Labs Pte. Ltd.」）、住所を追加してください。Stripe はこれらを自動生成されたすべての領収書と請求書に適用します。
</Accordion>

<Accordion title="支払い時の為替損失">
  Stripe アカウントのデフォルト通貨が請求通貨と異なる場合、Stripe は支払いのたびに変換します（1.5～2% のスプレッド）。Settings → Bank accounts and currencies で一致する決済通貨を追加し、同じ通貨の銀行口座を接続すると、Stripe は変換なしで同じ通貨の支払いをルーティングします。
</Accordion>
