cmux-billing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesecmux Billing
cmux账单
Read before changing billing, pricing, Stripe, Pro entitlement, checkout, webhook, or subscription code.
在修改账单、定价、Stripe、专业版权限、结账、webhook或订阅相关代码前,请先阅读本文档。
Architecture map
架构图
- creates Stripe Checkout Sessions for Pro when
/api/billing/checkoutis set. It setsSTRIPE_SECRET_KEYto 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 orclient_reference_id. The "already active" short-circuit lives here.plan=team - resolves the current Stack user, looks up their
/api/billing/portalrow, and creates a Stripe customer portal session returning tostripe_customers./pricing - cancels or resumes the active Stripe Pro subscription;
/api/billing/subscriptionrenders localized in-dashboard plan state and self-serve actions./dashboard/billing - is the shared idempotent recorder used by
web/services/billing/purchase.tsand/api/billing/complete. It attaches email to the purchaser, records/api/stripe/webhookon conflict, and never cross-grants based on an unverified email.billing_email_claims - in Stack
cmuxPlanis the only entitlement VM code reads; aclientReadOnlyMetadatamanual override wins.cmuxVmPlanORs legacy Stack products with activeresolveProPlanStatusrows.stripe_subscriptions - is signature-verified, insert-first idempotent through
/api/stripe/webhook, safe for foreign events in the shared Stripe account, and gates cmux handling onstripe_webhook_events. Return 2xx only after durable writes; return 500 to make Stripe retry.metadata.app === "cmux"
- 在设置
/api/billing/checkout后为专业版创建Stripe Checkout会话。它将STRIPE_SECRET_KEY设置为Stack用户ID,为未登录买家自动创建匿名Stack用户,当未设置Stripe或client_reference_id时回退到旧版Stack购买流程。“已激活”短路逻辑在此处实现。plan=team - 解析当前Stack用户,查找其
/api/billing/portal行,并创建返回至stripe_customers的Stripe客户门户会话。/pricing - 取消或恢复活跃的Stripe专业版订阅;
/api/billing/subscription渲染本地化的仪表盘内套餐状态及自助操作界面。/dashboard/billing - 是
web/services/billing/purchase.ts和/api/billing/complete共用的幂等记录器。它将邮箱附加到购买者,在冲突时记录/api/stripe/webhook,且绝不会基于未验证的邮箱跨账户授予权限。billing_email_claims - Stack 中的
clientReadOnlyMetadata是权限VM代码读取的唯一依据;手动覆盖的cmuxPlan优先级更高。cmuxVmPlan会将旧版Stack产品与活跃的resolveProPlanStatus行进行逻辑或运算。stripe_subscriptions - 经过签名验证,通过
/api/stripe/webhook实现插入优先的幂等性,可安全处理共享Stripe账户中的外部事件,并通过stripe_webhook_events控制cmux相关处理。仅在完成持久化写入后返回2xx状态码;返回500状态码可让Stripe重试。metadata.app === "cmux"
Dev workflow
开发工作流
- Use .
web/scripts/stripe/dev-stack.sh - The tagged app bakes into
CMUX_PORT; run the dev server on the tag's printed port, never a hardcoded one.Info.plist - Per-branch Docker Postgres ports collide with other agents' containers. Use and never stop containers you did not create.
--db-port - requires
/app-pricing.cmux_app=1threads the native deeplink return scheme;cmux_schemeschemes are honored only for localhost requests.cmux-dev-* - Repeat dogfood: use a private window for a fresh anonymous buyer, and to un-Pro a signed-in dev account before retesting checkout.
web/scripts/stripe/dev-reset.sh <email>
- 使用脚本。
web/scripts/stripe/dev-stack.sh - 打标签的应用会将写入
CMUX_PORT;请在标签打印的端口上运行开发服务器,切勿使用硬编码端口。Info.plist - 各分支的Docker Postgres端口会与其他代理的容器冲突。请使用参数,且切勿停止非你创建的容器。
--db-port - 需要设置
/app-pricing。cmux_app=1用于传递原生深度链接返回协议;cmux_scheme协议仅对localhost请求生效。cmux-dev-* - 重复内部测试:使用隐私窗口模拟新的匿名买家,并在重新测试结账前使用脚本取消已登录开发账户的专业版权限。
web/scripts/stripe/dev-reset.sh <email>
Test-mode resources
测试模式资源
Product . New checkouts use ($30/mo) and ($288/yr, equivalent to $24/mo). Keep ($240/yr) active for grandfathered subscriptions. Staging webhook endpoint forwards to ; its secrets are already in the Vercel project.
prod_UpIQRE6cj0nFjscmux-pro-monthlycmux-pro-yearly-288cmux-pro-yearlywe_1Tq1SZGhInAdn3JbWJReKNENcmux-staging.vercel.appcmux-staging产品ID为。新结账使用(每月30美元)和(每年288美元,相当于每月24美元)。需保持(每年240美元)活跃,以支持老用户订阅。 staging环境webhook端点会转发至;其密钥已配置在 Vercel项目中。
prod_UpIQRE6cj0nFjscmux-pro-monthlycmux-pro-yearly-288cmux-pro-yearlywe_1Tq1SZGhInAdn3JbWJReKNENcmux-staging.vercel.appcmux-stagingFeature flags
功能开关
pro-upgrade-ui-enabled-release741838/api/billing/checkoutcmux __internal_flagspro-upgrade-ui-enabled-release741838/api/billing/checkoutcmux __internal_flagsProd runbook
生产环境运行手册
Run with an operator key, add the two Vercel envs, deploy, validate live with a 100-percent-off promotion code purchase, then cancel.
web/scripts/stripe/provision-live.shDB migrations: , , staging deploy, then . Never run migrations from builds. See the Cloud VM ops flow.
bun run cloud-vm:preflightbun run cloud-vm:migrate -- stagingbun run cloud-vm:migrate -- production使用操作员密钥运行脚本,添加两个Vercel环境变量,部署后使用100%折扣码进行购买验证,然后取消订阅。
web/scripts/stripe/provision-live.sh数据库迁移:执行、,部署staging环境,再执行。切勿从构建版本中运行迁移。请查看Cloud VM操作流程。
bun run cloud-vm:preflightbun run cloud-vm:migrate -- stagingbun run cloud-vm:migrate -- productionGotchas
注意事项
- 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
bun mock.module.Export named X not found - Tests must not depend on being set.
DATABASE_URL - drizzle-1.0-beta wraps pg errors in ; read
DrizzleQueryErrorfor the pgerror.causeandcode.constraint - Pages outside need a
app/[locale]bypass (likeproxy.tsand/app-pricing), or/billingrewrites them into the locale tree and they 404 through missing root layout tags. Those subtrees also need their own layout withnext-intlandhtml.body
- 是进程全局的,因此每个模块模拟必须包含其他测试套件文件导入的所有真实导出。缺失的导出仅会在CI测试顺序中以
bun mock.module错误形式显现。Export named X not found - 测试不得依赖已配置。
DATABASE_URL - drizzle-1.0-beta会将pg错误包装在中;需读取
DrizzleQueryError获取pg的error.cause和code信息。constraint - 之外的页面需要
app/[locale]绕过(如proxy.ts和/app-pricing),否则/billing会将其重写至区域树中,因缺少根布局标签而返回404。这些子树还需要各自包含next-intl和html的布局。body