리소스 허브로

기술 문서 (프로토타입 버전)

Notion SaaS

저장소의 docs/reference/notion-saas.md 와 동일한 원문입니다. 아래에서 Markdown과 HTML 변환 결과를 각각 복사할 수 있습니다.

공개 문서 원문 (Markdown)

<!-- Notion Reference: 바이브코딩으로 SaaS 런칭하기 | 로그인·결제·배포 풀 튜토리얼 -->
<!-- 추출일: 2026-03-21 -->
<!-- 원본 ID: 32110791e8188192ae76ccedbdec0e81 -->

# 바이브코딩으로 SaaS 런칭하기 | 로그인·결제·배포 풀 튜토리얼


> 🚀 **이 문서는 유튜브 영상을 보고 혼자 따라하실 수 있도록 만든 상세 가이드입니다.**  
> 모든 단계를 디테일한 설명, 링크, AI 프롬프트와 함께 정리했습니다.


---


## 영상 개요


| 항목           | 내용                                                 |
| ------------ | -------------------------------------------------- |
| **핵심 메시지**   | "벽은 생각보다 낮다" — 시작, 인증, 결제 3가지 벽만 넘으면 누구나 서비스 런칭 가능 |
| **만들 서비스**   | Gemini 래퍼 SaaS (AI 채팅 + 로그인 + 결제 + 배포)             |
| **예상 소요 시간** | 약 60분                                              |


---


## 타임스탬프


| 파트     | 내용                                                 |
| ------ | -------------------------------------------------- |
| WALL 1 | 시작 (Antigravity + Next.js + GitHub)                |
| WALL 2 | 인증 (Supabase + Google Login)                       |
| 핵심 기능  | Gemini 래퍼 + UI + 대화 저장                             |
| WALL 3 | 결제 (Polar + Checkout + 배포 + Customer Portal + 테스트) |
| 구독 설계  | 3-tier 플랜 라이프사이클 구현                                |
| 테스트    | 구독 플로우 + UI 검증                                     |
| 암호화    | AES-256-GCM 유저 정보 보호                               |


---


## 사용 기술 스택 + 링크


