Skip to content

feat(saas): billing + plan gating (Free/Pro), server-enforced (slice 5, stacked on #216)#217

Merged
seonghobae merged 2 commits into
feat/saas-teamsfrom
feat/saas-billing
Jul 6, 2026
Merged

feat(saas): billing + plan gating (Free/Pro), server-enforced (slice 5, stacked on #216)#217
seonghobae merged 2 commits into
feat/saas-teamsfrom
feat/saas-billing

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

SaaS pivot — slice 5 of N (stacked on #216)

Monetization plumbing — fully buildable and testable without live Stripe keys.

Server

  • server/billing.mjs — plan config: Free = 2 projects / 3 members; Pro = unlimited, ₩19,000. Usage/limit helpers. createCheckout dynamically imports stripe only when STRIPE_SECRET_KEY is set (no hard dependency) — otherwise returns a mock checkout URL.
  • server/app.mjs — caps enforced server-side: POST /api/projects and invite-accept return 402 (+upgrade hint) past the Free limit. Routes: GET /api/orgs/:id/billing, POST /api/orgs/:id/checkout (owner), POST /api/stripe/webhook (upgrades plan on checkout.session.completed), and a dev-only POST _dev/activate-pro gated behind SCOPEWEAVE_DEV=1 (unreachable in prod).

Client (cloud-sync.js)

Team modal shows Free · 프로젝트 x/2 · 멤버 y/3 + a Pro 업그레이드 button (opens checkout; honest toast when Stripe isn't configured).

Verified

  • npm run test:api ✓ — Free caps (3rd project 402, 4th member 402), mock checkout, dev-activate → Pro → caps lifted.
  • app.js eval-safe ✓.
  • Real browser: team modal renders Free · 프로젝트 1/2 · 멤버 1/3 + upgrade button.

To go live (owner)

npm i stripe + set STRIPE_SECRET_KEY, STRIPE_PRICE_ID, STRIPE_WEBHOOK_SECRET. Webhook signature verification is a named ceiling before prod.

🤖 Generated with Claude Code

Slice 5: monetization plumbing. Fully testable without live Stripe keys.

- server/billing.mjs: plan config (Free = 2 projects / 3 members; Pro =
  unlimited, ₩19,000) + usage/limit helpers + createCheckout (dynamic-imports
  'stripe' ONLY when STRIPE_SECRET_KEY is set — no hard dep; else a mock URL).
- server/app.mjs: enforce caps server-side — POST /api/projects and invite
  accept return 402 (+upgrade hint) when over the Free limit. Billing routes:
  GET /api/orgs/:id/billing (plan+usage+limits), POST checkout (owner),
  POST /api/stripe/webhook (upgrades plan on checkout.session.completed),
  POST _dev/activate-pro (guarded: only when SCOPEWEAVE_DEV=1).
- cloud-sync.js: team modal shows "Free · 프로젝트 x/2 · 멤버 y/3" + Pro
  업그레이드 button (opens checkout; honest toast when Stripe isn't configured).
- tests/api/smoke.mjs: Free caps (3rd project 402, 4th member 402), mock
  checkout, dev-activate → Pro → caps lifted.

Live payments need: npm i stripe + STRIPE_SECRET_KEY/PRICE_ID/WEBHOOK_SECRET.
Webhook signature verification is a named ceiling before prod.

Verification: npm run test:api ✓ (full billing flow) · app.js eval-safe ✓ ·
real browser: team modal shows "Free · 프로젝트 1/2 · 멤버 1/3" + upgrade.

Stacked on #216.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018LMoqYsUY6usjNMBjvxCbU
@seonghobae seonghobae merged commit 92970e6 into feat/saas-teams Jul 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant