[{"data":1,"prerenderedAt":948},["ShallowReactive",2],{"navigation:en":3,"\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments:en":311},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Nuxfire Docs","\u002Fdocs","docs\u002F0.index",[9,11,152],{"title":5,"path":6,"stem":7,"icon":10},"i-ph-squares-four",{"title":12,"path":13,"stem":14,"children":15,"icon":151},"SaaS Starter Kit","\u002Fdocs\u002Fsaas-starter-kit","docs\u002F1.saas-starter-kit\u002F0.index",[16,19,63,129],{"title":17,"path":13,"stem":14,"icon":18},"What is the SaaS Starter Kit?","i-ph-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-ph-play",true,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started","docs\u002F1.saas-starter-kit\u002F1.getting-started",[26,30,34,38,42,46,50,54,58],{"title":27,"path":28,"stem":29},"Quick Start","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fquick-start","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F0.quick-start",{"title":31,"path":32,"stem":33},"Installation","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Finstallation","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F1.installation",{"title":35,"path":36,"stem":37},"Cloudflare Access","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fcloudflare-access","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F2.cloudflare-access",{"title":39,"path":40,"stem":41},"Configuration","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fconfiguration","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F3.configuration",{"title":43,"path":44,"stem":45},"Secrets","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fsecrets","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F4.secrets",{"title":47,"path":48,"stem":49},"Environment Variables","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables",{"title":51,"path":52,"stem":53},"Deploy","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fdeploy","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F6.deploy",{"title":55,"path":56,"stem":57},"Local Development","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Flocal-development","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F7.local-development",{"title":59,"path":60,"stem":61},"Project Structure","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fproject-structure","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F8.project-structure",false,{"title":64,"icon":65,"hide":22,"path":66,"stem":67,"children":68,"page":62},"Features","i-ph-cube","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures","docs\u002F1.saas-starter-kit\u002F2.features",[69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":70,"path":71,"stem":72},"Authentication","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fauth","docs\u002F1.saas-starter-kit\u002F2.features\u002F1.auth",{"title":74,"path":75,"stem":76},"Rate Limiting","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frate-limiting","docs\u002F1.saas-starter-kit\u002F2.features\u002F10.rate-limiting",{"title":78,"path":79,"stem":80},"WebSockets","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fwebsockets","docs\u002F1.saas-starter-kit\u002F2.features\u002F11.websockets",{"title":82,"path":83,"stem":84},"Blog","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fblog","docs\u002F1.saas-starter-kit\u002F2.features\u002F14.blog",{"title":86,"path":87,"stem":88},"Documentation","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdocumentation","docs\u002F1.saas-starter-kit\u002F2.features\u002F15.documentation",{"title":90,"path":91,"stem":92},"Analytics","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fanalytics","docs\u002F1.saas-starter-kit\u002F2.features\u002F16.analytics",{"title":94,"path":95,"stem":96},"Platform Admin Console","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fplatform-admin","docs\u002F1.saas-starter-kit\u002F2.features\u002F17.platform-admin",{"title":98,"path":99,"stem":100},"Database","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdatabase","docs\u002F1.saas-starter-kit\u002F2.features\u002F2.database",{"title":102,"path":103,"stem":104},"Payments & Billing","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments","docs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments",{"title":106,"path":107,"stem":108},"Emails","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Femails","docs\u002F1.saas-starter-kit\u002F2.features\u002F4.emails",{"title":110,"path":111,"stem":112},"Notifications","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications","docs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications",{"title":114,"path":115,"stem":116},"Role-Based Access Control","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frbac","docs\u002F1.saas-starter-kit\u002F2.features\u002F6.rbac",{"title":118,"path":119,"stem":120},"Async Jobs","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fasync-jobs","docs\u002F1.saas-starter-kit\u002F2.features\u002F7.async-jobs",{"title":122,"path":123,"stem":124},"Storage","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fstorage","docs\u002F1.saas-starter-kit\u002F2.features\u002F8.storage",{"title":126,"path":127,"stem":128},"Internationalization","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Finternationalization","docs\u002F1.saas-starter-kit\u002F2.features\u002F9.internationalization",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":62},"Concepts","i-ph-graduation-cap","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts","docs\u002F1.saas-starter-kit\u002F3.concepts",[135,139,143,147],{"title":136,"path":137,"stem":138},"Technology Stack","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Ftech-stack","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F0.tech-stack",{"title":140,"path":141,"stem":142},"SST.dev","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fsst","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F1.sst",{"title":144,"path":145,"stem":146},"NuxtHub","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fnuxt-hub","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F2.nuxt-hub",{"title":148,"path":149,"stem":150},"Design System & Branding","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fdesign-system","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F3.design-system","i-ph-lightning",{"title":153,"path":154,"stem":155,"children":156,"icon":159},"Fireskills","\u002Fdocs\u002Ffireskills","docs\u002F2.fireskills\u002F0.index",[157,160,219,249,275,293],{"title":158,"path":154,"stem":155,"icon":159},"What is Fireskills?","i-ph-fire",{"title":20,"icon":21,"hide":22,"path":161,"stem":162,"children":163,"page":62},"\u002Fdocs\u002Ffireskills\u002Fgetting-started","docs\u002F2.fireskills\u002F1.getting-started",[164,167,171,175,179,183,187,191,195,199,203,207,211,215],{"title":27,"path":165,"stem":166},"\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fquick-start","docs\u002F2.fireskills\u002F1.getting-started\u002F00.quick-start",{"title":168,"path":169,"stem":170},"Workflow & Governance","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fworkflow","docs\u002F2.fireskills\u002F1.getting-started\u002F01.workflow",{"title":172,"path":173,"stem":174},"Project Context","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fproject-context","docs\u002F2.fireskills\u002F1.getting-started\u002F02.project-context",{"title":176,"path":177,"stem":178},"Step 0: Setup","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-setup","docs\u002F2.fireskills\u002F1.getting-started\u002F03.step-setup",{"title":180,"path":181,"stem":182},"Step 1: Inbox","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-inbox","docs\u002F2.fireskills\u002F1.getting-started\u002F04.step-inbox",{"title":184,"path":185,"stem":186},"Step 2: Backlog","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-backlog","docs\u002F2.fireskills\u002F1.getting-started\u002F05.step-backlog",{"title":188,"path":189,"stem":190},"Step 3: Specify","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-specify","docs\u002F2.fireskills\u002F1.getting-started\u002F06.step-specify",{"title":192,"path":193,"stem":194},"Step 4: Validate","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-validate","docs\u002F2.fireskills\u002F1.getting-started\u002F07.step-validate",{"title":196,"path":197,"stem":198},"Step 5: Tasks","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tasks","docs\u002F2.fireskills\u002F1.getting-started\u002F08.step-tasks",{"title":200,"path":201,"stem":202},"Step 6: TDD\u002FBDD","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tdd-bdd","docs\u002F2.fireskills\u002F1.getting-started\u002F09.step-tdd-bdd",{"title":204,"path":205,"stem":206},"Step 7: Implement","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-implement","docs\u002F2.fireskills\u002F1.getting-started\u002F10.step-implement",{"title":208,"path":209,"stem":210},"Step 8: Documentator","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-documentator","docs\u002F2.fireskills\u002F1.getting-started\u002F11.step-documentator",{"title":212,"path":213,"stem":214},"Late Changes","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Flate-changes","docs\u002F2.fireskills\u002F1.getting-started\u002F12.late-changes",{"title":216,"path":217,"stem":218},"Other Entry Points","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fother-entry-points","docs\u002F2.fireskills\u002F1.getting-started\u002F13.other-entry-points",{"title":220,"icon":221,"hide":22,"path":222,"stem":223,"children":224,"page":62},"Methodology","i-ph-git-branch","\u002Fdocs\u002Ffireskills\u002Fmethodology","docs\u002F2.fireskills\u002F2.methodology",[225,229,233,237,241,245],{"title":226,"path":227,"stem":228},"The Three Acts","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-acts","docs\u002F2.fireskills\u002F2.methodology\u002F0.three-acts",{"title":230,"path":231,"stem":232},"The Three Gates","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-gates","docs\u002F2.fireskills\u002F2.methodology\u002F1.three-gates",{"title":234,"path":235,"stem":236},"The Canonical Spec","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fcanonical-spec","docs\u002F2.fireskills\u002F2.methodology\u002F2.canonical-spec",{"title":238,"path":239,"stem":240},"Lifecycle States","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Flifecycle-states","docs\u002F2.fireskills\u002F2.methodology\u002F3.lifecycle-states",{"title":242,"path":243,"stem":244},"Effort Scale","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Feffort-scale","docs\u002F2.fireskills\u002F2.methodology\u002F4.effort-scale",{"title":246,"path":247,"stem":248},"Milestones & Governance","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fgovernance-and-milestones","docs\u002F2.fireskills\u002F2.methodology\u002F5.governance-and-milestones",{"title":250,"icon":251,"hide":22,"path":252,"stem":253,"children":254,"page":62},"Skills","i-ph-stack","\u002Fdocs\u002Ffireskills\u002Fskills","docs\u002F2.fireskills\u002F3.skills",[255,259,263,267,271],{"title":256,"path":257,"stem":258},"Catalog","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fcatalog","docs\u002F2.fireskills\u002F3.skills\u002F0.catalog",{"title":260,"path":261,"stem":262},"Act I: Define","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-1-define","docs\u002F2.fireskills\u002F3.skills\u002F1.act-1-define",{"title":264,"path":265,"stem":266},"Act II: Design & Prove","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-2-design-prove","docs\u002F2.fireskills\u002F3.skills\u002F2.act-2-design-prove",{"title":268,"path":269,"stem":270},"Act III: Deliver","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-3-deliver","docs\u002F2.fireskills\u002F3.skills\u002F3.act-3-deliver",{"title":272,"path":273,"stem":274},"Governance & Decisions","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fgovernance-and-decisions","docs\u002F2.fireskills\u002F3.skills\u002F4.governance-and-decisions",{"title":276,"icon":277,"hide":22,"path":278,"stem":279,"children":280,"page":62},"Specialists","i-ph-star-four","\u002Fdocs\u002Ffireskills\u002Fspecialists","docs\u002F2.fireskills\u002F4.specialists",[281,285,289],{"title":282,"path":283,"stem":284},"Overview","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Foverview","docs\u002F2.fireskills\u002F4.specialists\u002F0.overview",{"title":286,"path":287,"stem":288},"Nuxfire Stack","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fnuxfire-stack","docs\u002F2.fireskills\u002F4.specialists\u002F1.nuxfire-stack",{"title":290,"path":291,"stem":292},"Engineering Specialists","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fengineering-specialists","docs\u002F2.fireskills\u002F4.specialists\u002F2.engineering-specialists",{"title":294,"icon":295,"hide":22,"path":296,"stem":297,"children":298,"page":62},"CLI & Tooling","i-ph-terminal","\u002Fdocs\u002Ffireskills\u002Fcli","docs\u002F2.fireskills\u002F5.cli",[299,303,307],{"title":300,"path":301,"stem":302},"CLI Overview","\u002Fdocs\u002Ffireskills\u002Fcli\u002Foverview","docs\u002F2.fireskills\u002F5.cli\u002F0.overview",{"title":304,"path":305,"stem":306},"Commands","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fcommands","docs\u002F2.fireskills\u002F5.cli\u002F1.commands",{"title":308,"path":309,"stem":310},"Safety Scripts","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fgovernance-scripts","docs\u002F2.fireskills\u002F5.cli\u002F2.governance-scripts",{"id":312,"title":102,"body":313,"description":943,"extension":944,"meta":945,"navigation":22,"path":103,"seo":946,"stem":104,"__hash__":947},"docs\u002Fdocs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments.md",{"type":314,"value":315,"toc":932},"minimark",[316,321,325,341,356,361,368,378,382,393,565,569,632,636,643,714,718,737,743,829,832,836,847,922,928],[317,318,320],"h2",{"id":319},"payment-provider","Payment Provider",[322,323,324],"p",{},"Stripe is the only payment provider. Plans, subscriptions and the buy flow all run through it.",[322,326,327,328,332,333,336,337,340],{},"Every paid ",[329,330,331],"code",{},"Plan"," row is backed by a Stripe Product and Price (",[329,334,335],{},"stripeProductId","\u002F",[329,338,339],{},"stripePriceId","); the Free plan has neither.",[322,342,343,344,347,348,351,352,355],{},"Each plan also says where a buyer lands after paying: in a team of their own (",[329,345,346],{},"NEW_TEAM",", the buyer becomes its Owner) or in an existing team you choose (",[329,349,350],{},"EXISTING_TEAM","). You set it in the plan form at ",[329,353,354],{},"\u002Fadmin\u002Fplans",". The checkout that acts on this setting is the anonymous Buy flow described below.",[357,358,360],"h3",{"id":359},"webhook-handlers","Webhook Handlers",[322,362,363,364,367],{},"Located in ",[329,365,366],{},"layers\u002Fbilling\u002Fserver\u002Fapi\u002Fwebhooks\u002F",":",[369,370,371],"ul",{},[372,373,374,377],"li",{},[329,375,376],{},"stripe.post.ts"," — handles Stripe subscription events",[357,379,381],{"id":380},"client-integration","Client Integration",[322,383,384,385,388,389,392],{},"Checkout starts on the billing page of a Space (",[329,386,387],{},"layers\u002Fbilling\u002Fapp\u002Fpages\u002F[[team]]\u002Fsettings\u002Fbilling.vue","). It calls ",[329,390,391],{},"stripe.createStripeCheckoutSession"," and sends the browser to the hosted Stripe Checkout URL:",[394,395,400],"pre",{"className":396,"code":397,"language":398,"meta":399,"style":399},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","const session = await useTrpcClient().stripe.createStripeCheckoutSession.mutate({\n  priceId: plan.stripePriceId,\n  teamId: team.id,\n  redirectUrl: window.location.pathname,\n});\nawait navigateTo(session.url, { external: true });\n","typescript","",[329,401,402,454,473,491,514,526],{"__ignoreMap":399},[403,404,407,411,415,419,423,427,431,435,438,440,443,445,448,451],"span",{"class":405,"line":406},"line",1,[403,408,410],{"class":409},"srJo8","const",[403,412,414],{"class":413},"s91G_"," session",[403,416,418],{"class":417},"sGXK2"," =",[403,420,422],{"class":421},"sRxSC"," await",[403,424,426],{"class":425},"sD0ED"," useTrpcClient",[403,428,430],{"class":429},"ss--_","()",[403,432,434],{"class":433},"swvn1",".",[403,436,437],{"class":429},"stripe",[403,439,434],{"class":433},[403,441,442],{"class":429},"createStripeCheckoutSession",[403,444,434],{"class":433},[403,446,447],{"class":425},"mutate",[403,449,450],{"class":429},"(",[403,452,453],{"class":433},"{\n",[403,455,457,461,463,466,468,470],{"class":405,"line":456},2,[403,458,460],{"class":459},"squCx","  priceId",[403,462,367],{"class":433},[403,464,465],{"class":429}," plan",[403,467,434],{"class":433},[403,469,339],{"class":429},[403,471,472],{"class":433},",\n",[403,474,476,479,481,484,486,489],{"class":405,"line":475},3,[403,477,478],{"class":459},"  teamId",[403,480,367],{"class":433},[403,482,483],{"class":429}," team",[403,485,434],{"class":433},[403,487,488],{"class":429},"id",[403,490,472],{"class":433},[403,492,494,497,499,502,504,507,509,512],{"class":405,"line":493},4,[403,495,496],{"class":459},"  redirectUrl",[403,498,367],{"class":433},[403,500,501],{"class":429}," window",[403,503,434],{"class":433},[403,505,506],{"class":429},"location",[403,508,434],{"class":433},[403,510,511],{"class":429},"pathname",[403,513,472],{"class":433},[403,515,517,520,523],{"class":405,"line":516},5,[403,518,519],{"class":433},"}",[403,521,522],{"class":429},")",[403,524,525],{"class":433},";\n",[403,527,529,532,535,538,540,543,546,549,552,554,558,561,563],{"class":405,"line":528},6,[403,530,531],{"class":421},"await",[403,533,534],{"class":425}," navigateTo",[403,536,537],{"class":429},"(session",[403,539,434],{"class":433},[403,541,542],{"class":429},"url",[403,544,545],{"class":433},",",[403,547,548],{"class":433}," {",[403,550,551],{"class":459}," external",[403,553,367],{"class":433},[403,555,557],{"class":556},"s8HiA"," true",[403,559,560],{"class":433}," }",[403,562,522],{"class":429},[403,564,525],{"class":433},[317,566,568],{"id":567},"setup-steps","Setup Steps",[570,571,572,583,613],"ol",{},[372,573,574,575,577,578,582],{},"Deploy, then sign in as a platform admin and open ",[329,576,354],{}," > ",[579,580,581],"strong",{},"Connections",". Paste the provider's API key (and, for Stripe, the webhook signing secret) there. The key is tested before it is saved and is never shown again.",[372,584,585,586,588,589,592,593,596,599,600,603,604,607,608,336,610,612],{},"Create plans at ",[329,587,354],{}," — see ",[590,591,94],"a",{"href":95},". This is a platform-owner action, not a seed script: plans aren't hardcoded, so price ids stay per-environment (test vs. live) without touching code.",[594,595],"br",{},[579,597,598],{},"For a Stripe-managed plan, no price id is pasted by hand."," The admin form takes a decimal amount, currency, and billing period; ",[329,601,602],{},"layers\u002Fbilling\u002Fserver\u002Futils\u002Fstripe-catalog.ts","'s ",[329,605,606],{},"syncStripePlanCatalog()"," creates (or updates) the Stripe Product and creates a new Price via the Stripe API whenever amount\u002Fcurrency\u002Fperiod actually changes — Prices are immutable on Stripe, so a change always mints a new Price and archives the old one under the same Product. ",[329,609,339],{},[329,611,335],{}," are written by that sync and never typed by hand.",[372,614,615,616,367,618],{},"Set up the webhook endpoint in the provider's dashboard, using the URL shown in ",[579,617,581],{},[369,619,620,626],{},[372,621,622,623],{},"Stripe: ",[329,624,625],{},"\u002Fapi\u002Fwebhooks\u002Fbilling\u002Fstripe",[372,627,628,629],{},"Asaas: ",[329,630,631],{},"\u002Fapi\u002Fwebhooks\u002Fbilling\u002Fasaas",[317,633,635],{"id":634},"buying-without-an-account-the-buy-cta","Buying Without an Account (the Buy CTA)",[322,637,638,639,642],{},"A visitor with no account and no team can pay for a plan using only an email address, then claim what they paid for after creating an account. This is what powers the landing page's (",[329,640,641],{},"apps\u002Fweb",") Buy CTA.",[570,644,645,660,690,706],{},[372,646,647,652,653,656,657,659],{},[579,648,649],{},[329,650,651],{},"\u002Fbuy\u002F[planId]"," (",[329,654,655],{},"apps\u002Fapp",", public) — the buyer picks a ",[329,658,331],{}," (by its id in the URL), enters an email, and pays through Stripe Checkout. No account or team exists yet at this point.",[372,661,662,652,667,670,671,674,675,336,678,681,682,685,686,689],{},[579,663,664],{},[329,665,666],{},"PendingPurchase",[329,668,669],{},"layers\u002Fbilling\u002Fserver\u002Fdatabase\u002Fschema.ts",") — created at checkout start, confirmed by Stripe's webhook once the payment\u002Fsubscription is active. States: ",[329,672,673],{},"PENDING → CLAIMED",", or ",[329,676,677],{},"EXPIRED",[329,679,680],{},"REJECTED",". An unclaimed purchase expires 30 days after creation. The webhook also sends the buyer a claim e-mail (",[329,683,684],{},"PurchaseClaim"," template, ",[329,687,688],{},"packages\u002Femails",").",[372,691,692,652,697,699,700,702,703,705],{},[579,693,694],{},[329,695,696],{},"\u002Fclaim\u002F[id]",[329,698,655],{},", protected — the buyer creates an account or logs in first, with the same e-mail) — finishes what the admin configured for that plan: a brand-new team with the buyer as Owner (",[329,701,346],{},"), or membership in the chosen existing team as Member (",[329,704,350],{},"). A destination that is no longer valid (the existing team was deleted or suspended) is refused safely — nothing is created, and the case stays visible to the platform admin.",[372,707,708,713],{},[579,709,710],{},[329,711,712],{},"\u002Fadmin\u002Fpending-purchases"," — every purchase, all statuses, for investigating a payment that never turned into access.",[357,715,717],{"id":716},"wiring-the-landing-pages-cta-no-redeploy-needed","Wiring the landing page's CTA — no redeploy needed",[322,719,720,721,652,723,726,727,730,731,733,734,434],{},"Which plan the Buy CTA on ",[329,722,641],{},[329,724,725],{},"CTAButton",", both the hero and the pricing card) sells is a database flag, not a deploy-time setting: ",[329,728,729],{},"Plan.isPublicOffer"," (at most one plan holds it at a time). Toggle it from ",[329,732,354],{}," — the \"Show on landing page\" checkbox in the plan form, or the row menu's \"Show on landing page\" \u002F \"Remove from landing page\" action — and the CTA updates on the next page load, for every visitor, with ",[579,735,736],{},"no env var and no redeploy",[322,738,739,740,742],{},"How the landing page (a separate Cloudflare Worker, its own domain) reads a database row that lives behind ",[329,741,655],{},"'s API without exposing that API publicly:",[570,744,745,761,799,812],{},[372,746,747,750,751,753,754,757,758,434],{},[329,748,749],{},"layers\u002Fbilling\u002Fserver\u002Fapi\u002Fpublic\u002Foffer.get.ts"," — a public, unauthenticated route on ",[329,752,655],{}," returning whichever plan has ",[329,755,756],{},"isPublicOffer: true"," (and isn't archived), or ",[329,759,760],{},"null",[372,762,763,766,767,770,771,774,775,778,779,782,783,786,787,790,791,794,795,798],{},[329,764,765],{},"infra\u002Fweb.ts"," links the ",[329,768,769],{},"App"," Worker into the ",[329,772,773],{},"Web"," Worker's ",[329,776,777],{},"link"," array — the same Cloudflare ",[579,780,781],{},"service binding"," pattern this project already uses for ",[329,784,785],{},"App → Auth"," (see the comment on ",[329,788,789],{},"link: [auth]"," in ",[329,792,793],{},"infra\u002Fapp.ts",", quoting Cloudflare's own best practices against a plain ",[329,796,797],{},"fetch()"," between Workers). No CORS, no public exposure: the call never leaves Cloudflare's network.",[372,800,801,804,805,808,809,811],{},[329,802,803],{},"apps\u002Fweb\u002Fserver\u002Fapi\u002Foffer.get.ts"," calls that route through the binding (",[329,806,807],{},"event.context.cloudflare.env.AppWorker",", falling back to a plain ",[329,810,797],{}," for local dev where the binding may not be wired) and returns the plan JSON to the browser.",[372,813,814,817,818,821,822,825,826,828],{},[329,815,816],{},"apps\u002Fweb\u002Fapp\u002Fcomposables\u002Fuse-offer.ts"," fetches ",[329,819,820],{},"\u002Fapi\u002Foffer"," and builds ",[329,823,824],{},"${appUrl}\u002Fbuy\u002F\u003CplanId>"," — the same URL shape as ",[329,827,651],{}," above — with no configuration of any kind.",[322,830,831],{},"No plan flagged yet: the CTA falls back to the same \"coming soon\" state it always had.",[357,833,835],{"id":834},"the-free-plan","The Free plan",[322,837,838,839,842,843,846],{},"Exactly one plan is created automatically, by ",[329,840,841],{},"apps\u002Ffunctions\u002Fsrc\u002Fdatabase\u002Fseed\u002Findex.ts"," on every deploy (idempotent — it checks for an existing ",[329,844,845],{},"tier: \"FREE\""," row first):",[394,848,851],{"className":396,"code":849,"filename":850,"language":398,"meta":399,"style":399},"export const FREE_PLAN: InsertPlan = {\n  name: \"Free Tier\",\n  tier: \"FREE\",\n  \u002F\u002F ...\n};\n","apps\u002Ffunctions\u002Fsrc\u002Fdatabase\u002Fseed\u002Fdata.ts",[329,852,853,875,895,911,917],{"__ignoreMap":399},[403,854,855,858,861,864,866,870,872],{"class":405,"line":406},[403,856,857],{"class":421},"export",[403,859,860],{"class":409}," const",[403,862,863],{"class":413}," FREE_PLAN",[403,865,367],{"class":417},[403,867,869],{"class":868},"sKvfc"," InsertPlan",[403,871,418],{"class":417},[403,873,874],{"class":433}," {\n",[403,876,877,880,882,886,890,893],{"class":405,"line":456},[403,878,879],{"class":459},"  name",[403,881,367],{"class":433},[403,883,885],{"class":884},"siCPE"," \"",[403,887,889],{"class":888},"sLACW","Free Tier",[403,891,892],{"class":884},"\"",[403,894,472],{"class":433},[403,896,897,900,902,904,907,909],{"class":405,"line":475},[403,898,899],{"class":459},"  tier",[403,901,367],{"class":433},[403,903,885],{"class":884},[403,905,906],{"class":888},"FREE",[403,908,892],{"class":884},[403,910,472],{"class":433},[403,912,913],{"class":405,"line":493},[403,914,916],{"class":915},"ss7Ak","  \u002F\u002F ...\n",[403,918,919],{"class":405,"line":516},[403,920,921],{"class":433},"};\n",[322,923,924,925,927],{},"Every paid plan is deliberately left out of the seed — Stripe price ids are per-environment, so they're created through ",[329,926,354],{}," instead of being baked into a script.",[929,930,931],"style",{},"html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s8HiA, html code.shiki .s8HiA{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}",{"title":399,"searchDepth":456,"depth":456,"links":933},[934,938,939],{"id":319,"depth":456,"text":320,"children":935},[936,937],{"id":359,"depth":475,"text":360},{"id":380,"depth":475,"text":381},{"id":567,"depth":456,"text":568},{"id":634,"depth":456,"text":635,"children":940},[941,942],{"id":716,"depth":475,"text":717},{"id":834,"depth":475,"text":835},"Billing powered by Stripe.","md",{},{"title":102,"description":943},"cZw0PeuoD-at24CqFw9IoHFBfecr4nfTEaHF4FNh8Lk",1792616803887]