AI 웹 앱에 Stripe 구독 추가하기

Stripe 구독(월간·연간)을 추가해 AI 웹 앱으로 수익을 냅니다.

로드맵과 자료

준비물 (시작하기 전에)

사전 준비 체크리스트

시작하기 전에 아래 항목이 준비되어 있는지 확인하세요.

이 Stripe 튜토리얼을 시작하기 전에, 웹 앱에 다음이 준비되어 있는지 확인하세요.

1. 앱이 이미 배포되어 있어야 합니다 (실제 URL 필요)

Stripe Checkout은 실제로 배포된 사이트에서만 작동합니다. Google AI Studio 미리보기 환경에서는 작동하지 않습니다.

배포 영상

배포 튜토리얼 보기

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

---

2. Firebase 설정이 모두 끝나 있어야 합니다 (영상 하나로 전부 설명합니다)

Firebase 설정이 미리 완료되어 있어야 합니다. 구체적으로는 다음과 같습니다. - Firebase Authentication 활성화 - Firestore Database 생성 - 웹 앱이 (본인의 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에서 웹훅 만들기 (필요한 이벤트 선택)

- 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 리디렉션용으로 간단한 라우트/페이지 두 개를 추가해 주세요…

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 보안 규칙은 예시입니다. 내 웹 앱의 데이터 구조에 맞게 반드시 검토하고 수정해야 합니다. 앱마다 컬렉션과 데이터 모델에 맞는 규칙이 필요합니다.

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 맞춤 설정에 따라 크게 달라지므로 여기서는 구체적인 프롬프트를 제공하지 않습니다.