40_課金と利用権限
本章では、有料化(課金)と、それに基づく利用権限(無料/有料の機能差)の扱いを定義する。
決済の正は Stripe、サービス内で参照する運用上の正は D1 の user_flags.paid とする。
用語と前提
- paid: ユーザーが有料利用可能であることを示すフラグ(0/1)
- 決済の正(source of truth): Stripe
- 運用上の参照点(APIが参照する正): D1 の
user_flags.paid
本章は「課金のUI(Checkout/Portal)」や「会員基盤のUI(ログイン画面)」は扱わない。
バックエンド API が扱う「支払い状態の反映」と「権限判定」を対象とする。
目的
- Stripe の支払い結果を、サービス内の利用権限(paid)へ反映する
- paid によって API の利用可否/範囲を制御できる状態にする
- Webhook の再送や重複を前提に、冪等性を担保する
- 反映遅延・失敗時に、ユーザー体験を破綻させない運用を可能にする
データモデル(参照)
user_flagspaid INTEGER NOT NULL DEFAULT 0 CHECK (paid IN (0,1))updated_atはトリガーで自動更新
詳細は 30_データモデル を参照。
利用権限(無料/有料の機能差)
本サービスでは、ユーザーの paid 状態に応じて、機能の提供範囲を切り替える。
具体的な差分はフロントエンド側の導線にも依存するため、ここではバックエンド側で扱う最小限の原則を定義する。
原則
- 無料ユーザー: 無料範囲の API のみ利用可能
- 有料ユーザー(paid=1): 有料範囲の API を利用可能
典型的な制御ポイント(例)
- thread/run の開始回数や、Step2(日次セッション)の利用可否
- messages 取得の範囲(期間・件数)
- LLM 関連エンドポイント(検証用途)の利用制限
実際の制御対象を確定する際は、フロントエンドの導線と合わせて「どのAPIをゲートするか」を
20_API仕様に追記する。
paid 判定 API
GET /api/paid
- 目的: 指定ユーザーの paid 状態を返す
- 運用上の正: D1 の
user_flags.paid - 典型用途: フロントエンドが画面表示/導線分岐のために参照
レスポンス例:
{ "ok": true, "paid": true }
paid の更新経路
経路A: Stripe Webhook(本命の更新経路)
- Stripe のイベントを受け取り、署名検証後に
user_flags.paidを更新する。 - Webhook は 再送・重複 がある前提で、冪等性を必須要件とする。
経路B: 管理用途の手動更新(暫定/運用用途)
POST /api/admin/set_paidにより、管理用途でuser_flags.paidを更新する。- 本番運用では誤用防止のため、強い制限(管理者のみ、環境限定、IP制限等)を推奨。
Stripe Webhook 設計
実装の詳細(エンドポイントパス、署名ヘッダ名、利用ライブラリ等)は
50_セキュリティと整合させる。
ここでは「設計として満たすべき性質」を定義する。
対象イベント(例)
- 支払い完了: checkout/session 完了、subscription 作成/更新、invoice 支払い成功 等
- 失効: subscription キャンセル、支払い失敗、返金 等
どのイベントを採用するかは決済モデル(単発/サブスク)に依存するため、確定時に追記する。
処理フロー(概略)
sequenceDiagram
autonumber
participant Stripe as Stripe
participant API as Backend API
participant D1 as D1
Stripe->>API: Webhook(イベント)
API->>API: 署名検証
API->>API: 冪等性チェック(重複排除)
API->>D1: user_flags.paid 更新(0/1)
API-->>Stripe: 200 OK
冪等性(Webhook 再送・重複への対応)
Webhook は、ネットワーク都合や Stripe 側の仕様により 同一イベントが複数回届く。
そのため「同じイベントを何回処理しても結果が同じ」ことを担保する。
推奨戦略
- イベントID(Stripe event.id) を一意キーとして、処理済みを記録する
- すでに処理済みなら DB 更新を行わず 200 を返す
現状の DDL にはイベント記録テーブルが存在しないため、Webhook 実装時に追加する。
例:stripe_webhook_events(event_id TEXT PRIMARY KEY, received_at TEXT, ...)
最低限の妥協策(非推奨だが暫定で成立しうる)
- paid 更新自体が 0/1 の上書きであり、同一結果への更新であれば副作用が小さいため、
「結果が同じになる更新」を行うだけでも破綻しにくい。
ただし以下のリスクが残る。 - 失効/復活など複数イベントの順序問題
- 二重処理による外部副作用(メール送信等)を将来追加した際に破綻
失敗時の扱い(反映遅延・不整合)
反映遅延(支払い直後に paid がまだ 0)
起こり得る理由:
- Webhook 到達遅延
- 署名検証失敗や一時障害による処理遅延
- フロントエンドが先に /api/paid を叩いた
推奨の取り扱い: - フロント側は「支払い完了後、数秒〜数十秒で反映される可能性」を前提にする - 一定時間(例: 数十秒)リトライ、または「反映待ち」画面を用意する - それでも反映されない場合はサポート導線へ
不整合(Stripe は有料だが D1 が paid=0 のまま)
- Webhook を再送できる設計にしておく(Stripe ダッシュボードから再送)
- 管理用途の
admin/set_paidを最終手段として保持する(ただし強い制限をかける)
セキュリティ前提(本章の要点)
- Webhook は 署名検証が必須
admin/set_paidは強い制限が必須(管理者のみ、環境限定など)- paid の判定に用いる
user_idの真正性(なりすまし防止)は50_セキュリティで定義する
参照
20_API仕様:/api/paid,/api/admin/set_paidを含むエンドポイント契約30_データモデル:user_flagsの定義50_セキュリティ: Webhook 署名検証、認証、Secrets の扱い60_利用制限と運用ポリシー: 管理操作の取り扱い、悪用対応