# 帳票設計書 6-Billing Report (Web UI)

## 概要

本ドキュメントは、opencode Web Console上の課金情報（残高・支払い方法）表示レポートの設計書である。

### 本帳票の処理概要

本帳票は、ワークスペースの現在残高、登録された支払い方法、および残高追加機能を提供するWebコンポーネントである。Stripeとの連携により決済処理を行う。

**業務上の目的・背景**：ユーザーがワークスペースの課金状況を把握し、残高管理を行うために必要である。プリペイド残高制を採用しており、残高が不足するとAIサービスが利用できなくなるため、適切な残高管理が重要となる。Stripe連携により安全な決済処理を実現している。

**帳票の利用シーン**：残高確認時、残高追加時、支払い方法の確認・変更時に利用される。定期的な残高チェックや、サービス利用前の残高確認にも使用される。

**主要な出力内容**：
1. 現在残高（ドル表示）
2. 登録支払い方法（クレジットカード/Stripe Link）
3. 残高追加機能（金額入力・Stripeチェックアウト連携）
4. 支払い方法管理ボタン（Stripeカスタマーポータル連携）

**帳票の出力タイミング**：ワークスペース課金ページ（`/workspace/[id]/billing`）にアクセスした時点で自動的に表示される。

**帳票の利用者**：ワークスペース管理者、経理担当者

## 帳票種別

課金情報 / 残高表示

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| BL-001 | 課金管理 | `/workspace/[id]/billing` | ページアクセス時自動表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML（Webコンポーネント） |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | 該当なし |
| 出力方法 | ブラウザ表示 |
| 文字コード | UTF-8 |

## 帳票レイアウト

### レイアウト概要

残高表示と支払い方法を並べて表示し、残高追加フォームはオンデマンドで表示する。

```
┌─────────────────────────────────────────────────────────────────┐
│  Billing                                                        │
│  Need help? Contact us for assistance.                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────┐  ┌────────────────────────────────┐  │
│  │  $123.45            │  │ [Add Balance]                   │  │
│  │  Current balance    │  │                                 │  │
│  └─────────────────────┘  │  [Credit Card Icon]             │  │
│                           │  •••• 4242                       │  │
│                           │  [Manage]                        │  │
│                           └────────────────────────────────┘  │
│                                                                 │
│  ── 残高追加フォーム展開時 ──                                   │
│  ┌────────────────────────────────────────────────────────┐   │
│  │  Add $[____] to balance                                │   │
│  │           [Cancel] [Add Balance]                       │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ── 未登録状態 ──                                               │
│  [Enable Pay-As-You-Go Billing]                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### 残高表示部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | balance-value | 現在残高 | billingInfo.balance | $xxx.xx |
| 2 | balance-label | ラベル | 固定文字列 | "Current balance" |

### 支払い方法表示部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | card-icon | 支払い方法アイコン | paymentMethodType | カード/Stripeアイコン |
| 2 | card-number | カード番号（末尾4桁） | paymentMethodLast4 | •••• xxxx |
| 3 | type-label | 支払いタイプ | paymentMethodType | "Linked to Stripe" (Link時) |

### 残高追加フォーム部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | amount-input | 追加金額 | ユーザー入力 | 数値（ドル） |
| 2 | cancel-button | キャンセルボタン | - | "Cancel" |
| 3 | submit-button | 追加ボタン | - | "Add Balance" |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| workspaceID | ワークスペースID | Yes |

### 表示状態分岐

| 条件 | 表示内容 |
|-----|---------|
| customerID存在 | 残高・支払い方法・追加ボタン表示 |
| customerID未登録 | "Enable Pay-As-You-Go Billing"ボタンのみ |
| paymentMethodType === "card" | カードアイコン + 末尾4桁 |
| paymentMethodType === "link" | Stripeアイコン + "Linked to Stripe" |

## データベース参照仕様

### 参照データ（Billing API経由）

| 項目名 | 説明 | 備考 |
|-------|------|------|
| customerID | StripeカスタマーID | null時は未登録 |
| balance | 残高（1/100,000,000ドル単位） | - |
| paymentMethodType | 支払い方法タイプ | "card" / "link" / null |
| paymentMethodLast4 | カード末尾4桁 | card時のみ |
| reloadAmount | デフォルト追加金額 | - |
| reloadAmountMin | 最小追加金額 | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| displayBalance | (balance ?? 0) / 100000000 | toFixed(2) | ドル表示用 |
| negativeZeroFix | balance === "-0.00" ? "0.00" : balance | - | -0.00対策 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[コンポーネントマウント] --> B[createAsync: queryBillingInfo]
    B --> C[Billing.get取得]
    C --> D[storeに反映]
    D --> E{customerID存在?}
    E -->|Yes| F[残高・支払い方法表示]
    E -->|No| G["Enable Billing"ボタン表示]
    F --> H[ユーザー操作待機]
    H --> I{操作種別}
    I -->|Add Balance| J[フォーム表示]
    I -->|Manage| K[createSessionUrl]
    I -->|Enable| L[createCheckoutUrl]
    J --> M[金額入力]
    M --> N[createCheckoutUrl]
    K --> O[Stripe Portal遷移]
    L --> P[Stripe Checkout遷移]
    N --> P
```

