Ajoute des abonnements Stripe à ton app web d'IA

Monétise ton app web d'IA en ajoutant des abonnements Stripe (mensuel et annuel).

Feuille de route et ressources

Prérequis (avant de commencer)

Checklist des prérequis

Assure-toi d'avoir ces éléments prêts avant de commencer.

Avant de suivre ce tutoriel Stripe, assure-toi que ton app web a déjà les éléments suivants prêts :

1. Ton app est déjà déployée (URL en production requise)

Stripe Checkout ne fonctionne que sur un vrai site web déployé — il ne fonctionne PAS dans l'environnement d'aperçu de Google AI Studio.

Vidéo de déploiement

Regarder le tutoriel de déploiement

https://youtu.be/HGs-4QjyjHQ?si=EBUOpPx38n9vVROR

---

2. Firebase est entièrement configuré (une seule vidéo couvre tout)

Ta configuration Firebase doit déjà être complète, incluant : - Firebase Authentication activé - Firestore Database créée - Ton app web connectée à Firebase (en utilisant ta configuration Firebase)

Vidéo de configuration de Firebase

Regarder le tutoriel Firebase

https://youtu.be/juEfJERgBE8?si=je5VF4gKMUJXl8kX

---

✔ Une fois ces prérequis prêts, tu peux continuer avec l'Étape 1 → Étape 5 sans problème.

Créer des plans d'abonnement mensuel et annuel

Ouvre Stripe et passe en Test Mode

Crée un plan d'abonnement récurrent mensuel

Crée un plan d'abonnement récurrent annuel

Sauvegarde les valeurs Stripe requises pour une utilisation ultérieure

Connecter les paiements Stripe à ton app

Ouvrir Firebase → Extensions

Installe « Run Payments with Stripe » (par Invertase)

Colle ta Stripe Secret Key dans les paramètres de l'extension

Crée un webhook dans Stripe (sélectionne les événements requis)

- 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

Copie la Webhook Endpoint URL depuis Firebase Functions et colle-la dans Stripe

Copie le Signing Secret de Stripe et colle-le dans Firebase (Webhook Secret)

Sauvegarde et confirme que tout est connecté

Configurer les pages de succès et d'annulation de paiement

Ouvre ton projet dans Google AI Studio

Lance le Prompt #1 : créer la configuration de facturation (URL déployée + Price IDs)

Prompt : créer la configuration de facturation

Crée un fichier de configuration de facturation et remplis-le avec mes vraies valeurs…

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.

Lance le Prompt #2 : créer les pages de succès + annulation

Prompt : créer les pages de succès et d'annulation

Ajoute deux routes/pages simples pour les redirections Stripe…

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.

Lance les prompts et confirme que les fichiers/pages ont été ajoutés

Fais en sorte que le bouton Upgrade ouvre Stripe Checkout

(Optionnel) Crée un bouton de mise à niveau

Lance le Prompt #3 : ajouter la modale de choix mensuel/annuel

Prompt : ajouter l'UI de choix de mise à niveau

Ajoute une UI simple de choix de mise à niveau sans repenser l'app…

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.

Lance le Prompt #4 : connecter le choix à Stripe Checkout

Prompt : connecter Stripe Checkout

Implémente le checkout d'abonnement Stripe en utilisant la Firebase Stripe Extension…

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)

Teste les paiements en utilisant ton URL en production

Lance le Prompt #5 : mettre à jour automatiquement le compte de Free → Pro

Prompt : détection du plan Pro

Ajoute la détection du plan Pro (lecture seule) depuis Firestore…

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.

Lance le Prompt #6 : ajouter Manage Subscription et le connecter au Stripe Billing Portal

Prompt : bouton de gestion de l'abonnement

Tâche : ajouter un bouton « Manage Subscription » fonctionnel pour les utilisateurs PRO…

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.

Ajouter les limites Gratuit vs Pro (vraie configuration SaaS)

Lance le Prompt #7 : ajouter les limites Free vs Pro

Prompt : ajouter des limites d'usage

Tâche : ajouter des limites d'usage Free/Pro SANS toucher ni changer le système existant de facturation/mise à niveau…

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.

Ajouter les règles Firestore (les utilisateurs n'accèdent qu'à leurs propres données)

Important : ces règles de sécurité Firestore sont un exemple. Tu DOIS les revoir et les adapter pour correspondre à la structure de données de ta propre app web. Chaque app a besoin de règles qui correspondent à ses collections et à son modèle de données spécifiques.

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; } } } }

Ajouter les règles Storage (les utilisateurs n'accèdent qu'à leurs propres fichiers)

Important : ces règles de sécurité Storage sont un exemple. Tu DOIS les revoir et les adapter pour correspondre au chemin de ton propre projet (par exemple, le nom du dossier uploads) et à tes limites. Chaque app a besoin de règles de stockage qui correspondent à sa propre structure.

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); } } }

(Optionnel) Lance un prompt rapide pour de petits ajustements d'UI (par exemple, mettre à jour les prix dans la modale)

Tu peux lancer un prompt supplémentaire plus tard pour peaufiner des détails d'UI (comme mettre à jour les prix dans la modale), mais nous ne fournissons pas de prompt spécifique ici car cela dépend beaucoup de tes personnalisations d'UI spécifiques.