feat(saas): billing + plan gating (Free/Pro), server-enforced (slice 5, stacked on #216)#217
Merged
Merged
Conversation
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
This was referenced Jul 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.createCheckoutdynamically importsstripeonly whenSTRIPE_SECRET_KEYis set (no hard dependency) — otherwise returns a mock checkout URL.server/app.mjs— caps enforced server-side:POST /api/projectsand invite-accept return 402 (+upgradehint) past the Free limit. Routes:GET /api/orgs/:id/billing,POST /api/orgs/:id/checkout(owner),POST /api/stripe/webhook(upgrades plan oncheckout.session.completed), and a dev-onlyPOST _dev/activate-progated behindSCOPEWEAVE_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.Free · 프로젝트 1/2 · 멤버 1/3+ upgrade button.To go live (owner)
npm i stripe+ setSTRIPE_SECRET_KEY,STRIPE_PRICE_ID,STRIPE_WEBHOOK_SECRET. Webhook signature verification is a named ceiling before prod.🤖 Generated with Claude Code