| 역할           | 기술                                  | 링크                                                 |
| ------------ | ----------------------------------- | -------------------------------------------------- |
| IDE          | Antigravity (Gemini Agent 내장)       | [antigravity.google](http://antigravity.google/)   |
| 프레임워크        | Next.js + TypeScript + Tailwind CSS | [nextjs.org](http://nextjs.org/)                   |
| 인증 + DB      | Supabase                            | [supabase.com](http://supabase.com/)               |
| AI           | Gemini API (2.0 Flash)              | [aistudio.google.com](http://aistudio.google.com/) |
| 결제           | Polar                               | [polar.sh](http://polar.sh/)                       |
| 배포           | Vercel                              | [vercel.com](http://vercel.com/)                   |
| 버전 관리        | GitHub                              | [github.com](http://github.com/)                   |
| UI 레퍼런스      | [21st.dev](http://21st.dev/)        | [21st.dev](http://21st.dev/)                       |
| Node.js (필수) | Node.js LTS                         | [nodejs.org](http://nodejs.org/)                   |


---


# 🧱 WALL 1: 시작 [2:00 ~ 8:00]


> 💡 딱 3가지만 깔면 됩니다: **Antigravity + Next.js + GitHub**


---


## 1-1. Antigravity 설치 + 셋업


    > 📌 **Antigravity** = 구글이 만든 AI 코딩 에디터. VS Code 기반 + Gemini 에이전트 내장. 현재 **무료**.

    1. [antigravity.google/download](http://antigravity.google/download) 에서 본인 OS에 맞는 버전 다운로드
    2. 설치 후 첫 실행 → 셋업 화면
    3. **에이전트 정책 선택**: **"Review-driven development"** 선택 (AI가 코드 짜면 우리가 확인·승인)
    4. 구글 계정 로그인
    5. 왼쪽 Agent Manager 패널 확인 → 여기서 에이전트에게 채팅으로 작업 지시

---


## 1-2. Next.js 프로젝트 생성


    > 📌 **Next.js** = 웹사이트 만드는 도구. 요즘 웬만한 웹 서비스가 이걸로 만들어짐.


    Antigravity 터미널에서 아래 명령어 실행:


    ```bash
    npx create-next-app@latest my-gemini-wrapper
    ```

<details>
<summary>옵션 설정</summary>
- TypeScript → **Yes**
- Tailwind CSS → **Yes**
- App Router → **Yes**
- 나머지 → 기본값 (Enter)

</details>


    에이전트에게 보일러플레이트 정리 요청:


    > 🤖 **AI 프롬프트:** "보일러플레이트 정리해줘. 불필요한 기본 스타일이랑 예시 코드 지워줘."


    > ⚠️ **에러 대응:** `npx`가 안 된다고 뜨면 → Node.js가 없는 것. [nodejs.org](http://nodejs.org/)에서 LTS 버전 다운로드 후 터미널 재시작.


---


## 1-3. GitHub 연동


    > 📌 바이브 코딩에서 Git = **세이브 포인트**. AI가 코드를 망치면 되돌려야 하니까 반드시 해주세요.

    1. [github.com](http://github.com/)에서 새 레포지토리 생성
    2. 터미널에서 실행:

    ```bash
    git remote add origin [본인 레포 URL]
    git branch -M main
    git push -u origin main
    ```


---


## 1-4. 랜딩 페이지 기본 구조

    1. [21st.dev](http://21st.dev/)에서 맘에 드는 랜딩 페이지 레퍼런스 찾기
    2. 에이전트에게 요청:

    > 🤖 **AI 프롬프트:** "이 레퍼런스를 참고해서 히어로 섹션을 만들어줘. Gemini 래퍼 서비스야. 다크 테마로 해주고, 심플하게."

    1. 히어로 + 기본 구조만 잡고 넘어가기 (나중에 다듬을 예정)
    2. 커밋:

    ```bash
    git add . && git commit -m "feat: landing page hero section" && git push
    ```


    > 💡 **바이브 코딩 팁:** 처음부터 완벽하게 하려고 하면 끝이 없습니다. 일단 돌아가게 만들고, 나중에 예쁘게!


---


### ✅ WALL 1 체크리스트

    - [ ] Antigravity 설치 + 셋업
    - [ ] Next.js 프로젝트 생성
    - [ ] GitHub 레포 연동
    - [ ] 랜딩 페이지 히어로 섹션 만들기

---


# 🧱 WALL 2: 인증 [8:00 ~ 18:00]


> 💡 **Supabase + Google Login**으로 해결. 콘솔 설정 빼면 코드는 에이전트가 3분 만에 짬.


## Supabase + Google OAuth 연동 흐름


서로 주고받는 키와 URL 관계


```mermaid
graph LR
    subgraph GCC["Google Cloud Console"]
        direction TB
        G1["OAuth 동의 화면 설정"] --> G2["OAuth 2.0 Client ID 생성"]
        G2 --> G3["Client ID 발급"]
        G2 --> G4["Client Secret 발급"]
        G5["Authorized Redirect URI 등록"]
    end
    subgraph SB["Supabase Dashboard"]
        direction TB
        S1["Auth > Providers > Google"] --> S2["Client ID 입력"]
        S1 --> S3["Client Secret 입력"]
        S4["Redirect URL 제공 (복사용)"]
        S5["Site URL 설정"]
    end
    G3 -- "① Client ID 복사해서 전달" --> S2
    G4 -- "② Client Secret 복사해서 전달" --> S3
    S4 -- "③ Redirect URL 복사해서 등록" --> G5
```


> 🗺️ **흐름 요약:** 1️⃣ Google Console에서 Client ID/Secret 생성 → 2️⃣ Supabase Provider에 ID/Secret 붙여넣기 → 3️⃣ Supabase Redirect URL을 Google에 등록 → ✅ OAuth 로그인 연동 완료!


---


## 2-1. Supabase 프로젝트 생성


    > 📌 **Supabase** = 백엔드 통째로 제공 (인증 + DB + 스토리지). 무료 플랜 있음. 인증 붙이면서 DB도 같이 세팅됨.

    1. [supabase.com](http://supabase.com/) 가입 (GitHub 계정 가능)
    2. "New Project" → 프로젝트 이름 입력 → 리전 선택
    3. 프로젝트 Settings에서 **URL**과 **anon key (publishable key)** 복사해두기

---


## 2-2. Google Cloud Console OAuth 설정


    > 📌 "우리 앱에서 구글 로그인을 쓸 거야"라고 구글한테 허락받는 과정.

    1. [Google Cloud Console](https://console.cloud.google.com/) 접속
    2. 새 프로젝트 생성
    3. 왼쪽 메뉴: **API 및 서비스** → **사용자 인증 정보**
    4. **"+ 사용자 인증 정보 만들기"** → **"OAuth 클라이언트 ID"**
    5. 애플리케이션 유형: **"웹 애플리케이션"**
    6. 승인된 리디렉션 URI에 Supabase 콜백 URL 입력:

    `https://[프로젝트ID].`[`supabase.co/auth/v1/callback`](http://supabase.co/auth/v1/callback)

    1. **클라이언트 ID**와 **클라이언트 시크릿** 복사

    > ⚠️ **에러 대응:** "동의 화면을 구성해야 합니다" 메시지 → OAuth 동의 화면 설정에서 앱 이름 + 이메일만 입력. **테스트 모드**로 시작하고 나중에 프로덕션으로 변경.


---


## 2-3. Supabase에 Google Provider 연결

    1. Supabase 대시보드 → **Authentication** → **Providers**
    2. **Google** 찾아서 활성화 (Enable)
    3. 아까 복사한 **클라이언트 ID**와 **시크릿** 붙여넣기
    4. 저장

---


## 2-4. 코드 작업 - 에이전트에게 로그인 구현 시키기

<details>
<summary>환경변수 설정</summary>

프로젝트 루트에 `.env.local` 파일 생성:


```javascript
NEXT_PUBLIC_SUPABASE_URL=https://[프로젝트ID].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[anon key]
```


</details>

<details>
<summary>에이전트에게 구현 요청</summary>

> 🤖 **AI 프롬프트:**  
> "Supabase Google OAuth 로그인을 구현해줘. 다음 요구사항이야:  
> 1. 로그인 페이지에 구글 로그인 버튼  
> 2. AuthContext로 전역 로그인 상태 관리  
> 3. 미로그인 시 대시보드 접근 차단 (리다이렉트)  
> 4. 로그아웃 기능  
> Supabase URL이랑 anon key는 .env.local에 넣어둘게."


</details>

<details>
<summary>에이전트가 만들어주는 것들</summary>
- **로그인 페이지** — 구글 로그인 버튼
- **AuthContext** — 앱 전체에서 로그인 상태 확인
- **미들웨어** — 비로그인 사용자 리다이렉트
- **로그아웃** — 로그아웃 기능

</details>


    > ⚠️ **에러 대응:**  
    > - "Invalid API key" → `.env.local`에 키를 잘못 넣었거나 앞뒤 공백이 있는 경우  
    > - "redirect_uri_mismatch" → Google Cloud Console URI와 Supabase 설정이 다른 경우  
    > - **에러 메시지를 그대로 에이전트한테 붙여넣으세요.** 대부분 알아서 해결해 줍니다.


---


## 2-5. 로그인 테스트

    1. `npm run dev`로 로컬 서버 실행
    2. 로그인 페이지 → "구글로 로그인" 클릭
    3. 구글 계정 선택 → 대시보드 진입 확인
    4. 로그아웃 → 랜딩 페이지로 돌아가는지 확인
    5. 로그아웃 상태에서 대시보드 URL 직접 입력 → 로그인 페이지로 리다이렉트 확인
    6. 커밋:

    ```bash
    git add . && git commit -m "feat: google auth with supabase" && git push
    ```


---


## 2-6. Supabase MCP 연결 (선택사항, 강력 추천)


    > 📌 **MCP (Model Context Protocol)** = AI 에이전트가 Supabase에 직접 접근할 수 있게 해주는 연결 규격. 연결하면 에이전트가 DB 테이블 조회, SQL 실행, 로그 확인, TypeScript 타입 생성 등을 **자연어 명령**으로 해줌.


    > 💡 **왜 쓰나요?** MCP 없이는 에이전트한테 DB 구조를 일일이 설명해야 하지만, MCP를 연결하면 에이전트가 알아서 DB를 보고 코드를 짜줍니다. 특히 RLS 정책, 마이그레이션, 테이블 생성 작업이 훨씬 빨라짐.

<details>
<summary>Supabase MCP가 제공하는 기능</summary>

| 기능 그룹              | 설명                                      |
| ------------------ | --------------------------------------- |
| **Database**       | 테이블 목록 조회, SQL 쿼리 실행, 마이그레이션 관리         |
| **Debugging**      | 서비스 로그 확인, 보안/성능 어드바이저                  |
| **Development**    | 프로젝트 URL, API 키 조회, TypeScript 타입 자동 생성 |
| **Edge Functions** | 엣지 펑션 목록 조회, 배포                         |
| **Docs**           | Supabase 공식 문서 검색                       |


</details>

<details>
<summary>설정 방법 (Antigravity / Cursor 기준)</summary>
1. 프로젝트 루트에 `.cursor/mcp.json` 파일 생성 (Antigravity도 동일 경로):

```json
{
  "mcpServers": {
    "supabase": {
      "url": "https://mcp.supabase.com/mcp"
    }
  }
}
```

1. 에디터 재시작 → MCP 클라이언트가 브라우저 로그인을 요청
2. Supabase 계정으로 로그인하면 연결 완료
3. 에이전트에게 "내 Supabase 테이블 목록 보여줘" 같은 자연어로 테스트

</details>

<details>
<summary>추천 설정 옵션</summary>

URL 파라미터로 기능을 제한할 수 있습니다:


```javascript
https://mcp.supabase.com/mcp?read_only=true&project_ref=[프로젝트ID]
```

- `read_only=true` — 읽기 전용 (실수로 데이터 변경 방지)
- `project_ref=[ID]` — 특정 프로젝트만 접근 가능하게 제한

</details>


    > ⚠️ **보안 주의사항:**  
    > - **프로덕션 DB에는 직접 연결하지 마세요** — 개발/테스트 환경에서만 사용  
    > - 처음에는 `read_only=true`로 시작하는 것을 추천  
    > - MCP 클라이언트의 **수동 도구 승인(manual tool approval)** 설정을 켜두세요


    > 💡 **자세한 설정 가이드:** [supabase.com/docs/guides/getting-started/mcp](http://supabase.com/docs/guides/getting-started/mcp)


---


### ✅ WALL 2 체크리스트

    - [ ] Supabase 프로젝트 생성
    - [ ] Google Cloud Console에서 OAuth 클라이언트 설정
    - [ ] Supabase Auth에 Google Provider 연결
    - [ ] 로그인/로그아웃 UI 구현
    - [ ] 로그인 테스트 (Google 계정으로 확인)
    - [ ] (선택) Supabase MCP 연결

---


# ⚡ 핵심 기능: Gemini 래퍼 [18:00 ~ 32:00]


> ⚡ 서비스의 심장을 만드는 단계. **대시보드 UI + Gemini API 연동 + 대화 저장**


---


## 3-1. 대시보드 UI 만들기


    > 🤖 **AI 프롬프트:**  
    > "로그인한 사용자를 위한 대시보드를 만들어줘.  
    > - 왼쪽: 대화 히스토리 사이드바 (대화 목록, 새 대화 버튼)  
    > - 오른쪽: 채팅 인터페이스 (메시지 표시, 입력창)  
    > - 상단: 사용자 정보, 남은 크레딧 표시  
    > - 다크 테마, 미니멀하게."


    > 💡 **수정 필요하면 말로 하세요.** 예: "사이드바 폭 좀 줄여주고, 채팅 입력창을 좀 더 크게 만들어줘." 코드를 읽을 필요 없이 원하는 걸 말로 설명하면 AI가 수정해줍니다.


    > ⚠️ **에러 대응:** UI를 여러 번 수정하다 레이아웃이 깨지면 → "마지막 변경 되돌려줘"라고 하거나, 심하면 `git`으로 되돌리세요.


---


## 3-2. Gemini API 연동

<details>
<summary>API 키 발급</summary>

> 📌 **Gemini API** = 구글의 AI 모델을 우리 앱에서 쓸 수 있게 해주는 열쇠. 무료로 발급 가능하고, Gemini 2.0 Flash 모델은 무료 티어가 넉넉해서 테스트 비용이 거의 없습니다.

1. [Google AI Studio](%7B%7Bhttps://aistudio.google.com%7D%7D) 접속
2. 구글 계정으로 로그인
3. 왼쪽 사이드바에서 **"Get API key"** 클릭
4. **"Create API key"** 버튼 클릭
5. Google Cloud 프로젝트 선택 (없으면 자동 생성됨)
6. 생성된 API 키 복사 (`AIza...`로 시작)
7. `.env.local`에 추가:

```javascript
GEMINI_API_KEY=AIza...
```


> ⚠️ **주의:** `NEXT_PUBLIC_` 접두사를 붙이지 마세요! API 키가 클라이언트에 노출되면 누구나 쓸 수 있게 됩니다. 서버 사이드에서만 사용해야 합니다.


</details>

<details>
<summary>에이전트에게 구현 요청</summary>

> 🤖 **AI 프롬프트:**  
> "Gemini API를 사용해서 채팅 기능을 구현해줘.  
> - API 라우트: /api/chat  
> - 스트리밍 응답으로 구현 (실시간으로 글자가 나오게)  
> - 사용자 메시지를 받아서 Gemini 2.0 Flash로 응답 생성  
> - @google/generative-ai SDK 사용  
> - 에러 핸들링도 해줘."


</details>


---


## 3-3. 대화 저장 + 히스토리 (Supabase DB)

<details>
<summary>DB 테이블 구조</summary>

```javascript
conversations (대화 목록)
├── id          (고유 번호)
├── user_id     (누구의 대화인지)
├── title       (대화 제목)
└── created_at  (언제 만들었는지)

messages (메시지 목록)
├── id               (고유 번호)
├── conversation_id  (어떤 대화에 속하는지)
├── role             (user인지 assistant인지)
├── content          (실제 메시지 내용)
└── created_at       (언제 보냈는지)
```


</details>

<details>
<summary>에이전트에게 구현 요청</summary>

> 🤖 **AI 프롬프트:**  
> "채팅 내용을 Supabase에 저장해줘.  
> - 새 대화를 시작하면 conversations 테이블에 레코드 생성  
> - 메시지를 보내면 messages 테이블에 저장  
> - 사이드바에서 이전 대화 목록을 불러오기  
> - 대화를 클릭하면 해당 대화의 메시지를 불러오기  
> - 대화 삭제 기능도 추가해줘."


</details>


    > ⚠️ **에러 대응:** "new row violates row-level security policy" → Supabase에서 RLS 정책 설정 필요. 에이전트한테 "RLS 정책 SQL 만들어줘" 요청. 또는 테스트 단계에서는 RLS 일단 끄고 나중에 켜기.


    커밋:


    ```bash
    git add . && git commit -m "feat: chat with gemini + conversation history" && git push
    ```


---


### ✅ 핵심 기능 체크리스트

    - [ ] 대시보드 레이아웃 (사이드바 + 메인)
    - [ ] Gemini API 키 발급 + 환경변수 설정
    - [ ] AI 채팅 API 라우트 구현
    - [ ] 채팅 UI 구현 (입력 + 응답 표시)
    - [ ] Supabase에 대화 테이블 생성
    - [ ] 대화 저장 + 히스토리 불러오기

---


# 🧱 WALL 3: 결제 [32:00 ~ 47:30]


> 💡 **Polar**를 쓰면 SaaS 결제를 쉽게 붙일 수 있습니다. 한국에서도 사용 가능! **배포를 먼저 하고 결제 테스트** → ngrok 불필요.


## Polar + Supabase + 도메인 연동 구조


결제 · 웹훅 · 구독 데이터 흐름


```mermaid
graph LR
    subgraph PD["Polar Dashboard"]
        direction TB
        P1["상품 등록 (Pro / Unlimited)"] --> P2["Product ID 발급"]
        P1 --> P3["API Token 발급"]
        P4["Webhook URL 등록"]
        P4 --> P5["Webhook Secret 발급"]
    end
    subgraph APP["우리 도메인 (Vercel)"]
        direction TB
        A1["Pricing > Polar Checkout 호출"]
        A2["/api/webhook/polar 엔드포인트"]
        A3["Webhook Secret으로 서명 검증"]
        A4["구독 정보 > Supabase에 저장"]
        A2 --> A3 --> A4
    end
    subgraph DB["Supabase"]
        direction TB
        D1["subscriptions 테이블<br>user_id, plan, status"]
        D2["usage 테이블<br>user_id, month, count"]
    end
    P2 -- "① API Token + Product ID<br>.env.local에 저장" --> A1
    P4 -. "② Webhook URL 등록" .-> A2
    P5 -- "③ Webhook Secret 복사<br>POLAR_WEBHOOK_SECRET" --> A3
    A4 -- "④ 구독/사용량 저장<br>INSERT subscriptions, usage" --> D1
    D1 -. "⑤ 플랜 · 사용량 조회<br>SELECT plan, count" .-> A1
```


> 🗺️ **흐름 요약:** 1️⃣ 유저가 Pricing에서 결제 → 2️⃣ Polar Checkout 결제 처리 → 3️⃣ Polar가 도메인으로 Webhook 전송 → 4️⃣ Secret으로 서명 검증 → 5️⃣ Supabase에 구독 정보 저장 → ✅ 유저 플랜 활성화 완료!


---


## 4-1. Polar Sandbox 셋업 + 상품 등록


    > 📌 **Polar** = SaaS 특화 결제 플랫폼. Stripe보다 구독/요금제가 쉬움. **Sandbox** = 테스트용 가짜 환경 (진짜 돈 안 나감).

    1. [polar.sh](http://polar.sh/) 가입 (GitHub 계정으로)
    2. 대시보드 왼쪽 하단 **"Sandbox"로 전환**
    3. Sandbox에서 상품 2개 생성:

    | 플랜            | 가격       | 월 호출 |
    | ------------- | -------- | ---- |
    | **Pro**       | $9.99/월  | 100회 |
    | **Unlimited** | $29.99/월 | 무제한  |

    1. 각 상품의 **Product ID** 복사
    2. Settings → API Tokens에서 **Polar API Token** 발급
    3. `.env.local`에 추가:

    ```javascript
    POLAR_ACCESS_TOKEN=...
    POLAR_PRODUCT_ID_PRO=...
    POLAR_PRODUCT_ID_UNLIMITED=...
    ```


---


## 4-2. Pricing UI + Checkout 연결


    > 🤖 **AI 프롬프트:**  
    > "Pricing 페이지를 만들고 Polar 결제를 연결해줘.  
    > - 3가지 플랜 카드: Free ($0, 월 10회), Pro ($9.99/월, 월 100회), Unlimited ($29.99/월, 무제한)  
    > - 현재 플랜에 '현재 플랜' 뱃지, 업그레이드 가능한 플랜에 '업그레이드' 버튼  
    > - 다크 테마, Pro 카드를 살짝 강조 (추천 표시)  
    > - 업그레이드 버튼 클릭 시 Polar Checkout으로 이동  
    > - @polar-sh/sdk 설치해서 사용  
    > - Polar product ID는 환경 변수로 관리  
    > - 결제 완료 후 success 페이지로 리디렉트"


    > 📌 **웹훅(Webhook)이란?** 결제가 완료되면 Polar가 우리 서버에 "이 사람 결제 완료!"라고 자동으로 알려주는 것. 카톡 알림 같은 거. 웹훅을 받으려면 서버가 인터넷에 있어야 하므로 **먼저 배포합니다.**


---


## 4-3. Vercel 배포 (결제 테스트 전에!)


    > 📌 **왜 먼저 배포?** 웹훅을 받으려면 서버가 인터넷에 있어야 함. 배포하면 ngrok 없이 실 URL로 웹훅 테스트 가능!

<details>
<summary>배포 전 필수!</summary>

```bash
git add . && git commit -m "feat: pricing page + polar checkout" && git push
```


Vercel은 GitHub에서 코드를 가져오므로 **반드시 푸시 먼저!**


</details>

<details>
<summary>배포 단계</summary>
1. [vercel.com](http://vercel.com/) → GitHub으로 로그인
2. **"New Project"** → 우리 레포지토리 **Import**
3. **환경 변수 입력** (중요!):

```javascript
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
GEMINI_API_KEY=...
POLAR_ACCESS_TOKEN=...
POLAR_PRODUCT_ID_PRO=...
POLAR_PRODUCT_ID_UNLIMITED=...
```


(POLAR_WEBHOOK_SECRET은 아직 안 넣음 → 나중에 테스트하면서 받을 것)

1. **Deploy** 클릭
2. 배포 완료 후 URL 확인

</details>

<details>
<summary>배포 후 필수 설정</summary>

> ⚠️ **꼭 해야 할 것:**  
> - **Supabase**: Authentication → URL Configuration에서 **배포된 도메인 추가** (안 하면 로그인 안 됨!)  
> - **Google Cloud Console**: 승인된 리디렉션 URI에 배포 도메인 추가


</details>


    > ⚠️ **에러 대응:** 배포 성공했는데 사이트 안 되면 → Vercel 대시보드에서 환경 변수 빠진 거 없는지 확인.


---


## 4-4. Customer Portal 만들기


    > 📌 **Customer Portal** = 사용자가 직접 구독 관리하는 페이지 (결제 수단 변경, 구독 취소, 청구서 확인). Polar가 자체 제공, 링크만 연결하면 됨.


    > 🤖 **AI 프롬프트:**  
    > "Billing Settings 페이지를 만들어줘.  
    > - 현재 구독 플랜 표시  
    > - 다음 결제일 표시  
    > - Polar Customer Portal 링크 버튼 (결제 수단 변경, 청구서 확인용)  
    > - 플랜 변경 버튼 (Pricing 페이지로 이동)  
    > - Polar SDK의 customerPortal 기능 사용  
    > - 대시보드 사이드바에서 'Billing' 메뉴로 접근 가능하게"


---


## 4-5. Payment Test (Sandbox)


    > 📌 **배포된 도메인에서** 테스트합니다. [localhost](http://localhost/)가 아닙니다!

<details>
<summary>테스트 순서</summary>
1. 배포된 사이트에서 구글 로그인
2. Pricing 페이지 → Pro 플랜 "업그레이드" 클릭
3. Polar Sandbox 결제 페이지 → 테스트 카드 정보 입력 (진짜 돈 안 나감)
4. 결제 완료 → success 페이지로 리디렉트

</details>

<details>
<summary>Webhook Secret 등록 (중요!)</summary>
1. Polar Sandbox 대시보드 → **Webhooks** 설정
2. 웹훅 엔드포인트 URL 등록: [`https://your-domain.vercel.app/api/webhooks/polar`](https://your-domain.vercel.app/api/webhooks/polar)
3. **Webhook Secret** 복사
4. `.env.local`에 추가: `POLAR_WEBHOOK_SECRET=...`
5. **Vercel** 대시보드 → Settings → Environment Variables에도 추가
6. **재배포** (Vercel Deployments → Redeploy 또는 git push)

</details>


    커밋:


    ```bash
    git add . && git commit -m "feat: polar payment + customer portal + deploy" && git push
    ```


---


### ✅ WALL 3 체크리스트

    - [ ] Polar 계정 생성 + Sandbox 모드 전환
    - [ ] Sandbox에서 상품 3개 등록 (Free/Pro/Unlimited)
    - [ ] Product ID 복사 → 환경변수 설정
    - [ ] Polar API Token 발급 → 환경변수 설정
    - [ ] Pricing 페이지 UI + Polar Checkout 연결
    - [ ] 커밋 + 푸시 (배포 전 필수!)
    - [ ] Vercel 배포 (GitHub Import + 환경변수)
    - [ ] Supabase redirect URL에 배포 도메인 추가
    - [ ] Customer Portal (Billing Settings 페이지) 구현
    - [ ] 배포된 도메인에서 Sandbox 결제 테스트
    - [ ] Polar Webhook 엔드포인트 등록 + Secret 복사
    - [ ] POLAR_WEBHOOK_SECRET → .env.local + Vercel 환경변수에 추가
    - [ ] Vercel 재배포

---


# 📋 구독 설계 구현 [47:30 ~ 53:00]


> 📋 결제만 되는 상태에서 → **실제 구독 라이프사이클** 구현. 3-tier 플랜, 사용량 관리, 업그레이드 모달까지.


---


## 5-1. 구독 시스템 개요

<details>
<summary>플랜 구성</summary>

|       | Free     | Pro      | Unlimited |
| ----- | -------- | -------- | --------- |
| 가격    | $0       | $9.99/월  | $29.99/월  |
| 월 호출  | 10회      | 100회     | 무제한       |
| 한도 초과 | 차단 (429) | 차단 (429) | —         |


</details>

<details>
<summary>구독 라이프사이클</summary>

```javascript
회원가입 → Free 자동 부여
  ↓
사용하다 한도 초과 → 업그레이드 모달
  ↓
Pro/Unlimited 결제 → 웹훅으로 플랜 변경
  ↓
매달 사용량 리셋 → 반복
  ↓
취소하면 → 현재 기간 끝까지 유지 → Free로 전환
```


</details>

<details>
<summary>웹훅 이벤트 정리</summary>

| 이벤트                     | 처리                        |
| ----------------------- | ------------------------- |
| `checkout.completed`    | 구독 생성, DB에 플랜·만료일 저장      |
| `subscription.active`   | 구독 활성 확인                  |
| `subscription.updated`  | 플랜 변경 반영                  |
| `subscription.canceled` | 취소 마킹, 만료일 기록 (기간 끝까지 유지) |
| `subscription.revoked`  | 즉시 Free 전환                |


</details>

<details>
<summary>DB 스키마</summary>

```sql
-- subscriptions
id, user_id, polar_subscription_id, plan (free/pro/unlimited),
status (active/canceled/expired), current_period_end, created_at, updated_at

-- usage
id, user_id, month (2025-02), count, updated_at
```


</details>

<details>
<summary>API 엔드포인트</summary>

| 메서드  | 경로                       | 설명                        |
| ---- | ------------------------ | ------------------------- |
| POST | /api/checkout            | Polar checkout session 생성 |
| POST | /api/webhooks/polar      | Webhook 수신·처리             |
| GET  | /api/subscription        | 현재 플랜·상태 조회               |
| POST | /api/subscription/cancel | 구독 취소                     |
| GET  | /api/usage               | 이번 달 사용량 조회               |


</details>


---


## 5-2. 에이전트에게 구현 시키기


    > 🤖 **AI 프롬프트:**  
    > "구독 시스템을 구현해줘. 다음 요구사항이야:  
    >   
    > **1. DB 테이블:**  
    > - subscriptions: user_id, polar_subscription_id, plan (free/pro/unlimited), status (active/canceled/expired), current_period_end  
    > - usage: user_id, month (2025-02 형식), count  
    >   
    > **2. 웹훅 핸들러 확장** (/api/webhooks/polar):  
    > - checkout.completed → 구독 생성, DB에 플랜/만료일 저장  
    > - subscription.active → 구독 활성 확인  
    > - subscription.updated → 플랜 변경 반영  
    > - subscription.canceled → 취소 마킹, 만료일 기록  
    > - subscription.revoked → 즉시 Free 전환  
    > - 웹훅 시그니처 검증  
    >   
    > **3. 사용량 추적:**  
    > - 채팅 API 호출 시마다 현재 월 사용량 체크  
    > - 한도 초과 시 429 에러 + 업그레이드 URL 반환  
    > - Unlimited는 카운팅 스킵  
    > - 매달 사용량 리셋  
    >   
    > **4. 사용량 대시보드 UI:**  
    > - '이번 달 7/10회 사용' 프로그레스바  
    > - 80% 도달 시 경고 배너  
    > - 100% 도달 시 업그레이드 CTA 모달  
    >   
    > **5. 회원가입 시 Free 자동 부여:**  
    > - 새 유저 생성 시 subscriptions에 plan='free' 자동 삽입"


    > ⚠️ **주의:** `subscription.canceled`와 `subscription.revoked`의 차이!  
    > - **canceled** = 기간 끝까지 유지 후 만료  
    > - **revoked** = 즉시 해지  
    > 에이전트한테 이 차이를 명확히 알려주세요.


    커밋:


    ```bash
    git add . && git commit -m "feat: subscription system + usage tracking" && git push
    ```


---


### ✅ 구독 설계 체크리스트

    - [ ] subscriptions 테이블 생성
    - [ ] usage 테이블 생성
    - [ ] 웹훅 핸들러 확장 (5개 이벤트)
    - [ ] 회원가입 시 Free 플랜 자동 부여
    - [ ] 사용량 추적 (채팅 시 카운트 증가, Unlimited 스킵)
    - [ ] 한도 초과 시 429 에러 + 업그레이드 URL 반환
    - [ ] 사용량 대시보드 UI (프로그레스바)
    - [ ] 80% 도달 시 경고 배너
    - [ ] 100% 도달 시 업그레이드 CTA 모달

---


# ✅ 테스트 [53:00 ~ 55:00]


> ✅ **배포된 실제 사이트에서 구독 플로우를 처음부터 끝까지 검증합니다.**


---


## MVP 데모 핵심 테스트


    | # | 카테고리   | 케이스               | 확인 포인트                              |
    | - | ------ | ----------------- | ----------------------------------- |
    | 1 | 구독 플로우 | 회원가입 → Free 자동 부여 | plan=free, 10회 한도                   |
    | 2 | 구독 플로우 | Free → Pro 결제 성공  | Polar checkout → webhook → plan=pro |
    | 3 | UI     | 사용량 대시보드 프로그레스바   | 정확한 수치 표시                           |
    | 4 | UI     | 한도 초과 시 업그레이드 모달  | CTA 노출 → checkout 연결                |


---


### ✅ 테스트 체크리스트

    - [ ] 회원가입 → Free 자동 부여 확인 (plan=free, 10회 한도)
    - [ ] Free → Pro 결제 성공 확인 (checkout → webhook → plan=pro)
    - [ ] 사용량 대시보드 프로그레스바 정확도 확인
    - [ ] 한도 초과 → 업그레이드 모달 → checkout 연결 확인

---


# 🔒 암호화: 유저 정보 보호 [55:00 ~ 59:00]


> 🔒 **배포 후 반드시 해야 할 것.** AES-256-GCM으로 유저 정보를 암호화합니다. DB가 털려도 정보를 못 읽게!


---


## 7-1. 암호화 개요

<details>
<summary>암호화 대상</summary>

| 테이블                | 컬럼         | 비고                   |
| ------------------ | ---------- | -------------------- |
| profiles           | email      | 검색 필요 → 해시 인덱스 컬럼 추가 |
| profiles           | full_name  | 검색 필요 → 해시 인덱스 컬럼 추가 |
| user_activity_logs | ip_address | 로그성 데이터              |


</details>

<details>
<summary>암호화 방식</summary>

| 항목     | 내용                                     |
| ------ | -------------------------------------- |
| 알고리즘   | AES-256-GCM ("군사 등급 암호화")              |
| 방식     | 앱 레벨 암호화 (코드에서 저장 전 암호화, 불러올 때 복호화)    |
| IV     | 매번 랜덤 생성 (16바이트)                       |
| 저장 포맷  | `iv:authTag:encryptedData` (hex 인코딩)   |
| 암호화 키  | ENCRYPTION_KEY 환경변수 (32바이트 = 64자리 hex) |
| 검색용 해시 | HMAC-SHA256 (HASH_KEY 환경변수)            |


</details>


---


## 7-2. 에이전트에게 구현 시키기


    > 🤖 **AI 프롬프트:**  
    > "유저 정보를 AES-256-GCM 방식으로 앱 레벨 암호화해줘.  
    >   
    > **1. 암호화 유틸리티:**  
    > - lib/encryption.ts에 encrypt(), decrypt() 함수  
    > - IV는 매번 랜덤 생성 (16바이트)  
    > - 저장 포맷: iv:authTag:encryptedData (hex 인코딩)  
    > - 암호화 키는 ENCRYPTION_KEY 환경변수 (32바이트 = 64자리 hex)  
    > - 검색용 hashForLookup() 함수 (HMAC-SHA256, HASH_KEY 환경변수)  
    >   
    > **2. 키 생성:**  
    > - ENCRYPTION_KEY와 HASH_KEY를 Node.js crypto로 생성하는 스크립트  
    > - .env.local에 반영  
    >   
    > **3. 암호화 대상:**  
    > - [profiles.email](http://profiles.email/), profiles.full_name → 암호화 + 해시 인덱스 컬럼 추가  
    > - user_activity_logs.ip_address → 암호화  
    >   
    > **4. DB 스키마 변경:**  
    > - profiles에 email_hash, full_name_hash 컬럼 + 인덱스 생성  
    >   
    > **5. 기존 데이터 마이그레이션 스크립트:**  
    > - scripts/migrate-encrypt.ts  
    > - 기존 평문 데이터를 암호화하는 배치 스크립트  
    >   
    > **6. RLS 호환성:**  
    > - 암호화된 컬럼에 대한 RLS 정책이 문제없는지 검토하고 수정해줘"


---


## 7-3. 암호화 확인 + 마무리

    1. 키 생성 스크립트 실행 → ENCRYPTION_KEY, HASH_KEY 자동 생성
    2. `.env.local`에 키 추가
    3. **Vercel 환경변수에도 추가** (ENCRYPTION_KEY, HASH_KEY)
    4. Supabase 대시보드에서 비포/애프터 확인:
        - **비포:** [`user@example.com`](mailto:user@example.com) (평문)
        - **애프터:** `a3f2b1c4d5e6:9f8e7d6c:2b3c4d5e6f7a...` (암호화)
    5. 앱에서는 정상적으로 표시됨 (서버사이드 복호화)
    6. 커밋:

    ```bash
    git add . && git commit -m "feat: AES-256-GCM encryption for user data" && git push
    ```


    > ⚠️ **주의사항:**  
    > - 암호화 키는 **절대 클라이언트에 노출 금지**  
    > - 모든 암호화/복호화는 **서버 사이드에서만** 수행  
    > - ENCRYPTION_KEY는 한번 정하면 **바꾸지 마세요** (바꾸면 이전 데이터 못 읽음)  
    > - `.env.local`과 Vercel **양쪽에 다** 넣어야 함


---


### ✅ 암호화 체크리스트

    - [ ] lib/encryption.ts — encrypt, decrypt, hashForLookup 함수
    - [ ] scripts/generate-keys.ts — ENCRYPTION_KEY, HASH_KEY 생성
    - [ ] .env.local에 ENCRYPTION_KEY, HASH_KEY 추가
    - [ ] DB 스키마 변경 — profiles에 email_hash, full_name_hash 컬럼 + 인덱스
    - [ ] 회원가입 플로우에 암호화 적용
    - [ ] 프로필 조회 시 복호화 적용
    - [ ] user_activity_logs.ip_address 암호화/복호화
    - [ ] RLS 정책 암호화 호환성 검토 및 수정
    - [ ] 기존 데이터 마이그레이션 스크립트 (scripts/migrate-encrypt.ts)
    - [ ] ENCRYPTION_KEY, HASH_KEY → Vercel 환경변수에 추가
    - [ ] Supabase 대시보드에서 암호화된 데이터 확인
    - [ ] 앱에서 정상 작동 확인 (서버사이드 복호화)

---


# 전체 환경변수 정리


> 📝 `.env.local` 파일에 필요한 모든 환경변수 목록입니다.


```javascript
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://[프로젝트ID].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[anon key]

# Gemini
GEMINI_API_KEY=AIza...

# Polar
POLAR_ACCESS_TOKEN=...
POLAR_WEBHOOK_SECRET=...
POLAR_PRODUCT_ID_PRO=...
POLAR_PRODUCT_ID_UNLIMITED=...

# 암호화
ENCRYPTION_KEY=[64자리 hex]
HASH_KEY=[64자리 hex]
```


> ⚠️ **중요:** `GEMINI_API_KEY`는 서버 사이드에서만 사용되니까 `NEXT_PUBLIC_` 접두사를 **붙이지 마세요.**


---


> 🎉 **축하합니다!** 여기까지 따라오셨다면 아이디어 하나로 시작해서 로그인, 결제, 배포, 보안까지 갖춘 진짜 SaaS 서비스를 만드신 겁니다. **벽은 생각보다 낮습니다.**


공개 문서 변환 코드 (HTML)

<!-- Notion Reference: 바이브코딩으로 SaaS 런칭하기 | 로그인·결제·배포 풀 튜토리얼 -->
<!-- 추출일: 2026-03-21 -->
<!-- 원본 ID: 32110791e8188192ae76ccedbdec0e81 --><h1>바이브코딩으로 SaaS 런칭하기 | 로그인·결제·배포 풀 튜토리얼</h1>
<blockquote>
<p>🚀 <strong>이 문서는 유튜브 영상을 보고 혼자 따라하실 수 있도록 만든 상세 가이드입니다.</strong><br>모든 단계를 디테일한 설명, 링크, AI 프롬프트와 함께 정리했습니다.</p>
</blockquote>
<hr>
<h2>영상 개요</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>핵심 메시지</strong></td>
<td>&quot;벽은 생각보다 낮다&quot; — 시작, 인증, 결제 3가지 벽만 넘으면 누구나 서비스 런칭 가능</td>
</tr>
<tr>
<td><strong>만들 서비스</strong></td>
<td>Gemini 래퍼 SaaS (AI 채팅 + 로그인 + 결제 + 배포)</td>
</tr>
<tr>
<td><strong>예상 소요 시간</strong></td>
<td>약 60분</td>
</tr>
</tbody></table>
<hr>
<h2>타임스탬프</h2>
<table>
<thead>
<tr>
<th>파트</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>WALL 1</td>
<td>시작 (Antigravity + Next.js + GitHub)</td>
</tr>
<tr>
<td>WALL 2</td>
<td>인증 (Supabase + Google Login)</td>
</tr>
<tr>
<td>핵심 기능</td>
<td>Gemini 래퍼 + UI + 대화 저장</td>
</tr>
<tr>
<td>WALL 3</td>
<td>결제 (Polar + Checkout + 배포 + Customer Portal + 테스트)</td>
</tr>
<tr>
<td>구독 설계</td>
<td>3-tier 플랜 라이프사이클 구현</td>
</tr>
<tr>
<td>테스트</td>
<td>구독 플로우 + UI 검증</td>
</tr>
<tr>
<td>암호화</td>
<td>AES-256-GCM 유저 정보 보호</td>
</tr>
</tbody></table>
<hr>
<h2>사용 기술 스택 + 링크</h2>
<table>
<thead>
<tr>
<th>역할</th>
<th>기술</th>
<th>링크</th>
</tr>
</thead>
<tbody><tr>
<td>IDE</td>
<td>Antigravity (Gemini Agent 내장)</td>
<td><a href="http://antigravity.google/">antigravity.google</a></td>
</tr>
<tr>
<td>프레임워크</td>
<td>Next.js + TypeScript + Tailwind CSS</td>
<td><a href="http://nextjs.org/">nextjs.org</a></td>
</tr>
<tr>
<td>인증 + DB</td>
<td>Supabase</td>
<td><a href="http://supabase.com/">supabase.com</a></td>
</tr>
<tr>
<td>AI</td>
<td>Gemini API (2.0 Flash)</td>
<td><a href="http://aistudio.google.com/">aistudio.google.com</a></td>
</tr>
<tr>
<td>결제</td>
<td>Polar</td>
<td><a href="http://polar.sh/">polar.sh</a></td>
</tr>
<tr>
<td>배포</td>
<td>Vercel</td>
<td><a href="http://vercel.com/">vercel.com</a></td>
</tr>
<tr>
<td>버전 관리</td>
<td>GitHub</td>
<td><a href="http://github.com/">github.com</a></td>
</tr>
<tr>
<td>UI 레퍼런스</td>
<td><a href="http://21st.dev/">21st.dev</a></td>
<td><a href="http://21st.dev/">21st.dev</a></td>
</tr>
<tr>
<td>Node.js (필수)</td>
<td>Node.js LTS</td>
<td><a href="http://nodejs.org/">nodejs.org</a></td>
</tr>
</tbody></table>
<hr>
<h1>🧱 WALL 1: 시작 [2:00 ~ 8:00]</h1>
<blockquote>
<p>💡 딱 3가지만 깔면 됩니다: <strong>Antigravity + Next.js + GitHub</strong></p>
</blockquote>
<hr>
<h2>1-1. Antigravity 설치 + 셋업</h2>
<pre><code>&gt; 📌 **Antigravity** = 구글이 만든 AI 코딩 에디터. VS Code 기반 + Gemini 에이전트 내장. 현재 **무료**.

1. [antigravity.google/download](http://antigravity.google/download) 에서 본인 OS에 맞는 버전 다운로드
2. 설치 후 첫 실행 → 셋업 화면
3. **에이전트 정책 선택**: **&quot;Review-driven development&quot;** 선택 (AI가 코드 짜면 우리가 확인·승인)
4. 구글 계정 로그인
5. 왼쪽 Agent Manager 패널 확인 → 여기서 에이전트에게 채팅으로 작업 지시
</code></pre>
<hr>
<h2>1-2. Next.js 프로젝트 생성</h2>
<pre><code>&gt; 📌 **Next.js** = 웹사이트 만드는 도구. 요즘 웬만한 웹 서비스가 이걸로 만들어짐.


Antigravity 터미널에서 아래 명령어 실행:


```bash
npx create-next-app@latest my-gemini-wrapper
```
</code></pre>
<details>
<summary>옵션 설정</summary>
- TypeScript → **Yes**
- Tailwind CSS → **Yes**
- App Router → **Yes**
- 나머지 → 기본값 (Enter)</details><pre><code>에이전트에게 보일러플레이트 정리 요청:


&gt; 🤖 **AI 프롬프트:** &quot;보일러플레이트 정리해줘. 불필요한 기본 스타일이랑 예시 코드 지워줘.&quot;


&gt; ⚠️ **에러 대응:** `npx`가 안 된다고 뜨면 → Node.js가 없는 것. [nodejs.org](http://nodejs.org/)에서 LTS 버전 다운로드 후 터미널 재시작.
</code></pre>
<hr>
<h2>1-3. GitHub 연동</h2>
<pre><code>&gt; 📌 바이브 코딩에서 Git = **세이브 포인트**. AI가 코드를 망치면 되돌려야 하니까 반드시 해주세요.

1. [github.com](http://github.com/)에서 새 레포지토리 생성
2. 터미널에서 실행:

```bash
git remote add origin [본인 레포 URL]
git branch -M main
git push -u origin main
```
</code></pre>
<hr>
<h2>1-4. 랜딩 페이지 기본 구조</h2>
<pre><code>1. [21st.dev](http://21st.dev/)에서 맘에 드는 랜딩 페이지 레퍼런스 찾기
2. 에이전트에게 요청:

&gt; 🤖 **AI 프롬프트:** &quot;이 레퍼런스를 참고해서 히어로 섹션을 만들어줘. Gemini 래퍼 서비스야. 다크 테마로 해주고, 심플하게.&quot;

1. 히어로 + 기본 구조만 잡고 넘어가기 (나중에 다듬을 예정)
2. 커밋:

```bash
git add . &amp;&amp; git commit -m &quot;feat: landing page hero section&quot; &amp;&amp; git push
```


&gt; 💡 **바이브 코딩 팁:** 처음부터 완벽하게 하려고 하면 끝이 없습니다. 일단 돌아가게 만들고, 나중에 예쁘게!
</code></pre>
<hr>
<h3>✅ WALL 1 체크리스트</h3>
<pre><code>- [ ] Antigravity 설치 + 셋업
- [ ] Next.js 프로젝트 생성
- [ ] GitHub 레포 연동
- [ ] 랜딩 페이지 히어로 섹션 만들기
</code></pre>
<hr>
<h1>🧱 WALL 2: 인증 [8:00 ~ 18:00]</h1>
<blockquote>
<p>💡 <strong>Supabase + Google Login</strong>으로 해결. 콘솔 설정 빼면 코드는 에이전트가 3분 만에 짬.</p>
</blockquote>
<h2>Supabase + Google OAuth 연동 흐름</h2>
<p>서로 주고받는 키와 URL 관계</p>
<pre><code class="language-mermaid">graph LR
    subgraph GCC[&quot;Google Cloud Console&quot;]
        direction TB
        G1[&quot;OAuth 동의 화면 설정&quot;] --&gt; G2[&quot;OAuth 2.0 Client ID 생성&quot;]
        G2 --&gt; G3[&quot;Client ID 발급&quot;]
        G2 --&gt; G4[&quot;Client Secret 발급&quot;]
        G5[&quot;Authorized Redirect URI 등록&quot;]
    end
    subgraph SB[&quot;Supabase Dashboard&quot;]
        direction TB
        S1[&quot;Auth &gt; Providers &gt; Google&quot;] --&gt; S2[&quot;Client ID 입력&quot;]
        S1 --&gt; S3[&quot;Client Secret 입력&quot;]
        S4[&quot;Redirect URL 제공 (복사용)&quot;]
        S5[&quot;Site URL 설정&quot;]
    end
    G3 -- &quot;① Client ID 복사해서 전달&quot; --&gt; S2
    G4 -- &quot;② Client Secret 복사해서 전달&quot; --&gt; S3
    S4 -- &quot;③ Redirect URL 복사해서 등록&quot; --&gt; G5
</code></pre>
<blockquote>
<p>🗺️ <strong>흐름 요약:</strong> 1️⃣ Google Console에서 Client ID/Secret 생성 → 2️⃣ Supabase Provider에 ID/Secret 붙여넣기 → 3️⃣ Supabase Redirect URL을 Google에 등록 → ✅ OAuth 로그인 연동 완료!</p>
</blockquote>
<hr>
<h2>2-1. Supabase 프로젝트 생성</h2>
<pre><code>&gt; 📌 **Supabase** = 백엔드 통째로 제공 (인증 + DB + 스토리지). 무료 플랜 있음. 인증 붙이면서 DB도 같이 세팅됨.

1. [supabase.com](http://supabase.com/) 가입 (GitHub 계정 가능)
2. &quot;New Project&quot; → 프로젝트 이름 입력 → 리전 선택
3. 프로젝트 Settings에서 **URL**과 **anon key (publishable key)** 복사해두기
</code></pre>
<hr>
<h2>2-2. Google Cloud Console OAuth 설정</h2>
<pre><code>&gt; 📌 &quot;우리 앱에서 구글 로그인을 쓸 거야&quot;라고 구글한테 허락받는 과정.

1. [Google Cloud Console](https://console.cloud.google.com/) 접속
2. 새 프로젝트 생성
3. 왼쪽 메뉴: **API 및 서비스** → **사용자 인증 정보**
4. **&quot;+ 사용자 인증 정보 만들기&quot;** → **&quot;OAuth 클라이언트 ID&quot;**
5. 애플리케이션 유형: **&quot;웹 애플리케이션&quot;**
6. 승인된 리디렉션 URI에 Supabase 콜백 URL 입력:

`https://[프로젝트ID].`[`supabase.co/auth/v1/callback`](http://supabase.co/auth/v1/callback)

1. **클라이언트 ID**와 **클라이언트 시크릿** 복사

&gt; ⚠️ **에러 대응:** &quot;동의 화면을 구성해야 합니다&quot; 메시지 → OAuth 동의 화면 설정에서 앱 이름 + 이메일만 입력. **테스트 모드**로 시작하고 나중에 프로덕션으로 변경.
</code></pre>
<hr>
<h2>2-3. Supabase에 Google Provider 연결</h2>
<pre><code>1. Supabase 대시보드 → **Authentication** → **Providers**
2. **Google** 찾아서 활성화 (Enable)
3. 아까 복사한 **클라이언트 ID**와 **시크릿** 붙여넣기
4. 저장
</code></pre>
<hr>
<h2>2-4. 코드 작업 - 에이전트에게 로그인 구현 시키기</h2>
<details>
<summary>환경변수 설정</summary><p>프로젝트 루트에 <code>.env.local</code> 파일 생성:</p>
<pre><code class="language-javascript">NEXT_PUBLIC_SUPABASE_URL=https://[프로젝트ID].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[anon key]
</code></pre>
</details><details>
<summary>에이전트에게 구현 요청</summary><blockquote>
<p>🤖 <strong>AI 프롬프트:</strong><br>&quot;Supabase Google OAuth 로그인을 구현해줘. 다음 요구사항이야:  </p>
<ol>
<li>로그인 페이지에 구글 로그인 버튼  </li>
<li>AuthContext로 전역 로그인 상태 관리  </li>
<li>미로그인 시 대시보드 접근 차단 (리다이렉트)  </li>
<li>로그아웃 기능<br>Supabase URL이랑 anon key는 .env.local에 넣어둘게.&quot;</li>
</ol>
</blockquote>
</details><details>
<summary>에이전트가 만들어주는 것들</summary>
- **로그인 페이지** — 구글 로그인 버튼
- **AuthContext** — 앱 전체에서 로그인 상태 확인
- **미들웨어** — 비로그인 사용자 리다이렉트
- **로그아웃** — 로그아웃 기능</details><pre><code>&gt; ⚠️ **에러 대응:**  
&gt; - &quot;Invalid API key&quot; → `.env.local`에 키를 잘못 넣었거나 앞뒤 공백이 있는 경우  
&gt; - &quot;redirect_uri_mismatch&quot; → Google Cloud Console URI와 Supabase 설정이 다른 경우  
&gt; - **에러 메시지를 그대로 에이전트한테 붙여넣으세요.** 대부분 알아서 해결해 줍니다.
</code></pre>
<hr>
<h2>2-5. 로그인 테스트</h2>
<pre><code>1. `npm run dev`로 로컬 서버 실행
2. 로그인 페이지 → &quot;구글로 로그인&quot; 클릭
3. 구글 계정 선택 → 대시보드 진입 확인
4. 로그아웃 → 랜딩 페이지로 돌아가는지 확인
5. 로그아웃 상태에서 대시보드 URL 직접 입력 → 로그인 페이지로 리다이렉트 확인
6. 커밋:

```bash
git add . &amp;&amp; git commit -m &quot;feat: google auth with supabase&quot; &amp;&amp; git push
```
</code></pre>
<hr>
<h2>2-6. Supabase MCP 연결 (선택사항, 강력 추천)</h2>
<pre><code>&gt; 📌 **MCP (Model Context Protocol)** = AI 에이전트가 Supabase에 직접 접근할 수 있게 해주는 연결 규격. 연결하면 에이전트가 DB 테이블 조회, SQL 실행, 로그 확인, TypeScript 타입 생성 등을 **자연어 명령**으로 해줌.


&gt; 💡 **왜 쓰나요?** MCP 없이는 에이전트한테 DB 구조를 일일이 설명해야 하지만, MCP를 연결하면 에이전트가 알아서 DB를 보고 코드를 짜줍니다. 특히 RLS 정책, 마이그레이션, 테이블 생성 작업이 훨씬 빨라짐.
</code></pre>
<details>
<summary>Supabase MCP가 제공하는 기능</summary><table>
<thead>
<tr>
<th>기능 그룹</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Database</strong></td>
<td>테이블 목록 조회, SQL 쿼리 실행, 마이그레이션 관리</td>
</tr>
<tr>
<td><strong>Debugging</strong></td>
<td>서비스 로그 확인, 보안/성능 어드바이저</td>
</tr>
<tr>
<td><strong>Development</strong></td>
<td>프로젝트 URL, API 키 조회, TypeScript 타입 자동 생성</td>
</tr>
<tr>
<td><strong>Edge Functions</strong></td>
<td>엣지 펑션 목록 조회, 배포</td>
</tr>
<tr>
<td><strong>Docs</strong></td>
<td>Supabase 공식 문서 검색</td>
</tr>
</tbody></table>
</details><details>
<summary>설정 방법 (Antigravity / Cursor 기준)</summary>
1. 프로젝트 루트에 `.cursor/mcp.json` 파일 생성 (Antigravity도 동일 경로):<pre><code class="language-json">{
  &quot;mcpServers&quot;: {
    &quot;supabase&quot;: {
      &quot;url&quot;: &quot;https://mcp.supabase.com/mcp&quot;
    }
  }
}
</code></pre>
<ol>
<li>에디터 재시작 → MCP 클라이언트가 브라우저 로그인을 요청</li>
<li>Supabase 계정으로 로그인하면 연결 완료</li>
<li>에이전트에게 &quot;내 Supabase 테이블 목록 보여줘&quot; 같은 자연어로 테스트</li>
</ol>
</details><details>
<summary>추천 설정 옵션</summary><p>URL 파라미터로 기능을 제한할 수 있습니다:</p>
<pre><code class="language-javascript">https://mcp.supabase.com/mcp?read_only=true&amp;project_ref=[프로젝트ID]
</code></pre>
<ul>
<li><code>read_only=true</code> — 읽기 전용 (실수로 데이터 변경 방지)</li>
<li><code>project_ref=[ID]</code> — 특정 프로젝트만 접근 가능하게 제한</li>
</ul>
</details><pre><code>&gt; ⚠️ **보안 주의사항:**  
&gt; - **프로덕션 DB에는 직접 연결하지 마세요** — 개발/테스트 환경에서만 사용  
&gt; - 처음에는 `read_only=true`로 시작하는 것을 추천  
&gt; - MCP 클라이언트의 **수동 도구 승인(manual tool approval)** 설정을 켜두세요


&gt; 💡 **자세한 설정 가이드:** [supabase.com/docs/guides/getting-started/mcp](http://supabase.com/docs/guides/getting-started/mcp)
</code></pre>
<hr>
<h3>✅ WALL 2 체크리스트</h3>
<pre><code>- [ ] Supabase 프로젝트 생성
- [ ] Google Cloud Console에서 OAuth 클라이언트 설정
- [ ] Supabase Auth에 Google Provider 연결
- [ ] 로그인/로그아웃 UI 구현
- [ ] 로그인 테스트 (Google 계정으로 확인)
- [ ] (선택) Supabase MCP 연결
</code></pre>
<hr>
<h1>⚡ 핵심 기능: Gemini 래퍼 [18:00 ~ 32:00]</h1>
<blockquote>
<p>⚡ 서비스의 심장을 만드는 단계. <strong>대시보드 UI + Gemini API 연동 + 대화 저장</strong></p>
</blockquote>
<hr>
<h2>3-1. 대시보드 UI 만들기</h2>
<pre><code>&gt; 🤖 **AI 프롬프트:**  
&gt; &quot;로그인한 사용자를 위한 대시보드를 만들어줘.  
&gt; - 왼쪽: 대화 히스토리 사이드바 (대화 목록, 새 대화 버튼)  
&gt; - 오른쪽: 채팅 인터페이스 (메시지 표시, 입력창)  
&gt; - 상단: 사용자 정보, 남은 크레딧 표시  
&gt; - 다크 테마, 미니멀하게.&quot;


&gt; 💡 **수정 필요하면 말로 하세요.** 예: &quot;사이드바 폭 좀 줄여주고, 채팅 입력창을 좀 더 크게 만들어줘.&quot; 코드를 읽을 필요 없이 원하는 걸 말로 설명하면 AI가 수정해줍니다.


&gt; ⚠️ **에러 대응:** UI를 여러 번 수정하다 레이아웃이 깨지면 → &quot;마지막 변경 되돌려줘&quot;라고 하거나, 심하면 `git`으로 되돌리세요.
</code></pre>
<hr>
<h2>3-2. Gemini API 연동</h2>
<details>
<summary>API 키 발급</summary><blockquote>
<p>📌 <strong>Gemini API</strong> = 구글의 AI 모델을 우리 앱에서 쓸 수 있게 해주는 열쇠. 무료로 발급 가능하고, Gemini 2.0 Flash 모델은 무료 티어가 넉넉해서 테스트 비용이 거의 없습니다.</p>
</blockquote>
<ol>
<li><a href="%7B%7Bhttps://aistudio.google.com%7D%7D">Google AI Studio</a> 접속</li>
<li>구글 계정으로 로그인</li>
<li>왼쪽 사이드바에서 <strong>&quot;Get API key&quot;</strong> 클릭</li>
<li><strong>&quot;Create API key&quot;</strong> 버튼 클릭</li>
<li>Google Cloud 프로젝트 선택 (없으면 자동 생성됨)</li>
<li>생성된 API 키 복사 (<code>AIza...</code>로 시작)</li>
<li><code>.env.local</code>에 추가:</li>
</ol>
<pre><code class="language-javascript">GEMINI_API_KEY=AIza...
</code></pre>
<blockquote>
<p>⚠️ <strong>주의:</strong> <code>NEXT_PUBLIC_</code> 접두사를 붙이지 마세요! API 키가 클라이언트에 노출되면 누구나 쓸 수 있게 됩니다. 서버 사이드에서만 사용해야 합니다.</p>
</blockquote>
</details><details>
<summary>에이전트에게 구현 요청</summary><blockquote>
<p>🤖 <strong>AI 프롬프트:</strong><br>&quot;Gemini API를 사용해서 채팅 기능을 구현해줘.  </p>
<ul>
<li>API 라우트: /api/chat  </li>
<li>스트리밍 응답으로 구현 (실시간으로 글자가 나오게)  </li>
<li>사용자 메시지를 받아서 Gemini 2.0 Flash로 응답 생성  </li>
<li>@google/generative-ai SDK 사용  </li>
<li>에러 핸들링도 해줘.&quot;</li>
</ul>
</blockquote>
</details><hr>
<h2>3-3. 대화 저장 + 히스토리 (Supabase DB)</h2>
<details>
<summary>DB 테이블 구조</summary><pre><code class="language-javascript">conversations (대화 목록)
├── id          (고유 번호)
├── user_id     (누구의 대화인지)
├── title       (대화 제목)
└── created_at  (언제 만들었는지)

messages (메시지 목록)
├── id               (고유 번호)
├── conversation_id  (어떤 대화에 속하는지)
├── role             (user인지 assistant인지)
├── content          (실제 메시지 내용)
└── created_at       (언제 보냈는지)
</code></pre>
</details><details>
<summary>에이전트에게 구현 요청</summary><blockquote>
<p>🤖 <strong>AI 프롬프트:</strong><br>&quot;채팅 내용을 Supabase에 저장해줘.  </p>
<ul>
<li>새 대화를 시작하면 conversations 테이블에 레코드 생성  </li>
<li>메시지를 보내면 messages 테이블에 저장  </li>
<li>사이드바에서 이전 대화 목록을 불러오기  </li>
<li>대화를 클릭하면 해당 대화의 메시지를 불러오기  </li>
<li>대화 삭제 기능도 추가해줘.&quot;</li>
</ul>
</blockquote>
</details><pre><code>&gt; ⚠️ **에러 대응:** &quot;new row violates row-level security policy&quot; → Supabase에서 RLS 정책 설정 필요. 에이전트한테 &quot;RLS 정책 SQL 만들어줘&quot; 요청. 또는 테스트 단계에서는 RLS 일단 끄고 나중에 켜기.


커밋:


```bash
git add . &amp;&amp; git commit -m &quot;feat: chat with gemini + conversation history&quot; &amp;&amp; git push
```
</code></pre>
<hr>
<h3>✅ 핵심 기능 체크리스트</h3>
<pre><code>- [ ] 대시보드 레이아웃 (사이드바 + 메인)
- [ ] Gemini API 키 발급 + 환경변수 설정
- [ ] AI 채팅 API 라우트 구현
- [ ] 채팅 UI 구현 (입력 + 응답 표시)
- [ ] Supabase에 대화 테이블 생성
- [ ] 대화 저장 + 히스토리 불러오기
</code></pre>
<hr>
<h1>🧱 WALL 3: 결제 [32:00 ~ 47:30]</h1>
<blockquote>
<p>💡 <strong>Polar</strong>를 쓰면 SaaS 결제를 쉽게 붙일 수 있습니다. 한국에서도 사용 가능! <strong>배포를 먼저 하고 결제 테스트</strong> → ngrok 불필요.</p>
</blockquote>
<h2>Polar + Supabase + 도메인 연동 구조</h2>
<p>결제 · 웹훅 · 구독 데이터 흐름</p>
<pre><code class="language-mermaid">graph LR
    subgraph PD[&quot;Polar Dashboard&quot;]
        direction TB
        P1[&quot;상품 등록 (Pro / Unlimited)&quot;] --&gt; P2[&quot;Product ID 발급&quot;]
        P1 --&gt; P3[&quot;API Token 발급&quot;]
        P4[&quot;Webhook URL 등록&quot;]
        P4 --&gt; P5[&quot;Webhook Secret 발급&quot;]
    end
    subgraph APP[&quot;우리 도메인 (Vercel)&quot;]
        direction TB
        A1[&quot;Pricing &gt; Polar Checkout 호출&quot;]
        A2[&quot;/api/webhook/polar 엔드포인트&quot;]
        A3[&quot;Webhook Secret으로 서명 검증&quot;]
        A4[&quot;구독 정보 &gt; Supabase에 저장&quot;]
        A2 --&gt; A3 --&gt; A4
    end
    subgraph DB[&quot;Supabase&quot;]
        direction TB
        D1[&quot;subscriptions 테이블&lt;br&gt;user_id, plan, status&quot;]
        D2[&quot;usage 테이블&lt;br&gt;user_id, month, count&quot;]
    end
    P2 -- &quot;① API Token + Product ID&lt;br&gt;.env.local에 저장&quot; --&gt; A1
    P4 -. &quot;② Webhook URL 등록&quot; .-&gt; A2
    P5 -- &quot;③ Webhook Secret 복사&lt;br&gt;POLAR_WEBHOOK_SECRET&quot; --&gt; A3
    A4 -- &quot;④ 구독/사용량 저장&lt;br&gt;INSERT subscriptions, usage&quot; --&gt; D1
    D1 -. &quot;⑤ 플랜 · 사용량 조회&lt;br&gt;SELECT plan, count&quot; .-&gt; A1
</code></pre>
<blockquote>
<p>🗺️ <strong>흐름 요약:</strong> 1️⃣ 유저가 Pricing에서 결제 → 2️⃣ Polar Checkout 결제 처리 → 3️⃣ Polar가 도메인으로 Webhook 전송 → 4️⃣ Secret으로 서명 검증 → 5️⃣ Supabase에 구독 정보 저장 → ✅ 유저 플랜 활성화 완료!</p>
</blockquote>
<hr>
<h2>4-1. Polar Sandbox 셋업 + 상품 등록</h2>
<pre><code>&gt; 📌 **Polar** = SaaS 특화 결제 플랫폼. Stripe보다 구독/요금제가 쉬움. **Sandbox** = 테스트용 가짜 환경 (진짜 돈 안 나감).

1. [polar.sh](http://polar.sh/) 가입 (GitHub 계정으로)
2. 대시보드 왼쪽 하단 **&quot;Sandbox&quot;로 전환**
3. Sandbox에서 상품 2개 생성:

| 플랜            | 가격       | 월 호출 |
| ------------- | -------- | ---- |
| **Pro**       | $9.99/월  | 100회 |
| **Unlimited** | $29.99/월 | 무제한  |

1. 각 상품의 **Product ID** 복사
2. Settings → API Tokens에서 **Polar API Token** 발급
3. `.env.local`에 추가:

```javascript
POLAR_ACCESS_TOKEN=...
POLAR_PRODUCT_ID_PRO=...
POLAR_PRODUCT_ID_UNLIMITED=...
```
</code></pre>
<hr>
<h2>4-2. Pricing UI + Checkout 연결</h2>
<pre><code>&gt; 🤖 **AI 프롬프트:**  
&gt; &quot;Pricing 페이지를 만들고 Polar 결제를 연결해줘.  
&gt; - 3가지 플랜 카드: Free ($0, 월 10회), Pro ($9.99/월, 월 100회), Unlimited ($29.99/월, 무제한)  
&gt; - 현재 플랜에 &#39;현재 플랜&#39; 뱃지, 업그레이드 가능한 플랜에 &#39;업그레이드&#39; 버튼  
&gt; - 다크 테마, Pro 카드를 살짝 강조 (추천 표시)  
&gt; - 업그레이드 버튼 클릭 시 Polar Checkout으로 이동  
&gt; - @polar-sh/sdk 설치해서 사용  
&gt; - Polar product ID는 환경 변수로 관리  
&gt; - 결제 완료 후 success 페이지로 리디렉트&quot;


&gt; 📌 **웹훅(Webhook)이란?** 결제가 완료되면 Polar가 우리 서버에 &quot;이 사람 결제 완료!&quot;라고 자동으로 알려주는 것. 카톡 알림 같은 거. 웹훅을 받으려면 서버가 인터넷에 있어야 하므로 **먼저 배포합니다.**
</code></pre>
<hr>
<h2>4-3. Vercel 배포 (결제 테스트 전에!)</h2>
<pre><code>&gt; 📌 **왜 먼저 배포?** 웹훅을 받으려면 서버가 인터넷에 있어야 함. 배포하면 ngrok 없이 실 URL로 웹훅 테스트 가능!
</code></pre>
<details>
<summary>배포 전 필수!</summary><pre><code class="language-bash">git add . &amp;&amp; git commit -m &quot;feat: pricing page + polar checkout&quot; &amp;&amp; git push
</code></pre>
<p>Vercel은 GitHub에서 코드를 가져오므로 <strong>반드시 푸시 먼저!</strong></p>
</details><details>
<summary>배포 단계</summary>
1. [vercel.com](http://vercel.com/) → GitHub으로 로그인
2. **"New Project"** → 우리 레포지토리 **Import**
3. **환경 변수 입력** (중요!):<pre><code class="language-javascript">NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
GEMINI_API_KEY=...
POLAR_ACCESS_TOKEN=...
POLAR_PRODUCT_ID_PRO=...
POLAR_PRODUCT_ID_UNLIMITED=...
</code></pre>
<p>(POLAR_WEBHOOK_SECRET은 아직 안 넣음 → 나중에 테스트하면서 받을 것)</p>
<ol>
<li><strong>Deploy</strong> 클릭</li>
<li>배포 완료 후 URL 확인</li>
</ol>
</details><details>
<summary>배포 후 필수 설정</summary><blockquote>
<p>⚠️ <strong>꼭 해야 할 것:</strong>  </p>
<ul>
<li><strong>Supabase</strong>: Authentication → URL Configuration에서 <strong>배포된 도메인 추가</strong> (안 하면 로그인 안 됨!)  </li>
<li><strong>Google Cloud Console</strong>: 승인된 리디렉션 URI에 배포 도메인 추가</li>
</ul>
</blockquote>
</details><pre><code>&gt; ⚠️ **에러 대응:** 배포 성공했는데 사이트 안 되면 → Vercel 대시보드에서 환경 변수 빠진 거 없는지 확인.
</code></pre>
<hr>
<h2>4-4. Customer Portal 만들기</h2>
<pre><code>&gt; 📌 **Customer Portal** = 사용자가 직접 구독 관리하는 페이지 (결제 수단 변경, 구독 취소, 청구서 확인). Polar가 자체 제공, 링크만 연결하면 됨.


&gt; 🤖 **AI 프롬프트:**  
&gt; &quot;Billing Settings 페이지를 만들어줘.  
&gt; - 현재 구독 플랜 표시  
&gt; - 다음 결제일 표시  
&gt; - Polar Customer Portal 링크 버튼 (결제 수단 변경, 청구서 확인용)  
&gt; - 플랜 변경 버튼 (Pricing 페이지로 이동)  
&gt; - Polar SDK의 customerPortal 기능 사용  
&gt; - 대시보드 사이드바에서 &#39;Billing&#39; 메뉴로 접근 가능하게&quot;
</code></pre>
<hr>
<h2>4-5. Payment Test (Sandbox)</h2>
<pre><code>&gt; 📌 **배포된 도메인에서** 테스트합니다. [localhost](http://localhost/)가 아닙니다!
</code></pre>
<details>
<summary>테스트 순서</summary>
1. 배포된 사이트에서 구글 로그인
2. Pricing 페이지 → Pro 플랜 "업그레이드" 클릭
3. Polar Sandbox 결제 페이지 → 테스트 카드 정보 입력 (진짜 돈 안 나감)
4. 결제 완료 → success 페이지로 리디렉트</details><details>
<summary>Webhook Secret 등록 (중요!)</summary>
1. Polar Sandbox 대시보드 → **Webhooks** 설정
2. 웹훅 엔드포인트 URL 등록: [`https://your-domain.vercel.app/api/webhooks/polar`](https://your-domain.vercel.app/api/webhooks/polar)
3. **Webhook Secret** 복사
4. `.env.local`에 추가: `POLAR_WEBHOOK_SECRET=...`
5. **Vercel** 대시보드 → Settings → Environment Variables에도 추가
6. **재배포** (Vercel Deployments → Redeploy 또는 git push)</details><pre><code>커밋:


```bash
git add . &amp;&amp; git commit -m &quot;feat: polar payment + customer portal + deploy&quot; &amp;&amp; git push
```
</code></pre>
<hr>
<h3>✅ WALL 3 체크리스트</h3>
<pre><code>- [ ] Polar 계정 생성 + Sandbox 모드 전환
- [ ] Sandbox에서 상품 3개 등록 (Free/Pro/Unlimited)
- [ ] Product ID 복사 → 환경변수 설정
- [ ] Polar API Token 발급 → 환경변수 설정
- [ ] Pricing 페이지 UI + Polar Checkout 연결
- [ ] 커밋 + 푸시 (배포 전 필수!)
- [ ] Vercel 배포 (GitHub Import + 환경변수)
- [ ] Supabase redirect URL에 배포 도메인 추가
- [ ] Customer Portal (Billing Settings 페이지) 구현
- [ ] 배포된 도메인에서 Sandbox 결제 테스트
- [ ] Polar Webhook 엔드포인트 등록 + Secret 복사
- [ ] POLAR_WEBHOOK_SECRET → .env.local + Vercel 환경변수에 추가
- [ ] Vercel 재배포
</code></pre>
<hr>
<h1>📋 구독 설계 구현 [47:30 ~ 53:00]</h1>
<blockquote>
<p>📋 결제만 되는 상태에서 → <strong>실제 구독 라이프사이클</strong> 구현. 3-tier 플랜, 사용량 관리, 업그레이드 모달까지.</p>
</blockquote>
<hr>
<h2>5-1. 구독 시스템 개요</h2>
<details>
<summary>플랜 구성</summary><table>
<thead>
<tr>
<th></th>
<th>Free</th>
<th>Pro</th>
<th>Unlimited</th>
</tr>
</thead>
<tbody><tr>
<td>가격</td>
<td>$0</td>
<td>$9.99/월</td>
<td>$29.99/월</td>
</tr>
<tr>
<td>월 호출</td>
<td>10회</td>
<td>100회</td>
<td>무제한</td>
</tr>
<tr>
<td>한도 초과</td>
<td>차단 (429)</td>
<td>차단 (429)</td>
<td>—</td>
</tr>
</tbody></table>
</details><details>
<summary>구독 라이프사이클</summary><pre><code class="language-javascript">회원가입 → Free 자동 부여
  ↓
사용하다 한도 초과 → 업그레이드 모달
  ↓
Pro/Unlimited 결제 → 웹훅으로 플랜 변경
  ↓
매달 사용량 리셋 → 반복
  ↓
취소하면 → 현재 기간 끝까지 유지 → Free로 전환
</code></pre>
</details><details>
<summary>웹훅 이벤트 정리</summary><table>
<thead>
<tr>
<th>이벤트</th>
<th>처리</th>
</tr>
</thead>
<tbody><tr>
<td><code>checkout.completed</code></td>
<td>구독 생성, DB에 플랜·만료일 저장</td>
</tr>
<tr>
<td><code>subscription.active</code></td>
<td>구독 활성 확인</td>
</tr>
<tr>
<td><code>subscription.updated</code></td>
<td>플랜 변경 반영</td>
</tr>
<tr>
<td><code>subscription.canceled</code></td>
<td>취소 마킹, 만료일 기록 (기간 끝까지 유지)</td>
</tr>
<tr>
<td><code>subscription.revoked</code></td>
<td>즉시 Free 전환</td>
</tr>
</tbody></table>
</details><details>
<summary>DB 스키마</summary><pre><code class="language-sql">-- subscriptions
id, user_id, polar_subscription_id, plan (free/pro/unlimited),
status (active/canceled/expired), current_period_end, created_at, updated_at

-- usage
id, user_id, month (2025-02), count, updated_at
</code></pre>
</details><details>
<summary>API 엔드포인트</summary><table>
<thead>
<tr>
<th>메서드</th>
<th>경로</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>POST</td>
<td>/api/checkout</td>
<td>Polar checkout session 생성</td>
</tr>
<tr>
<td>POST</td>
<td>/api/webhooks/polar</td>
<td>Webhook 수신·처리</td>
</tr>
<tr>
<td>GET</td>
<td>/api/subscription</td>
<td>현재 플랜·상태 조회</td>
</tr>
<tr>
<td>POST</td>
<td>/api/subscription/cancel</td>
<td>구독 취소</td>
</tr>
<tr>
<td>GET</td>
<td>/api/usage</td>
<td>이번 달 사용량 조회</td>
</tr>
</tbody></table>
</details><hr>
<h2>5-2. 에이전트에게 구현 시키기</h2>
<pre><code>&gt; 🤖 **AI 프롬프트:**  
&gt; &quot;구독 시스템을 구현해줘. 다음 요구사항이야:  
&gt;   
&gt; **1. DB 테이블:**  
&gt; - subscriptions: user_id, polar_subscription_id, plan (free/pro/unlimited), status (active/canceled/expired), current_period_end  
&gt; - usage: user_id, month (2025-02 형식), count  
&gt;   
&gt; **2. 웹훅 핸들러 확장** (/api/webhooks/polar):  
&gt; - checkout.completed → 구독 생성, DB에 플랜/만료일 저장  
&gt; - subscription.active → 구독 활성 확인  
&gt; - subscription.updated → 플랜 변경 반영  
&gt; - subscription.canceled → 취소 마킹, 만료일 기록  
&gt; - subscription.revoked → 즉시 Free 전환  
&gt; - 웹훅 시그니처 검증  
&gt;   
&gt; **3. 사용량 추적:**  
&gt; - 채팅 API 호출 시마다 현재 월 사용량 체크  
&gt; - 한도 초과 시 429 에러 + 업그레이드 URL 반환  
&gt; - Unlimited는 카운팅 스킵  
&gt; - 매달 사용량 리셋  
&gt;   
&gt; **4. 사용량 대시보드 UI:**  
&gt; - &#39;이번 달 7/10회 사용&#39; 프로그레스바  
&gt; - 80% 도달 시 경고 배너  
&gt; - 100% 도달 시 업그레이드 CTA 모달  
&gt;   
&gt; **5. 회원가입 시 Free 자동 부여:**  
&gt; - 새 유저 생성 시 subscriptions에 plan=&#39;free&#39; 자동 삽입&quot;


&gt; ⚠️ **주의:** `subscription.canceled`와 `subscription.revoked`의 차이!  
&gt; - **canceled** = 기간 끝까지 유지 후 만료  
&gt; - **revoked** = 즉시 해지  
&gt; 에이전트한테 이 차이를 명확히 알려주세요.


커밋:


```bash
git add . &amp;&amp; git commit -m &quot;feat: subscription system + usage tracking&quot; &amp;&amp; git push
```
</code></pre>
<hr>
<h3>✅ 구독 설계 체크리스트</h3>
<pre><code>- [ ] subscriptions 테이블 생성
- [ ] usage 테이블 생성
- [ ] 웹훅 핸들러 확장 (5개 이벤트)
- [ ] 회원가입 시 Free 플랜 자동 부여
- [ ] 사용량 추적 (채팅 시 카운트 증가, Unlimited 스킵)
- [ ] 한도 초과 시 429 에러 + 업그레이드 URL 반환
- [ ] 사용량 대시보드 UI (프로그레스바)
- [ ] 80% 도달 시 경고 배너
- [ ] 100% 도달 시 업그레이드 CTA 모달
</code></pre>
<hr>
<h1>✅ 테스트 [53:00 ~ 55:00]</h1>
<blockquote>
<p>✅ <strong>배포된 실제 사이트에서 구독 플로우를 처음부터 끝까지 검증합니다.</strong></p>
</blockquote>
<hr>
<h2>MVP 데모 핵심 테스트</h2>
<pre><code>| # | 카테고리   | 케이스               | 확인 포인트                              |
| - | ------ | ----------------- | ----------------------------------- |
| 1 | 구독 플로우 | 회원가입 → Free 자동 부여 | plan=free, 10회 한도                   |
| 2 | 구독 플로우 | Free → Pro 결제 성공  | Polar checkout → webhook → plan=pro |
| 3 | UI     | 사용량 대시보드 프로그레스바   | 정확한 수치 표시                           |
| 4 | UI     | 한도 초과 시 업그레이드 모달  | CTA 노출 → checkout 연결                |
</code></pre>
<hr>
<h3>✅ 테스트 체크리스트</h3>
<pre><code>- [ ] 회원가입 → Free 자동 부여 확인 (plan=free, 10회 한도)
- [ ] Free → Pro 결제 성공 확인 (checkout → webhook → plan=pro)
- [ ] 사용량 대시보드 프로그레스바 정확도 확인
- [ ] 한도 초과 → 업그레이드 모달 → checkout 연결 확인
</code></pre>
<hr>
<h1>🔒 암호화: 유저 정보 보호 [55:00 ~ 59:00]</h1>
<blockquote>
<p>🔒 <strong>배포 후 반드시 해야 할 것.</strong> AES-256-GCM으로 유저 정보를 암호화합니다. DB가 털려도 정보를 못 읽게!</p>
</blockquote>
<hr>
<h2>7-1. 암호화 개요</h2>
<details>
<summary>암호화 대상</summary><table>
<thead>
<tr>
<th>테이블</th>
<th>컬럼</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>profiles</td>
<td>email</td>
<td>검색 필요 → 해시 인덱스 컬럼 추가</td>
</tr>
<tr>
<td>profiles</td>
<td>full_name</td>
<td>검색 필요 → 해시 인덱스 컬럼 추가</td>
</tr>
<tr>
<td>user_activity_logs</td>
<td>ip_address</td>
<td>로그성 데이터</td>
</tr>
</tbody></table>
</details><details>
<summary>암호화 방식</summary><table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>알고리즘</td>
<td>AES-256-GCM (&quot;군사 등급 암호화&quot;)</td>
</tr>
<tr>
<td>방식</td>
<td>앱 레벨 암호화 (코드에서 저장 전 암호화, 불러올 때 복호화)</td>
</tr>
<tr>
<td>IV</td>
<td>매번 랜덤 생성 (16바이트)</td>
</tr>
<tr>
<td>저장 포맷</td>
<td><code>iv:authTag:encryptedData</code> (hex 인코딩)</td>
</tr>
<tr>
<td>암호화 키</td>
<td>ENCRYPTION_KEY 환경변수 (32바이트 = 64자리 hex)</td>
</tr>
<tr>
<td>검색용 해시</td>
<td>HMAC-SHA256 (HASH_KEY 환경변수)</td>
</tr>
</tbody></table>
</details><hr>
<h2>7-2. 에이전트에게 구현 시키기</h2>
<pre><code>&gt; 🤖 **AI 프롬프트:**  
&gt; &quot;유저 정보를 AES-256-GCM 방식으로 앱 레벨 암호화해줘.  
&gt;   
&gt; **1. 암호화 유틸리티:**  
&gt; - lib/encryption.ts에 encrypt(), decrypt() 함수  
&gt; - IV는 매번 랜덤 생성 (16바이트)  
&gt; - 저장 포맷: iv:authTag:encryptedData (hex 인코딩)  
&gt; - 암호화 키는 ENCRYPTION_KEY 환경변수 (32바이트 = 64자리 hex)  
&gt; - 검색용 hashForLookup() 함수 (HMAC-SHA256, HASH_KEY 환경변수)  
&gt;   
&gt; **2. 키 생성:**  
&gt; - ENCRYPTION_KEY와 HASH_KEY를 Node.js crypto로 생성하는 스크립트  
&gt; - .env.local에 반영  
&gt;   
&gt; **3. 암호화 대상:**  
&gt; - [profiles.email](http://profiles.email/), profiles.full_name → 암호화 + 해시 인덱스 컬럼 추가  
&gt; - user_activity_logs.ip_address → 암호화  
&gt;   
&gt; **4. DB 스키마 변경:**  
&gt; - profiles에 email_hash, full_name_hash 컬럼 + 인덱스 생성  
&gt;   
&gt; **5. 기존 데이터 마이그레이션 스크립트:**  
&gt; - scripts/migrate-encrypt.ts  
&gt; - 기존 평문 데이터를 암호화하는 배치 스크립트  
&gt;   
&gt; **6. RLS 호환성:**  
&gt; - 암호화된 컬럼에 대한 RLS 정책이 문제없는지 검토하고 수정해줘&quot;
</code></pre>
<hr>
<h2>7-3. 암호화 확인 + 마무리</h2>
<pre><code>1. 키 생성 스크립트 실행 → ENCRYPTION_KEY, HASH_KEY 자동 생성
2. `.env.local`에 키 추가
3. **Vercel 환경변수에도 추가** (ENCRYPTION_KEY, HASH_KEY)
4. Supabase 대시보드에서 비포/애프터 확인:
    - **비포:** [`user@example.com`](mailto:user@example.com) (평문)
    - **애프터:** `a3f2b1c4d5e6:9f8e7d6c:2b3c4d5e6f7a...` (암호화)
5. 앱에서는 정상적으로 표시됨 (서버사이드 복호화)
6. 커밋:

```bash
git add . &amp;&amp; git commit -m &quot;feat: AES-256-GCM encryption for user data&quot; &amp;&amp; git push
```


&gt; ⚠️ **주의사항:**  
&gt; - 암호화 키는 **절대 클라이언트에 노출 금지**  
&gt; - 모든 암호화/복호화는 **서버 사이드에서만** 수행  
&gt; - ENCRYPTION_KEY는 한번 정하면 **바꾸지 마세요** (바꾸면 이전 데이터 못 읽음)  
&gt; - `.env.local`과 Vercel **양쪽에 다** 넣어야 함
</code></pre>
<hr>
<h3>✅ 암호화 체크리스트</h3>
<pre><code>- [ ] lib/encryption.ts — encrypt, decrypt, hashForLookup 함수
- [ ] scripts/generate-keys.ts — ENCRYPTION_KEY, HASH_KEY 생성
- [ ] .env.local에 ENCRYPTION_KEY, HASH_KEY 추가
- [ ] DB 스키마 변경 — profiles에 email_hash, full_name_hash 컬럼 + 인덱스
- [ ] 회원가입 플로우에 암호화 적용
- [ ] 프로필 조회 시 복호화 적용
- [ ] user_activity_logs.ip_address 암호화/복호화
- [ ] RLS 정책 암호화 호환성 검토 및 수정
- [ ] 기존 데이터 마이그레이션 스크립트 (scripts/migrate-encrypt.ts)
- [ ] ENCRYPTION_KEY, HASH_KEY → Vercel 환경변수에 추가
- [ ] Supabase 대시보드에서 암호화된 데이터 확인
- [ ] 앱에서 정상 작동 확인 (서버사이드 복호화)
</code></pre>
<hr>
<h1>전체 환경변수 정리</h1>
<blockquote>
<p>📝 <code>.env.local</code> 파일에 필요한 모든 환경변수 목록입니다.</p>
</blockquote>
<pre><code class="language-javascript"># Supabase
NEXT_PUBLIC_SUPABASE_URL=https://[프로젝트ID].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[anon key]

# Gemini
GEMINI_API_KEY=AIza...

# Polar
POLAR_ACCESS_TOKEN=...
POLAR_WEBHOOK_SECRET=...
POLAR_PRODUCT_ID_PRO=...
POLAR_PRODUCT_ID_UNLIMITED=...

# 암호화
ENCRYPTION_KEY=[64자리 hex]
HASH_KEY=[64자리 hex]
</code></pre>
<blockquote>
<p>⚠️ <strong>중요:</strong> <code>GEMINI_API_KEY</code>는 서버 사이드에서만 사용되니까 <code>NEXT_PUBLIC_</code> 접두사를 <strong>붙이지 마세요.</strong></p>
</blockquote>
<hr>
<blockquote>
<p>🎉 <strong>축하합니다!</strong> 여기까지 따라오셨다면 아이디어 하나로 시작해서 로그인, 결제, 배포, 보안까지 갖춘 진짜 SaaS 서비스를 만드신 겁니다. <strong>벽은 생각보다 낮습니다.</strong></p>
</blockquote>