### Stripe連携フロー

```mermaid
flowchart TD
    A[残高追加クリック] --> B[フォーム表示]
    B --> C[金額入力]
    C --> D[Add Balanceクリック]
    D --> E[createCheckoutUrl呼び出し]
    E --> F[Billing.generateCheckoutUrl]
    F --> G{成功?}
    G -->|Yes| H[Stripe Checkoutにリダイレクト]
    G -->|No| I[エラー表示]
    H --> J[Stripe決済処理]
    J --> K[successUrl/cancelUrlにリダイレクト]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| チェックアウトエラー | Stripe API失敗 | エラーメッセージ表示 | 再試行 |
| セッションエラー | Portal URL生成失敗 | エラーメッセージ表示 | 再試行 |
| 認証エラー | withActorで認証失敗 | - | ログイン画面へリダイレクト |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1レコード（billingInfo） |
| 目標出力時間 | 1秒以内 |
| 外部API呼び出し | Stripe API（リダイレクト時のみ） |

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

- withActorによるワークスペースアクセス権限確認
- カード番号は末尾4桁のみ表示（PCI DSS準拠）
- Stripe Checkoutへのリダイレクトで決済情報を直接扱わない
- Stripe Customer Portal経由での支払い方法管理

## 備考

- 残高が-0.00になる場合は0.00として表示
- 最小追加金額（reloadAmountMin）による入力制限
- ローディング状態の表示（pending / redirecting）
- i18nによる多言語対応（日本語/英語等）
- テスト用のダミーデータがコメントアウトで用意されている（88-138行）

---

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

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

### 推奨読解順序

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

まず、APIから取得されるBilling情報の構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | queryBillingInfo関数（93-120行）の戻り値を確認 |
| 1-2 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | formatBalance関数（36-39行）の実装を確認 |

**読解のコツ**: Billing.get()が返すオブジェクトの各プロパティの意味を理解する。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | billing-section.tsx | `packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx` | BillingSection関数コンポーネント（29-251行） |

**主要処理フロー**:
1. **31-43行**: 初期化（billingInfo, checkoutAction, sessionAction, store）
2. **51行**: balance計算（formatBalance使用）
3. **53-71行**: Stripe連携関数（onClickCheckout, onClickSession）
4. **140-249行**: JSXレンダリング

#### Step 3: 状態分岐を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | billing-section.tsx | `packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx` | customerID存在チェック（156行, 236行）、paymentMethodType分岐（204-221行） |

**主要処理フロー**:
- **156行**: `<Show when={billingInfo()?.customerID}>` で登録済み判定
- **204-221行**: `<Switch>` / `<Match>` でcard/link分岐

#### Step 4: Stripe連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | billing-section.tsx | `packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx` | createSessionUrl action（12-27行）、onClickCheckout/onClickSession（53-71行） |
| 4-2 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | createCheckoutUrl action（74-91行） |

**主要処理フロー**:
- **53-62行**: チェックアウトURL取得→リダイレクト
- **64-71行**: セッションURL取得→リダイレクト

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

```
BillingSection (billing-section.tsx:29)
    │
    ├─ createAsync(queryBillingInfo) - 課金情報取得
    │      └─ queryBillingInfo(workspaceID)
    │             └─ withActor() → Billing.get()
    │
    ├─ formatBalance(balance) - 残高フォーマット
    │      (common.tsx:36-39)
    │
    ├─ onClickCheckout() - 残高追加
    │      └─ checkoutAction(workspaceID, amount, baseUrl, baseUrl)
    │             └─ createCheckoutUrl action
    │                    └─ Billing.generateCheckoutUrl()
    │
    └─ onClickSession() - 支払い方法管理
           └─ sessionAction(workspaceID, returnUrl)
                  └─ createSessionUrl action
                         └─ Billing.generateSessionUrl()
```

### データフロー図

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

workspaceID ──────────▶ queryBillingInfo()
                              │
                              ▼
                    Billing.get() ───────▶ billingInfo
                              │               │
                              │      ┌────────┴────────┐
                              │      ▼                 ▼
                              │  customerID判定   formatBalance()
                              │      │                 │
                              │      ▼                 ▼
                              │  表示分岐        $xxx.xx
                              │      │
                    ┌─────────┴──────┴──────────┐
                    ▼                           ▼
              登録済み                       未登録
                    │                           │
        ┌───────────┼───────────┐              │
        ▼           ▼           ▼              ▼
     残高表示   支払い方法   追加ボタン    Enableボタン
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| billing-section.tsx | `packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx` | ソース | 課金情報コンポーネント |
| billing-section.module.css | `packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css` | スタイル | コンポーネントスタイル |
| common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | ソース | 共通ユーティリティ、queryBillingInfo |
| icon.tsx | `packages/console/app/src/component/icon.tsx` | ソース | IconCreditCard, IconStripe |
| auth.withActor.ts | `packages/console/app/src/context/auth.withActor.ts` | ソース | 認証ラッパー |
| i18n.ts | `packages/console/app/src/context/i18n.ts` | ソース | 国際化コンテキスト |
| billing.js | `@opencode-ai/console-core/billing.js` | ソース | Billing API |
