cmux-billing
Stripe checkout, pricing, subscription, Pro plan, webhook, and entitlement runbook for cmux billing work. Use when editing or debugging billing, pricing, Stripe Checkout, subscription recording, Pro plan status, webhooks, entitlement metadata, or pricing dev/prod tooling.
By manaflow-ai · 2,617 installs
npx skills add manaflow-ai/cmux --skill cmux-billing
Source repository · Upstream listing
cmux Billing
Read before changing billing, pricing, Stripe, Pro entitlement, checkout, webhook, or subscription code.
Architecture map
/api/billing/checkout creates Stripe Checkout Sessions for Pro when STRIPE SECRET KEY is set. It sets client reference id to the Stack user id, auto creates an anonymous Stack user for signed out buyers, and falls back to the legacy Stack purchase path when Stripe is unset or plan=team . The "already active" short circuit lives here.
/api/billing/portal resolves the current Stack user, looks up their stripe customers row, and creates a Stripe customer portal session returning to /pricing .
/api/billing/subscription cancels or resumes the active Stripe Pro subscription; /dashboard/billing renders localized in dashboard plan state and self serve actions.
web/services/billing/purchase.ts is the shared idempotent recorder used by /api/billing/complete and /api/stripe/webhook . It attaches email to the purchaser, records billing email claims on conflict, and never cross grants based on an unverified email.
VM code reads the plan from Stack clientReadOnlyMetadata : a non empty cmuxVmPlan (operator override) takes precedence, otherwise cmuxPlan (the Stripe mirror) supplies the entitlement. resolveProPlanStatus reports Pro for an active stripe subscriptions row or a paid cmuxVmPlan override ( pro , team , founders ); billingManagement stays Stripe only, so a granted account shows Pro without a portal link.
The private cmux admin app (https://cmux admin.vercel.app/pro, source admin/ in cmuxterm hq) lets verified cmux.com , manaflow.ai , and manaflow.com accounts search users, teams, and emails and, after a confirmation dialog: grant or remove the user cmuxVmPlan override ( /api/admin/users ), grant or remove a team override of team ( /api/admin/teams ), grant Pro to an email with no account yet ( /api/admin/email grants , stored in admin plan grants and applied by the after sign in callback once the mailbox is verified), and downgrade a paying customer by cancelling the Stripe subscription at period end ( /api/admin/subscriptions , same service as the self serve form). services/admin/access.ts is the gate, services/admin/proGrants.ts writes under the account mutation lease and records serverMetadata.cmuxAdminPlanGrant (who, when, which plan). Non admins get 403 on the API; there is no admin page on cmux.com. The admin plan grants migration is an operator step; until it runs, pending email grants report 503 and search omits them.
/api/stripe/webhook is signature verified, insert first idempotent through stripe webhook events , safe for foreign events in the shared Stripe account, and gates cmux handling on metadata.app === "cmux" . Return 2xx only after durable writes; return 500 to make Stripe retry.
Dev workflow
Use web/scripts/stripe/dev stack.sh .
Local bun dev accounts use the development Stack project and receive a
non persistent Pro entitlement automatically. This applies only when
CMUX LOCAL DEV PRO=1 , NODE ENV=development , VERCEL ENV is unset, and
NEXT PUBLIC STACK PROJECT ID is the development project. Release and
preview deployments remain billing backed. Use dev grant.sh only when
testing an explicit manual grant or a non local deployment.
The tagged app bakes CMUX PORT into Info.plist ; run the dev server on the tag's printed port, never a hardcoded one.
Per branch Docker Postgres ports collide with other agents' containers. Use db port and never stop containers you did not create.
/app pricing requires cmux app=1 . cmux scheme threads the native deeplink return scheme; cmux dev schemes are honored only for localhost requests.
Repeat dogfood: use a private window for a fresh anonymous buyer, and web/scripts/stripe/dev reset.sh <email to un Pro a signed in dev account before retesting checkout.
Fake payment, two ways: web/scripts/stripe/dev grant.sh <email writes cmuxVmPlan: "pro" directly (same override as the admin page) (instant, no checkout; undo with dev reset). For the full checkout path at $0, enter promotion code CMUXDEV100 in test mode checkout — a 100% off forever coupon in the test account; allow promotion codes is already set on checkout sessions.
Newer Stripe CLI prints stripe config list as key=value (older builds used key = 'value' ); dev stack.sh and dev reset.sh accept both. If key extraction fails, re run stripe login .
Catalog
Prices live in web/services/billing/plans.ts and are provisioned by web/scripts/stripe/provision catalog.sh . Current checkout keys: cmux pro monthly 50 ($50/mo), cmux pro yearly 480 ($480/yr, $40/mo equivalent), cmux team monthly 60 ($60/user/mo), cmux team yearly 576 ($576/user/yr, $48/mo equivalent). Stripe amounts are immutable, so a price change mints a new lookup key carrying the amount and adds the old key to LEGACY PRICE LOOKUP KEYS ; grandfathered Prices ( cmux pro monthly $30, cmux pro yearly $240, cmux pro yearly 288 $288, cmux team monthly $35, cmux team yearly 336 $336) stay active for the subscriptions on them and /dashboard/billing prices every row from its own Stripe amount. Env price id overrides carry the amount in their name ( STRIPE PRO MONTHLY 50 PRICE ID and friends); a retired name fails env validation, so delete it from Vercel before deploying a price change. Test mode Pro product prod UyHgRPpmCzrkLJ , live prod Uq4a28vk0fP3E6 . Staging webhook endpoint we 1Tq1SZGhInAdn3JbWJReKNEN forwards to cmux staging.vercel.app ; its secrets are already in the cmux staging Vercel project.
The paid Cloud VM allowance lives in web/services/vms/machineSpec.ts : Pro allows up to 50 machines for one paid account. Team grants 50 machines per paid seat. Each machine has its own CPU, memory, and disk; there is no aggregate resource pool. The default machine has 8 GB RAM and 32 GB disk. Disk grows independently up to 256 GB. The repository enforces machine counts under the billing team lock and tracks individual shapes for fork, snapshot, and resize recovery. Paid limit environment overrides are retired; the create kill switch remains the incident control.
Feature flags
pro upgrade ui enabled release (PostHog id 741838 ) gates all Pro UI and stays OFF in release until launch; DEBUG builds default it on. Public Pro and Team pricing CTAs always route through /api/billing/checkout , never the download confirmation page. cmux internal flags , once merged, inspects and overrides flags locally.
Prod runbook
Run web/scripts/stripe/provision live.sh with an operator key, add the two Vercel envs, deploy, validate live with a 100 percent off promotion code purchase, then cancel.
DB migrations: bun run cloud vm:preflight , bun run cloud vm:migrate staging , staging deploy, then bun run cloud vm:migrate production . Never run migrations from builds. See the Cloud VM ops flow.
Gotchas
bun mock.module is process global, so every module mock must carry every real export other suite files import. A missing export can surface only in CI's test order as Export named X not found .
Tests must not depend on DATABASE URL being set.
drizzle 1.0 beta wraps pg errors in DrizzleQueryError ; read error.cause for the pg code and constraint .
Pages outside app/[locale] need a proxy.ts bypass (like /app pricing and /billing ), or next intl rewrites them into the locale tree and they 404 through missing root layout tags. Those subtrees also need their own layout with html and body .