AI WebアプリにStripeのサブスクリプションを追加する
Stripeのサブスクリプション(月額・年額)を追加して、AI Webアプリを収益化します。
ロードマップと資料
必要なもの(始める前に)
事前準備チェックリスト
始める前に、これらが準備できているか確認してください。
このStripeチュートリアルを始める前に、Webアプリで次の準備ができていることを確認してください。
1. アプリがすでにデプロイ済みであること(公開URLが必要)
Stripe Checkoutは実際にデプロイしたサイトでのみ動作します。 Google AI Studioのプレビュー環境では動作しません。
デプロイ解説動画
デプロイのチュートリアルを見る
https://youtu.be/HGs-4QjyjHQ?si=EBUOpPx38n9vVROR
---
2. Firebaseの設定が完了していること(1本の動画ですべて解説しています)
Firebaseの設定が事前に完了している必要があります。具体的には次のとおりです。 - Firebase Authenticationが有効になっている - Firestore Databaseが作成されている - Webアプリが(ご自身のFirebase設定で)Firebaseに接続されている
Firebase設定の解説動画
Firebaseのチュートリアルを見る
https://youtu.be/juEfJERgBE8?si=je5VF4gKMUJXl8kX
---
✔ これらの準備が整えば、 ステップ1からステップ5まで問題なく進められます。
月額プランと年額プランを作成する
Stripeを開いてTest Modeに切り替える
月額の継続課金プランを作成する
年額の継続課金プランを作成する
あとで使うStripeの値を保存する
Stripe決済をアプリに連携する
Firebase → Extensions を開く
「Run Payments with Stripe」をインストールする(提供:Invertase)
Stripeのシークレットキーを拡張機能の設定に貼り付ける
StripeでWebhookを作成する(必要なイベントを選択)
- checkout.session.completed
- customer.subscription.created
- invoice.paid
- invoice.payment_failed
- customer.subscription.updated
- customer.subscription.deleted
- invoice.upcoming
- payment_intent.succeeded
- payment_intent.payment_failed
- customer.subscription.paused
- charge.refunded
- charge.dispute.created
Firebase FunctionsからWebhook Endpoint URLをコピーし、Stripeに貼り付ける
StripeのSigning Secretをコピーし、Firebaseに貼り付ける(Webhook Secret)
保存してすべて連携できているか確認する
決済の成功・キャンセルページを設定する
Google AI Studioでプロジェクトを開く
プロンプト#1を実行:請求設定を作成する(デプロイ済みURL+price ID)
プロンプト:請求設定ファイルを作成する
請求設定ファイルを作成し、実際の値を入れてください…
Create a billing config file and fill it with my real values (no placeholders). Use EXACTLY: DEPLOYED_URL = "https://deployment-url.com" MONTHLY_PRICE_ID = "price_MONTHLY_HERE" YEARLY_PRICE_ID = "price_YEARLY_HERE" Create: src/billingConfig.js (or .ts if the project is TypeScript) and export: - DEPLOYED_URL - MONTHLY_PRICE_ID - YEARLY_PRICE_ID - A function successUrl() that returns the correct URL for /success - A function cancelUrl() that returns the correct URL for /failure Important: - Detect if the app uses hash routing automatically: If existing routes contain "/#/" then use DEPLOYED_URL + "/#/success" and "/#/failure" Otherwise use DEPLOYED_URL + "/success" and "/failure" Do not change any existing UI or app logic. Return only the new file content.
プロンプト#2を実行:SuccessとCancelのページを作成する
プロンプト:成功・キャンセルページを作成する
Stripeのリダイレクト用に、シンプルなルート/ページを2つ追加してください…
Add two simple routes/pages for Stripe redirects: success and failure. Success page: - Text: "Payment successful" - Redirect to dashboard after 1.5 seconds Failure page: - Text: "Payment cancelled" - Redirect to dashboard after 1.5 seconds Use the same routing style already used in the project (hash or normal). Do not redesign UI. Return exact files changed.
プロンプトを実行して、ファイルやページが追加されたか確認する
アップグレードボタンからStripe Checkoutが開くようにする
(任意)アップグレードボタンを作成する
プロンプト#3を実行:月額/年額の選択モーダルを追加する
プロンプト:アップグレード選択UIを追加する
アプリを作り直さずに、シンプルなアップグレード選択UIを追加してください…
Add a simple upgrade choice UI without redesigning the app. When the user clicks the existing Upgrade button: - Show a small modal (or dropdown) with 2 options: 1) Monthly 2) Yearly - Add a close button. Keep current styling. Do not change any other UI. Return exact files changed.
プロンプト#4を実行:選択内容をStripe Checkoutに接続する
プロンプト:Stripe Checkoutを接続する
Firebase Stripe Extensionを使ってStripeのサブスク決済を実装してください…
Implement Stripe subscription checkout using Firebase Stripe Extension. On Monthly selection: - Create a Firestore doc at: customers/{uid}/checkout_sessions/{autoId} - Fields: price: MONTHLY_PRICE_ID (import from billingConfig) mode: "subscription" success_url: successUrl() (import from billingConfig) cancel_url: cancelUrl() (import from billingConfig) On Yearly selection: - Same but price: YEARLY_PRICE_ID After creating the doc: - Listen to that same checkout session document until it contains a field "url" - Redirect the browser to that url Do not add Stripe secret keys to frontend. Do not touch existing CRUD features. Return exact files changed. Test payments using your deployed live URL (not the preview)
デプロイした公開URLで決済をテストする
プロンプト#5を実行:アカウントをFree → Proに自動更新する
プロンプト:Proプランの判定
Firestoreから読み取るProプラン判定(読み取り専用)を追加する…
Add Pro plan detection (read-only) from Firestore. - Listen in real-time (onSnapshot) to: customers/{uid}/subscriptions - Set isPro = true if any subscription status is "active" or "trialing" - If isPro true: - Show a "Pro" badge - Hide the Upgrade button - If isPro false: - Show a "Free" badge - Show the Upgrade button Important: Do NOT block Free users from using the app features. Do not redesign UI. Return exact files changed.
プロンプト#6を実行:Manage Subscriptionを追加してStripe Billing Portalに接続する
プロンプト:サブスク管理ボタン
タスク:PROユーザー向けに、実際に動く「Manage Subscription」ボタンを追加してください…
Task: Add a working "Manage Subscription" button for PRO users using Stripe Customer Portal (Firebase Stripe Extension). Context: - The app already uses the Firebase Stripe Extension (customers/{uid} and checkout_sessions work). - The app uses hash routing (/#/dashboard). - Do NOT change the UI design. Only add logic. - The button must work in production (no CORS errors). Requirements: 1) Show "Manage Subscription" button ONLY when the current user is PRO (isPro === true). 2) On click, DO NOT call any cloudfunctions.net URL directly and DO NOT use fetch to Cloud Functions. 3) Instead, create a Firestore document at: customers/{uid}/portal_sessions/{autoId} with fields: - return_url: "https://vloma.com/#/dashboard" 4) After creating the doc, listen in real-time to that same document until the extension writes a field: - url Then redirect the browser to that url (window.location.href = url). 5) Add basic UX: - disable the button while loading - show a friendly alert if no url appears after 20 seconds - log the real error to console Implementation notes: - Use the existing Firebase client (auth + firestore). - Keep existing files structure. Modify only the minimal files needed. - Return the exact files changed and the exact code changes. Deliverable: - A working Manage Subscription button that opens Stripe Customer Portal and returns to /#/dashboard.
FreeプランとProプランの制限を追加する(実践的なSaaS構成)
プロンプト#7を実行:FreeとProの上限を追加する
プロンプト:利用上限を追加する
タスク:既存の課金・アップグレードのしくみには一切手を加えずに、Free/Proの利用上限を追加してください…
Task: Add Free/Pro usage limits WITHOUT touching or changing the existing billing/upgrade system. CRITICAL RULES (must follow): - DO NOT modify the existing Stripe checkout / upgrade code. - DO NOT modify how "isPro" is determined in the project. - DO NOT create or use users/{uid}.plan field (do not add plan to Firestore at all). - DO NOT change Firestore rules or Storage rules. - Keep UI the same (only small toast/alert/modal allowed). Limits: - FREE: max 5 files, max 5 folders, max 100MB total stored - PRO: max 50 files, max 50 folders, max 5GB total stored How to implement (safe approach): 1) Use the CURRENT existing "isPro" boolean/state already working in the app. - Whatever source it currently uses (Stripe Extension subscriptions / custom claims / existing code), keep it exactly as-is. - Only READ isPro, do not re-implement it. 2) Track usage from the existing Firestore structure (do not rename collections): - Files count = number of docs in users/{uid}/files - Folders count = number of docs in users/{uid}/folders - Total storage used = sum of file sizes from the file docs (field: size bytes). If old docs don’t have size, treat missing size as 0, and ensure new uploads always write size. 3) Enforce limits ONLY in the UI/Client for now: - Before creating a new file doc or uploading: check limits using current isPro. - Before creating a new folder doc: check limits using current isPro. - If Free exceeds limit: block action and show: “Free plan limit reached. Upgrade to Pro to continue.” Add an “Upgrade” button that triggers the EXISTING checkout flow (call the same function already used in the app). 4) IMPORTANT: After checkout completes, the app MUST refresh/reflect Pro automatically: - Do not add new plan logic. - Just ensure the same existing listener/state updates isPro and the UI re-checks limits. - Add a small debug log (console.log) showing current isPro value after returning from checkout. Deliverable: - Limits work and show upgrade prompt. - Paying and upgrading still changes the app to Pro exactly like before. - Return exact files changed.
Firestoreのルールを追加する(ユーザーは自分のデータだけにアクセスできる)
重要:このFirestoreセキュリティルールはあくまで一例です。ご自身のWebアプリのデータ構造に合わせて、必ず見直して調整してください。アプリごとに、そのコレクションとデータモデルに合ったルールが必要です。
firestore.rules
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function signedIn() { return request.auth != null; } function isOwner(uid) { return signedIn() && request.auth.uid == uid; } // ------------------------- // Users data (your app data) // ------------------------- match /users/{uid} { allow read, write: if isOwner(uid); // Everything under this user is also only for the owner match /{allPaths=**} { allow read, write: if isOwner(uid); } } // ------------------------- // Stripe Extension // customers/{uid}/... // ------------------------- match /customers/{uid} { // user can read their stripe docs allow read: if isOwner(uid); // user must NOT write to stripe docs directly allow write: if false; // user can create a checkout session doc match /checkout_sessions/{id} { allow create, read: if isOwner(uid); allow update, delete: if false; } // user can create a portal session doc match /portal_sessions/{id} { allow create, read: if isOwner(uid); allow update, delete: if false; } // subscriptions/payments are server-controlled (read only) match /subscriptions/{id} { allow read: if isOwner(uid); allow write: if false; } match /payments/{id} { allow read: if isOwner(uid); allow write: if false; } } } }
Storageのルールを追加する(ユーザーは自分のファイルだけにアクセスできる)
重要:このStorageセキュリティルールはあくまで一例です。ご自身のプロジェクトのパス(例:uploadsフォルダ名)や上限に合わせて、必ず見直して調整してください。アプリごとに、その構成に合ったstorageルールが必要です。
storage.rules
rules_version = '2'; service firebase.storage { match /b/{bucket}/o { function signedIn() { return request.auth != null; } function isOwner(uid) { return signedIn() && request.auth.uid == uid; } match /user_uploads/{uid}/{allPaths=**} { allow read: if isOwner(uid); // write create/update/delete // request.resource null delete، allow write: if isOwner(uid) && (request.resource == null || request.resource.size <= 200 * 1024 * 1024); } } }
(任意)UIの細かい修正のために簡単なプロンプトを実行する(例:モーダル内の価格を更新する)
あとから追加のプロンプトを実行して、UIの細部(モーダル内の価格の更新など)を整えることもできますが、これはご自身のUIのカスタマイズ内容に大きく左右されるため、ここでは具体的なプロンプトは用意していません。