|
| 1 | +# Convex Integration Plan — Browser-AI Account & Payment System |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +The extension currently has no backend — all settings, API keys, and profiles live in `chrome.storage.local`. We want to add user accounts so we can accept payments. Users choose one of two paths on install: |
| 6 | + |
| 7 | +1. **BYOK (Bring Your Own Key)** — free, works exactly as today |
| 8 | +2. **Paid Plan** — user pays a subscription, AI calls are proxied through Convex HTTP Actions using server-managed API keys |
| 9 | + |
| 10 | +## Architecture Overview |
| 11 | + |
| 12 | +``` |
| 13 | +┌─────────────────────────────────────────────────┐ |
| 14 | +│ Chrome Extension (packages/extension/) │ |
| 15 | +│ │ |
| 16 | +│ Sidepanel UI ──→ Convex Auth (login/signup) │ |
| 17 | +│ ──→ chrome.storage.local (BYOK) │ |
| 18 | +│ ──→ Convex DB (account, sub) │ |
| 19 | +│ │ |
| 20 | +│ Background ──→ BYOK: direct AI provider calls │ |
| 21 | +│ ──→ Paid: Convex HTTP Action proxy │ |
| 22 | +└──────────────────────┬──────────────────────────┘ |
| 23 | + │ |
| 24 | +┌──────────────────────▼──────────────────────────┐ |
| 25 | +│ Convex Backend (packages/backend/convex/) │ |
| 26 | +│ │ |
| 27 | +│ Auth: Email/Password + Google + GitHub OAuth │ |
| 28 | +│ DB: users, subscriptions, usage │ |
| 29 | +│ HTTP Actions: /ai-proxy (streams AI responses) │ |
| 30 | +│ HTTP Actions: /stripe-webhook │ |
| 31 | +│ Actions: createCheckoutSession, manageSubscription│ |
| 32 | +└─────────────────────────────────────────────────┘ |
| 33 | +``` |
| 34 | + |
| 35 | +## Decisions Made |
| 36 | + |
| 37 | +- **AI Proxy:** Convex HTTP Actions (all-in-one, no extra infra) |
| 38 | +- **Auth:** Email/Password + Google OAuth + GitHub OAuth via `@convex-dev/auth` |
| 39 | +- **Location:** `packages/backend/` (new monorepo package) |
| 40 | +- **Payment:** Stripe Checkout + webhook handling |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Phase 1: Convex Backend Setup |
| 45 | + |
| 46 | +### 1.1 Create `packages/backend/` |
| 47 | + |
| 48 | +``` |
| 49 | +packages/backend/ |
| 50 | +├── package.json |
| 51 | +├── convex/ |
| 52 | +│ ├── schema.ts # DB schema |
| 53 | +│ ├── auth.ts # Auth config (providers) |
| 54 | +│ ├── http.ts # HTTP router (auth routes, stripe webhook, AI proxy) |
| 55 | +│ ├── users.ts # User queries/mutations |
| 56 | +│ ├── subscriptions.ts # Subscription queries/mutations |
| 57 | +│ ├── payments.ts # Stripe checkout action |
| 58 | +│ └── aiProxy.ts # AI proxy HTTP action |
| 59 | +└── .env.local # CONVEX_URL (gitignored) |
| 60 | +``` |
| 61 | + |
| 62 | +### 1.2 Schema (`convex/schema.ts`) |
| 63 | + |
| 64 | +```typescript |
| 65 | +import { defineSchema, defineTable } from "convex/server"; |
| 66 | +import { v } from "convex/values"; |
| 67 | +import { authTables } from "@convex-dev/auth/server"; |
| 68 | + |
| 69 | +export default defineSchema({ |
| 70 | + ...authTables, |
| 71 | + |
| 72 | + // authTables already creates `users` with name, email, etc. |
| 73 | + |
| 74 | + subscriptions: defineTable({ |
| 75 | + userId: v.id("users"), |
| 76 | + plan: v.union(v.literal("free"), v.literal("pro")), |
| 77 | + stripeCustomerId: v.optional(v.string()), |
| 78 | + stripeSubscriptionId: v.optional(v.string()), |
| 79 | + status: v.union( |
| 80 | + v.literal("active"), |
| 81 | + v.literal("canceled"), |
| 82 | + v.literal("past_due"), |
| 83 | + v.literal("inactive") |
| 84 | + ), |
| 85 | + currentPeriodEnd: v.optional(v.number()), |
| 86 | + }) |
| 87 | + .index("by_userId", ["userId"]) |
| 88 | + .index("by_stripeCustomerId", ["stripeCustomerId"]) |
| 89 | + .index("by_stripeSubscriptionId", ["stripeSubscriptionId"]), |
| 90 | + |
| 91 | + usage: defineTable({ |
| 92 | + userId: v.id("users"), |
| 93 | + month: v.string(), // "2026-02" |
| 94 | + requestCount: v.number(), |
| 95 | + tokensUsed: v.number(), |
| 96 | + }) |
| 97 | + .index("by_userId_month", ["userId", "month"]), |
| 98 | +}); |
| 99 | +``` |
| 100 | + |
| 101 | +### 1.3 Auth Config (`convex/auth.ts`) |
| 102 | + |
| 103 | +- Providers: `Password`, `GitHub`, `Google` |
| 104 | +- Environment variables: `JWT_PRIVATE_KEY`, `JWKS`, `SITE_URL`, `AUTH_GITHUB_ID`, `AUTH_GITHUB_SECRET`, `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET` |
| 105 | + |
| 106 | +### 1.4 AI Proxy HTTP Action (`convex/aiProxy.ts` + route in `http.ts`) |
| 107 | + |
| 108 | +- Receives POST requests from the extension with: model, messages, settings |
| 109 | +- Validates the user's auth token (from Authorization header) |
| 110 | +- Checks subscription is active |
| 111 | +- Increments usage counters |
| 112 | +- Forwards the request to the AI provider (OpenAI/Anthropic) with server-side API keys |
| 113 | +- Streams the response back to the extension |
| 114 | +- Environment variables: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` |
| 115 | + |
| 116 | +### 1.5 Stripe Integration |
| 117 | + |
| 118 | +- `payments.ts`: `createCheckoutSession` action — creates Stripe Checkout session, returns URL |
| 119 | +- `http.ts`: `/stripe-webhook` route — handles `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted` |
| 120 | +- Environment variables: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PRO_PRICE_ID` |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Phase 2: Extension Client Integration |
| 125 | + |
| 126 | +### 2.1 New Files |
| 127 | + |
| 128 | +``` |
| 129 | +packages/extension/ |
| 130 | +├── convex/ |
| 131 | +│ └── client.ts # ConvexClient setup, auth helpers, chromeTokenStorage |
| 132 | +├── sidepanel/ui/ |
| 133 | +│ └── account/ |
| 134 | +│ ├── panel-account.ts # Account UI mixin (login/signup/manage) |
| 135 | +│ └── account-styles.css # Account panel styles |
| 136 | +``` |
| 137 | + |
| 138 | +### 2.2 Convex Client (`packages/extension/convex/client.ts`) |
| 139 | + |
| 140 | +- Creates `ConvexClient` from `"convex/browser"` with deployment URL (build-time constant) |
| 141 | +- Custom `chromeTokenStorage` using `chrome.storage.local` for auth tokens |
| 142 | +- Exports: `convexClient`, `signIn()`, `signOut()`, `getAuthState()`, `getSubscription()` |
| 143 | +- Uses `anyApi` from `"convex/server"` since backend is in separate package |
| 144 | + |
| 145 | +### 2.3 Account UI (`panel-account.ts`) |
| 146 | + |
| 147 | +New mixin on `SidePanelUI` following existing pattern: |
| 148 | +- **Onboarding screen** (shown on first install): "Use your own API keys" vs "Get a subscription" |
| 149 | +- **Login/signup form**: email/password fields, Google/GitHub OAuth buttons |
| 150 | +- **Account section** in settings: shows plan, usage, manage subscription button |
| 151 | +- Attaches to existing settings panel as a new collapsible section |
| 152 | + |
| 153 | +### 2.4 Background Service Changes (`background/service.ts`) |
| 154 | + |
| 155 | +Modify the AI call path: |
| 156 | +- On startup, initialize Convex client (if user has account) |
| 157 | +- Before each AI call, check: does user have own API key set? → use it directly (BYOK) |
| 158 | +- No API key + active subscription? → route through Convex HTTP Action proxy |
| 159 | +- No API key + no subscription? → show "add API key or subscribe" message |
| 160 | + |
| 161 | +### 2.5 Build Changes (`scripts/build.mjs`) |
| 162 | + |
| 163 | +- Add `CONVEX_URL` as esbuild `define` constant (from env or hardcoded for prod) |
| 164 | +- Ensure `convex/browser` module is bundled correctly for extension |
| 165 | +- No changes to entry points needed — new code imports into existing entries |
| 166 | + |
| 167 | +### 2.6 Manifest Changes |
| 168 | + |
| 169 | +- Add Convex deployment domain to `host_permissions`: `https://*.convex.cloud/*`, `https://*.convex.site/*` |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Phase 3: Payment Flow |
| 174 | + |
| 175 | +### User Journey (Paid Path) |
| 176 | + |
| 177 | +1. User clicks "Get a subscription" on onboarding (or "Upgrade" in settings) |
| 178 | +2. Extension calls `convexClient.action(anyApi.payments.createCheckoutSession, {})` |
| 179 | +3. Action returns Stripe Checkout URL |
| 180 | +4. Extension opens URL in new tab (`chrome.tabs.create`) |
| 181 | +5. User completes payment on Stripe |
| 182 | +6. Stripe webhook fires → Convex creates/updates subscription record |
| 183 | +7. Extension polls or subscribes to subscription status → unlocks paid features |
| 184 | +8. AI calls now routed through Convex proxy |
| 185 | + |
| 186 | +### User Journey (BYOK Path) |
| 187 | + |
| 188 | +1. User clicks "Use your own keys" on onboarding |
| 189 | +2. Extension shows existing settings panel (provider, API key, model) |
| 190 | +3. Works exactly as today — no Convex account required |
| 191 | +4. Optional: user can later create account for cloud sync / backup |
| 192 | + |
| 193 | +--- |
| 194 | + |
| 195 | +## Files to Create |
| 196 | + |
| 197 | +| File | Purpose | |
| 198 | +|------|---------| |
| 199 | +| `packages/backend/package.json` | Backend package config | |
| 200 | +| `packages/backend/convex/schema.ts` | Database schema | |
| 201 | +| `packages/backend/convex/auth.ts` | Auth providers config | |
| 202 | +| `packages/backend/convex/http.ts` | HTTP router (auth, webhook, proxy) | |
| 203 | +| `packages/backend/convex/users.ts` | User queries/mutations | |
| 204 | +| `packages/backend/convex/subscriptions.ts` | Subscription CRUD | |
| 205 | +| `packages/backend/convex/payments.ts` | Stripe checkout action | |
| 206 | +| `packages/backend/convex/aiProxy.ts` | AI proxy HTTP action | |
| 207 | +| `packages/extension/convex/client.ts` | Extension Convex client | |
| 208 | +| `packages/extension/sidepanel/ui/account/panel-account.ts` | Account UI mixin | |
| 209 | +| `packages/extension/sidepanel/styles/account.css` | Account styles | |
| 210 | + |
| 211 | +## Files to Modify |
| 212 | + |
| 213 | +| File | Change | |
| 214 | +|------|--------| |
| 215 | +| `packages/extension/manifest.json` | Add Convex domains to host_permissions | |
| 216 | +| `packages/extension/background/service.ts` | Add subscription check + proxy routing | |
| 217 | +| `packages/extension/sidepanel/ui/core/panel-core.ts` | Initialize account UI, add account section | |
| 218 | +| `packages/extension/sidepanel/ui/settings/panel-settings.ts` | Add account section to settings | |
| 219 | +| `packages/extension/sidepanel/templates/main.html` | Add account UI containers | |
| 220 | +| `packages/extension/sidepanel/panel.ts` | Import account mixin | |
| 221 | +| `scripts/build.mjs` | Add CONVEX_URL define | |
| 222 | +| `package.json` (root) | Add backend workspace, convex deps | |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +## Verification |
| 227 | + |
| 228 | +1. **Backend:** `cd packages/backend && npx convex dev` — schema deploys, functions sync |
| 229 | +2. **Auth:** Sign up with email, verify login works, test Google/GitHub OAuth |
| 230 | +3. **BYOK flow:** Existing behavior unchanged — enter API key, chat works |
| 231 | +4. **Paid flow:** Create checkout → pay with Stripe test card → subscription activates → AI calls proxy through Convex |
| 232 | +5. **Proxy:** Send a message as paid user → response streams back via Convex HTTP action |
| 233 | +6. **Cancellation:** Cancel in Stripe → webhook fires → subscription deactivated → proxy stops working |
| 234 | +7. **Build:** `npm run build` from root succeeds, extension loads in Chrome |
| 235 | + |
| 236 | +--- |
| 237 | + |
| 238 | +## Implementation Order |
| 239 | + |
| 240 | +1. Backend setup (schema, auth, basic functions) — can test independently via Convex dashboard |
| 241 | +2. Extension client + account UI (login/signup flow) |
| 242 | +3. Stripe integration (checkout, webhook, subscription management) |
| 243 | +4. AI proxy HTTP action (the most complex piece) |
| 244 | +5. Background service routing (BYOK vs proxy decision logic) |
| 245 | +6. Polish (onboarding flow, error handling, loading states) |
0 commit comments