[{"data":1,"prerenderedAt":2193},["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-lucide-layout-grid",{"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-lucide-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-lucide-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-lucide-box","\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-lucide-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-lucide-zap",{"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-lucide-flame",{"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-lucide-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-lucide-layers","\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-lucide-sparkles","\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-lucide-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":2188,"extension":2189,"meta":2190,"navigation":22,"path":103,"seo":2191,"stem":104,"__hash__":2192},"docs\u002Fdocs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments.md",{"type":314,"value":315,"toc":2175},"minimark",[316,321,331,335,339,344,1455,1470,1484,1488,1494,1508,1512,1523,1729,1736,1740,1887,1891,1898,1968,1972,1991,1997,2083,2086,2090,2101,2165,2171],[317,318,320],"h2",{"id":319},"supported-payment-providers","Supported Payment Providers",[322,323,324,328],"ul",{},[325,326,327],"li",{},"Stripe",[325,329,330],{},"Paddle",[332,333,334],"p",{},"Both providers are configured to work in parallel, letting you choose one or use both.",[317,336,338],{"id":337},"core-components","Core Components",[340,341,343],"h3",{"id":342},"database-schema","Database Schema",[345,346,352],"pre",{"className":347,"code":348,"filename":349,"language":350,"meta":351,"style":351},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","export const plans = pgTable(\"Plan\", {\n  id: text(\"id\").$default(() => cuid()).primaryKey().notNull(),\n  name: text(\"name\").notNull(),\n  description: text(\"description\").notNull(),\n  tier: text(\"tier\").default(\"FREE\").notNull(),\n  price: text(\"price\").notNull(),\n  period: text(\"period\").notNull(),\n  provider: text(\"provider\"),\n  providerPriceId: text(\"providerPriceId\"),\n  providerProductId: text(\"providerProductId\"), \u002F\u002F the Stripe Product behind providerPriceId — Prices are immutable, so a changed amount creates a new Price under the same Product\n  unitAmount: integer(\"unitAmount\"), \u002F\u002F minor units (e.g. cents); null for plans not managed by Stripe's catalog\n  currency: text(\"currency\"), \u002F\u002F lowercase ISO 4217; null alongside unitAmount\n  maxSeats: integer(\"maxSeats\"), \u002F\u002F null = unlimited\n  limits: jsonb(\"limits\"), \u002F\u002F open-ended bag for future quotas\n  buyerTeamMode: text(\"buyerTeamMode\").notNull().default(\"NEW_TEAM\"), \u002F\u002F \"NEW_TEAM\" or \"EXISTING_TEAM\"\n  buyerTeamId: text(\"buyerTeamId\"), \u002F\u002F the team an EXISTING_TEAM plan adds the buyer to\n  isArchived: boolean(\"isArchived\").notNull().default(false), \u002F\u002F soft delete\n  \u002F\u002F ...timestamps\n});\n\nexport const subscriptions = pgTable(\"Subscription\", {\n  id: text(\"id\").$default(() => cuid()).primaryKey().notNull(),\n  teamId: text(\"teamId\").notNull().references(() => teams.id),\n  status: text(\"status\").default(\"ACTIVE\").notNull(),\n  provider: text(\"provider\").notNull(),\n  providerSubscriptionId: text(\"providerSubscriptionId\").notNull(),\n  planId: text(\"planId\").notNull().references(() => plans.id),\n  currentPeriodStart: timestamp(\"currentPeriodStart\", { withTimezone: true }),\n  currentPeriodEnd: timestamp(\"currentPeriodEnd\", { withTimezone: true }),\n  cancelAtPeriodEnd: boolean(\"cancelAtPeriodEnd\").default(false).notNull(),\n  providerMetadata: jsonb(\"providerMetadata\"),\n  customerEmail: text(\"customerEmail\"),\n  lastPaymentDate: timestamp(\"lastPaymentDate\", { withTimezone: true }),\n  nextPaymentDate: timestamp(\"nextPaymentDate\", { withTimezone: true }),\n  \u002F\u002F ...timestamps\n});\n","layers\u002Fbilling\u002Fserver\u002Fdatabase\u002Fschema.ts","typescript","",[353,354,355,401,463,492,521,566,595,624,647,670,697,724,750,776,803,850,876,921,927,938,944,971,1018,1066,1110,1137,1166,1211,1251,1286,1325,1348,1371,1406,1441,1446],"code",{"__ignoreMap":351},[356,357,360,364,368,372,376,380,384,388,392,394,398],"span",{"class":358,"line":359},"line",1,[356,361,363],{"class":362},"sRxSC","export",[356,365,367],{"class":366},"srJo8"," const",[356,369,371],{"class":370},"s91G_"," plans",[356,373,375],{"class":374},"sGXK2"," =",[356,377,379],{"class":378},"sD0ED"," pgTable",[356,381,383],{"class":382},"ss--_","(",[356,385,387],{"class":386},"siCPE","\"",[356,389,391],{"class":390},"sLACW","Plan",[356,393,387],{"class":386},[356,395,397],{"class":396},"swvn1",",",[356,399,400],{"class":396}," {\n",[356,402,404,408,411,414,416,418,421,423,426,429,432,434,437,440,443,446,448,451,453,455,458,460],{"class":358,"line":403},2,[356,405,407],{"class":406},"squCx","  id",[356,409,410],{"class":396},":",[356,412,413],{"class":378}," text",[356,415,383],{"class":382},[356,417,387],{"class":386},[356,419,420],{"class":390},"id",[356,422,387],{"class":386},[356,424,425],{"class":382},")",[356,427,428],{"class":396},".",[356,430,431],{"class":378},"$default",[356,433,383],{"class":382},[356,435,436],{"class":396},"()",[356,438,439],{"class":366}," =>",[356,441,442],{"class":378}," cuid",[356,444,445],{"class":382},"())",[356,447,428],{"class":396},[356,449,450],{"class":378},"primaryKey",[356,452,436],{"class":382},[356,454,428],{"class":396},[356,456,457],{"class":378},"notNull",[356,459,436],{"class":382},[356,461,462],{"class":396},",\n",[356,464,466,469,471,473,475,477,480,482,484,486,488,490],{"class":358,"line":465},3,[356,467,468],{"class":406},"  name",[356,470,410],{"class":396},[356,472,413],{"class":378},[356,474,383],{"class":382},[356,476,387],{"class":386},[356,478,479],{"class":390},"name",[356,481,387],{"class":386},[356,483,425],{"class":382},[356,485,428],{"class":396},[356,487,457],{"class":378},[356,489,436],{"class":382},[356,491,462],{"class":396},[356,493,495,498,500,502,504,506,509,511,513,515,517,519],{"class":358,"line":494},4,[356,496,497],{"class":406},"  description",[356,499,410],{"class":396},[356,501,413],{"class":378},[356,503,383],{"class":382},[356,505,387],{"class":386},[356,507,508],{"class":390},"description",[356,510,387],{"class":386},[356,512,425],{"class":382},[356,514,428],{"class":396},[356,516,457],{"class":378},[356,518,436],{"class":382},[356,520,462],{"class":396},[356,522,524,527,529,531,533,535,538,540,542,544,547,549,551,554,556,558,560,562,564],{"class":358,"line":523},5,[356,525,526],{"class":406},"  tier",[356,528,410],{"class":396},[356,530,413],{"class":378},[356,532,383],{"class":382},[356,534,387],{"class":386},[356,536,537],{"class":390},"tier",[356,539,387],{"class":386},[356,541,425],{"class":382},[356,543,428],{"class":396},[356,545,546],{"class":378},"default",[356,548,383],{"class":382},[356,550,387],{"class":386},[356,552,553],{"class":390},"FREE",[356,555,387],{"class":386},[356,557,425],{"class":382},[356,559,428],{"class":396},[356,561,457],{"class":378},[356,563,436],{"class":382},[356,565,462],{"class":396},[356,567,569,572,574,576,578,580,583,585,587,589,591,593],{"class":358,"line":568},6,[356,570,571],{"class":406},"  price",[356,573,410],{"class":396},[356,575,413],{"class":378},[356,577,383],{"class":382},[356,579,387],{"class":386},[356,581,582],{"class":390},"price",[356,584,387],{"class":386},[356,586,425],{"class":382},[356,588,428],{"class":396},[356,590,457],{"class":378},[356,592,436],{"class":382},[356,594,462],{"class":396},[356,596,598,601,603,605,607,609,612,614,616,618,620,622],{"class":358,"line":597},7,[356,599,600],{"class":406},"  period",[356,602,410],{"class":396},[356,604,413],{"class":378},[356,606,383],{"class":382},[356,608,387],{"class":386},[356,610,611],{"class":390},"period",[356,613,387],{"class":386},[356,615,425],{"class":382},[356,617,428],{"class":396},[356,619,457],{"class":378},[356,621,436],{"class":382},[356,623,462],{"class":396},[356,625,627,630,632,634,636,638,641,643,645],{"class":358,"line":626},8,[356,628,629],{"class":406},"  provider",[356,631,410],{"class":396},[356,633,413],{"class":378},[356,635,383],{"class":382},[356,637,387],{"class":386},[356,639,640],{"class":390},"provider",[356,642,387],{"class":386},[356,644,425],{"class":382},[356,646,462],{"class":396},[356,648,650,653,655,657,659,661,664,666,668],{"class":358,"line":649},9,[356,651,652],{"class":406},"  providerPriceId",[356,654,410],{"class":396},[356,656,413],{"class":378},[356,658,383],{"class":382},[356,660,387],{"class":386},[356,662,663],{"class":390},"providerPriceId",[356,665,387],{"class":386},[356,667,425],{"class":382},[356,669,462],{"class":396},[356,671,673,676,678,680,682,684,687,689,691,693],{"class":358,"line":672},10,[356,674,675],{"class":406},"  providerProductId",[356,677,410],{"class":396},[356,679,413],{"class":378},[356,681,383],{"class":382},[356,683,387],{"class":386},[356,685,686],{"class":390},"providerProductId",[356,688,387],{"class":386},[356,690,425],{"class":382},[356,692,397],{"class":396},[356,694,696],{"class":695},"ss7Ak"," \u002F\u002F the Stripe Product behind providerPriceId — Prices are immutable, so a changed amount creates a new Price under the same Product\n",[356,698,700,703,705,708,710,712,715,717,719,721],{"class":358,"line":699},11,[356,701,702],{"class":406},"  unitAmount",[356,704,410],{"class":396},[356,706,707],{"class":378}," integer",[356,709,383],{"class":382},[356,711,387],{"class":386},[356,713,714],{"class":390},"unitAmount",[356,716,387],{"class":386},[356,718,425],{"class":382},[356,720,397],{"class":396},[356,722,723],{"class":695}," \u002F\u002F minor units (e.g. cents); null for plans not managed by Stripe's catalog\n",[356,725,727,730,732,734,736,738,741,743,745,747],{"class":358,"line":726},12,[356,728,729],{"class":406},"  currency",[356,731,410],{"class":396},[356,733,413],{"class":378},[356,735,383],{"class":382},[356,737,387],{"class":386},[356,739,740],{"class":390},"currency",[356,742,387],{"class":386},[356,744,425],{"class":382},[356,746,397],{"class":396},[356,748,749],{"class":695}," \u002F\u002F lowercase ISO 4217; null alongside unitAmount\n",[356,751,753,756,758,760,762,764,767,769,771,773],{"class":358,"line":752},13,[356,754,755],{"class":406},"  maxSeats",[356,757,410],{"class":396},[356,759,707],{"class":378},[356,761,383],{"class":382},[356,763,387],{"class":386},[356,765,766],{"class":390},"maxSeats",[356,768,387],{"class":386},[356,770,425],{"class":382},[356,772,397],{"class":396},[356,774,775],{"class":695}," \u002F\u002F null = unlimited\n",[356,777,779,782,784,787,789,791,794,796,798,800],{"class":358,"line":778},14,[356,780,781],{"class":406},"  limits",[356,783,410],{"class":396},[356,785,786],{"class":378}," jsonb",[356,788,383],{"class":382},[356,790,387],{"class":386},[356,792,793],{"class":390},"limits",[356,795,387],{"class":386},[356,797,425],{"class":382},[356,799,397],{"class":396},[356,801,802],{"class":695}," \u002F\u002F open-ended bag for future quotas\n",[356,804,806,809,811,813,815,817,820,822,824,826,828,830,832,834,836,838,841,843,845,847],{"class":358,"line":805},15,[356,807,808],{"class":406},"  buyerTeamMode",[356,810,410],{"class":396},[356,812,413],{"class":378},[356,814,383],{"class":382},[356,816,387],{"class":386},[356,818,819],{"class":390},"buyerTeamMode",[356,821,387],{"class":386},[356,823,425],{"class":382},[356,825,428],{"class":396},[356,827,457],{"class":378},[356,829,436],{"class":382},[356,831,428],{"class":396},[356,833,546],{"class":378},[356,835,383],{"class":382},[356,837,387],{"class":386},[356,839,840],{"class":390},"NEW_TEAM",[356,842,387],{"class":386},[356,844,425],{"class":382},[356,846,397],{"class":396},[356,848,849],{"class":695}," \u002F\u002F \"NEW_TEAM\" or \"EXISTING_TEAM\"\n",[356,851,853,856,858,860,862,864,867,869,871,873],{"class":358,"line":852},16,[356,854,855],{"class":406},"  buyerTeamId",[356,857,410],{"class":396},[356,859,413],{"class":378},[356,861,383],{"class":382},[356,863,387],{"class":386},[356,865,866],{"class":390},"buyerTeamId",[356,868,387],{"class":386},[356,870,425],{"class":382},[356,872,397],{"class":396},[356,874,875],{"class":695}," \u002F\u002F the team an EXISTING_TEAM plan adds the buyer to\n",[356,877,879,882,884,887,889,891,894,896,898,900,902,904,906,908,910,914,916,918],{"class":358,"line":878},17,[356,880,881],{"class":406},"  isArchived",[356,883,410],{"class":396},[356,885,886],{"class":378}," boolean",[356,888,383],{"class":382},[356,890,387],{"class":386},[356,892,893],{"class":390},"isArchived",[356,895,387],{"class":386},[356,897,425],{"class":382},[356,899,428],{"class":396},[356,901,457],{"class":378},[356,903,436],{"class":382},[356,905,428],{"class":396},[356,907,546],{"class":378},[356,909,383],{"class":382},[356,911,913],{"class":912},"s8HiA","false",[356,915,425],{"class":382},[356,917,397],{"class":396},[356,919,920],{"class":695}," \u002F\u002F soft delete\n",[356,922,924],{"class":358,"line":923},18,[356,925,926],{"class":695},"  \u002F\u002F ...timestamps\n",[356,928,930,933,935],{"class":358,"line":929},19,[356,931,932],{"class":396},"}",[356,934,425],{"class":382},[356,936,937],{"class":396},";\n",[356,939,941],{"class":358,"line":940},20,[356,942,943],{"emptyLinePlaceholder":22},"\n",[356,945,947,949,951,954,956,958,960,962,965,967,969],{"class":358,"line":946},21,[356,948,363],{"class":362},[356,950,367],{"class":366},[356,952,953],{"class":370}," subscriptions",[356,955,375],{"class":374},[356,957,379],{"class":378},[356,959,383],{"class":382},[356,961,387],{"class":386},[356,963,964],{"class":390},"Subscription",[356,966,387],{"class":386},[356,968,397],{"class":396},[356,970,400],{"class":396},[356,972,974,976,978,980,982,984,986,988,990,992,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016],{"class":358,"line":973},22,[356,975,407],{"class":406},[356,977,410],{"class":396},[356,979,413],{"class":378},[356,981,383],{"class":382},[356,983,387],{"class":386},[356,985,420],{"class":390},[356,987,387],{"class":386},[356,989,425],{"class":382},[356,991,428],{"class":396},[356,993,431],{"class":378},[356,995,383],{"class":382},[356,997,436],{"class":396},[356,999,439],{"class":366},[356,1001,442],{"class":378},[356,1003,445],{"class":382},[356,1005,428],{"class":396},[356,1007,450],{"class":378},[356,1009,436],{"class":382},[356,1011,428],{"class":396},[356,1013,457],{"class":378},[356,1015,436],{"class":382},[356,1017,462],{"class":396},[356,1019,1021,1024,1026,1028,1030,1032,1035,1037,1039,1041,1043,1045,1047,1050,1052,1054,1056,1059,1061,1064],{"class":358,"line":1020},23,[356,1022,1023],{"class":406},"  teamId",[356,1025,410],{"class":396},[356,1027,413],{"class":378},[356,1029,383],{"class":382},[356,1031,387],{"class":386},[356,1033,1034],{"class":390},"teamId",[356,1036,387],{"class":386},[356,1038,425],{"class":382},[356,1040,428],{"class":396},[356,1042,457],{"class":378},[356,1044,436],{"class":382},[356,1046,428],{"class":396},[356,1048,1049],{"class":378},"references",[356,1051,383],{"class":382},[356,1053,436],{"class":396},[356,1055,439],{"class":366},[356,1057,1058],{"class":382}," teams",[356,1060,428],{"class":396},[356,1062,1063],{"class":382},"id)",[356,1065,462],{"class":396},[356,1067,1069,1072,1074,1076,1078,1080,1083,1085,1087,1089,1091,1093,1095,1098,1100,1102,1104,1106,1108],{"class":358,"line":1068},24,[356,1070,1071],{"class":406},"  status",[356,1073,410],{"class":396},[356,1075,413],{"class":378},[356,1077,383],{"class":382},[356,1079,387],{"class":386},[356,1081,1082],{"class":390},"status",[356,1084,387],{"class":386},[356,1086,425],{"class":382},[356,1088,428],{"class":396},[356,1090,546],{"class":378},[356,1092,383],{"class":382},[356,1094,387],{"class":386},[356,1096,1097],{"class":390},"ACTIVE",[356,1099,387],{"class":386},[356,1101,425],{"class":382},[356,1103,428],{"class":396},[356,1105,457],{"class":378},[356,1107,436],{"class":382},[356,1109,462],{"class":396},[356,1111,1113,1115,1117,1119,1121,1123,1125,1127,1129,1131,1133,1135],{"class":358,"line":1112},25,[356,1114,629],{"class":406},[356,1116,410],{"class":396},[356,1118,413],{"class":378},[356,1120,383],{"class":382},[356,1122,387],{"class":386},[356,1124,640],{"class":390},[356,1126,387],{"class":386},[356,1128,425],{"class":382},[356,1130,428],{"class":396},[356,1132,457],{"class":378},[356,1134,436],{"class":382},[356,1136,462],{"class":396},[356,1138,1140,1143,1145,1147,1149,1151,1154,1156,1158,1160,1162,1164],{"class":358,"line":1139},26,[356,1141,1142],{"class":406},"  providerSubscriptionId",[356,1144,410],{"class":396},[356,1146,413],{"class":378},[356,1148,383],{"class":382},[356,1150,387],{"class":386},[356,1152,1153],{"class":390},"providerSubscriptionId",[356,1155,387],{"class":386},[356,1157,425],{"class":382},[356,1159,428],{"class":396},[356,1161,457],{"class":378},[356,1163,436],{"class":382},[356,1165,462],{"class":396},[356,1167,1169,1172,1174,1176,1178,1180,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209],{"class":358,"line":1168},27,[356,1170,1171],{"class":406},"  planId",[356,1173,410],{"class":396},[356,1175,413],{"class":378},[356,1177,383],{"class":382},[356,1179,387],{"class":386},[356,1181,1182],{"class":390},"planId",[356,1184,387],{"class":386},[356,1186,425],{"class":382},[356,1188,428],{"class":396},[356,1190,457],{"class":378},[356,1192,436],{"class":382},[356,1194,428],{"class":396},[356,1196,1049],{"class":378},[356,1198,383],{"class":382},[356,1200,436],{"class":396},[356,1202,439],{"class":366},[356,1204,371],{"class":382},[356,1206,428],{"class":396},[356,1208,1063],{"class":382},[356,1210,462],{"class":396},[356,1212,1214,1217,1219,1222,1224,1226,1229,1231,1233,1236,1239,1241,1244,1247,1249],{"class":358,"line":1213},28,[356,1215,1216],{"class":406},"  currentPeriodStart",[356,1218,410],{"class":396},[356,1220,1221],{"class":378}," timestamp",[356,1223,383],{"class":382},[356,1225,387],{"class":386},[356,1227,1228],{"class":390},"currentPeriodStart",[356,1230,387],{"class":386},[356,1232,397],{"class":396},[356,1234,1235],{"class":396}," {",[356,1237,1238],{"class":406}," withTimezone",[356,1240,410],{"class":396},[356,1242,1243],{"class":912}," true",[356,1245,1246],{"class":396}," }",[356,1248,425],{"class":382},[356,1250,462],{"class":396},[356,1252,1254,1257,1259,1261,1263,1265,1268,1270,1272,1274,1276,1278,1280,1282,1284],{"class":358,"line":1253},29,[356,1255,1256],{"class":406},"  currentPeriodEnd",[356,1258,410],{"class":396},[356,1260,1221],{"class":378},[356,1262,383],{"class":382},[356,1264,387],{"class":386},[356,1266,1267],{"class":390},"currentPeriodEnd",[356,1269,387],{"class":386},[356,1271,397],{"class":396},[356,1273,1235],{"class":396},[356,1275,1238],{"class":406},[356,1277,410],{"class":396},[356,1279,1243],{"class":912},[356,1281,1246],{"class":396},[356,1283,425],{"class":382},[356,1285,462],{"class":396},[356,1287,1289,1292,1294,1296,1298,1300,1303,1305,1307,1309,1311,1313,1315,1317,1319,1321,1323],{"class":358,"line":1288},30,[356,1290,1291],{"class":406},"  cancelAtPeriodEnd",[356,1293,410],{"class":396},[356,1295,886],{"class":378},[356,1297,383],{"class":382},[356,1299,387],{"class":386},[356,1301,1302],{"class":390},"cancelAtPeriodEnd",[356,1304,387],{"class":386},[356,1306,425],{"class":382},[356,1308,428],{"class":396},[356,1310,546],{"class":378},[356,1312,383],{"class":382},[356,1314,913],{"class":912},[356,1316,425],{"class":382},[356,1318,428],{"class":396},[356,1320,457],{"class":378},[356,1322,436],{"class":382},[356,1324,462],{"class":396},[356,1326,1328,1331,1333,1335,1337,1339,1342,1344,1346],{"class":358,"line":1327},31,[356,1329,1330],{"class":406},"  providerMetadata",[356,1332,410],{"class":396},[356,1334,786],{"class":378},[356,1336,383],{"class":382},[356,1338,387],{"class":386},[356,1340,1341],{"class":390},"providerMetadata",[356,1343,387],{"class":386},[356,1345,425],{"class":382},[356,1347,462],{"class":396},[356,1349,1351,1354,1356,1358,1360,1362,1365,1367,1369],{"class":358,"line":1350},32,[356,1352,1353],{"class":406},"  customerEmail",[356,1355,410],{"class":396},[356,1357,413],{"class":378},[356,1359,383],{"class":382},[356,1361,387],{"class":386},[356,1363,1364],{"class":390},"customerEmail",[356,1366,387],{"class":386},[356,1368,425],{"class":382},[356,1370,462],{"class":396},[356,1372,1374,1377,1379,1381,1383,1385,1388,1390,1392,1394,1396,1398,1400,1402,1404],{"class":358,"line":1373},33,[356,1375,1376],{"class":406},"  lastPaymentDate",[356,1378,410],{"class":396},[356,1380,1221],{"class":378},[356,1382,383],{"class":382},[356,1384,387],{"class":386},[356,1386,1387],{"class":390},"lastPaymentDate",[356,1389,387],{"class":386},[356,1391,397],{"class":396},[356,1393,1235],{"class":396},[356,1395,1238],{"class":406},[356,1397,410],{"class":396},[356,1399,1243],{"class":912},[356,1401,1246],{"class":396},[356,1403,425],{"class":382},[356,1405,462],{"class":396},[356,1407,1409,1412,1414,1416,1418,1420,1423,1425,1427,1429,1431,1433,1435,1437,1439],{"class":358,"line":1408},34,[356,1410,1411],{"class":406},"  nextPaymentDate",[356,1413,410],{"class":396},[356,1415,1221],{"class":378},[356,1417,383],{"class":382},[356,1419,387],{"class":386},[356,1421,1422],{"class":390},"nextPaymentDate",[356,1424,387],{"class":386},[356,1426,397],{"class":396},[356,1428,1235],{"class":396},[356,1430,1238],{"class":406},[356,1432,410],{"class":396},[356,1434,1243],{"class":912},[356,1436,1246],{"class":396},[356,1438,425],{"class":382},[356,1440,462],{"class":396},[356,1442,1444],{"class":358,"line":1443},35,[356,1445,926],{"class":695},[356,1447,1449,1451,1453],{"class":358,"line":1448},36,[356,1450,932],{"class":396},[356,1452,425],{"class":382},[356,1454,937],{"class":396},[332,1456,1457,1458,1460,1461,1463,1464,1466,1467,1469],{},"A ",[353,1459,391],{}," row belongs to exactly one provider (",[353,1462,640],{},"\u002F",[353,1465,663],{},") — offering the same plan on both Stripe and Paddle means two ",[353,1468,391],{}," rows, one per provider.",[332,1471,1472,1473,1475,1476,1479,1480,1483],{},"Each plan also says where a buyer lands after paying: in a team of their own (",[353,1474,840],{},", the buyer becomes its Owner) or in an existing team you choose (",[353,1477,1478],{},"EXISTING_TEAM","). You set it in the plan form at ",[353,1481,1482],{},"\u002Fadmin\u002Fplans",". The checkout that acts on this setting is the anonymous Buy flow described below.",[340,1485,1487],{"id":1486},"webhook-handlers","Webhook Handlers",[332,1489,1490,1491,410],{},"Located in ",[353,1492,1493],{},"layers\u002Fbilling\u002Fserver\u002Fapi\u002Fwebhooks\u002F",[322,1495,1496,1502],{},[325,1497,1498,1501],{},[353,1499,1500],{},"stripe.post.ts"," — handles Stripe subscription events",[325,1503,1504,1507],{},[353,1505,1506],{},"paddle.post.ts"," — handles Paddle subscription events",[340,1509,1511],{"id":1510},"client-integration","Client Integration",[332,1513,1514,1515,1518,1519,1522],{},"The ",[353,1516,1517],{},"usePayments()"," composable (",[353,1520,1521],{},"layers\u002Fbilling\u002Fapp\u002Fcomposables\u002Fuse-payments.ts",") handles client-side checkout, with a different shape per provider:",[345,1524,1526],{"className":347,"code":1525,"language":350,"meta":351,"style":351},"\u002F\u002F Paddle — items with a Paddle price id, opens Paddle's own checkout overlay\nconst paddle = usePayments(\"PADDLE\");\nawait paddle?.checkout([{ priceId: \"pri_123\" }], { data: { teamId: team.id } });\n\n\u002F\u002F Stripe — returns a hosted Checkout Session URL to redirect to\nconst stripe = usePayments(\"STRIPE\");\nconst sessionUrl = await stripe?.checkout(\"price_123\", team.id);\nif (sessionUrl) navigateTo(sessionUrl, { external: true });\n",[353,1527,1528,1533,1559,1628,1632,1637,1661,1698],{"__ignoreMap":351},[356,1529,1530],{"class":358,"line":359},[356,1531,1532],{"class":695},"\u002F\u002F Paddle — items with a Paddle price id, opens Paddle's own checkout overlay\n",[356,1534,1535,1538,1541,1543,1546,1548,1550,1553,1555,1557],{"class":358,"line":403},[356,1536,1537],{"class":366},"const",[356,1539,1540],{"class":370}," paddle",[356,1542,375],{"class":374},[356,1544,1545],{"class":378}," usePayments",[356,1547,383],{"class":382},[356,1549,387],{"class":386},[356,1551,1552],{"class":390},"PADDLE",[356,1554,387],{"class":386},[356,1556,425],{"class":382},[356,1558,937],{"class":396},[356,1560,1561,1564,1566,1569,1572,1575,1578,1581,1583,1586,1589,1591,1593,1596,1598,1600,1603,1605,1607,1610,1612,1615,1617,1620,1622,1624,1626],{"class":358,"line":465},[356,1562,1563],{"class":362},"await",[356,1565,1540],{"class":382},[356,1567,1568],{"class":396},"?.",[356,1570,1571],{"class":378},"checkout",[356,1573,1574],{"class":382},"([",[356,1576,1577],{"class":396},"{",[356,1579,1580],{"class":406}," priceId",[356,1582,410],{"class":396},[356,1584,1585],{"class":386}," \"",[356,1587,1588],{"class":390},"pri_123",[356,1590,387],{"class":386},[356,1592,1246],{"class":396},[356,1594,1595],{"class":382},"]",[356,1597,397],{"class":396},[356,1599,1235],{"class":396},[356,1601,1602],{"class":406}," data",[356,1604,410],{"class":396},[356,1606,1235],{"class":396},[356,1608,1609],{"class":406}," teamId",[356,1611,410],{"class":396},[356,1613,1614],{"class":382}," team",[356,1616,428],{"class":396},[356,1618,1619],{"class":382},"id ",[356,1621,932],{"class":396},[356,1623,1246],{"class":396},[356,1625,425],{"class":382},[356,1627,937],{"class":396},[356,1629,1630],{"class":358,"line":494},[356,1631,943],{"emptyLinePlaceholder":22},[356,1633,1634],{"class":358,"line":523},[356,1635,1636],{"class":695},"\u002F\u002F Stripe — returns a hosted Checkout Session URL to redirect to\n",[356,1638,1639,1641,1644,1646,1648,1650,1652,1655,1657,1659],{"class":358,"line":568},[356,1640,1537],{"class":366},[356,1642,1643],{"class":370}," stripe",[356,1645,375],{"class":374},[356,1647,1545],{"class":378},[356,1649,383],{"class":382},[356,1651,387],{"class":386},[356,1653,1654],{"class":390},"STRIPE",[356,1656,387],{"class":386},[356,1658,425],{"class":382},[356,1660,937],{"class":396},[356,1662,1663,1665,1668,1670,1673,1675,1677,1679,1681,1683,1686,1688,1690,1692,1694,1696],{"class":358,"line":597},[356,1664,1537],{"class":366},[356,1666,1667],{"class":370}," sessionUrl",[356,1669,375],{"class":374},[356,1671,1672],{"class":362}," await",[356,1674,1643],{"class":382},[356,1676,1568],{"class":396},[356,1678,1571],{"class":378},[356,1680,383],{"class":382},[356,1682,387],{"class":386},[356,1684,1685],{"class":390},"price_123",[356,1687,387],{"class":386},[356,1689,397],{"class":396},[356,1691,1614],{"class":382},[356,1693,428],{"class":396},[356,1695,1063],{"class":382},[356,1697,937],{"class":396},[356,1699,1700,1703,1706,1709,1712,1714,1716,1719,1721,1723,1725,1727],{"class":358,"line":626},[356,1701,1702],{"class":362},"if",[356,1704,1705],{"class":382}," (sessionUrl) ",[356,1707,1708],{"class":378},"navigateTo",[356,1710,1711],{"class":382},"(sessionUrl",[356,1713,397],{"class":396},[356,1715,1235],{"class":396},[356,1717,1718],{"class":406}," external",[356,1720,410],{"class":396},[356,1722,1243],{"class":912},[356,1724,1246],{"class":396},[356,1726,425],{"class":382},[356,1728,937],{"class":396},[332,1730,1731,1732,1735],{},"Paddle checkout requires ",[353,1733,1734],{},"NUXT_PUBLIC_PADDLE_CLIENT_TOKEN"," to be configured — it throws explicitly if that value is empty, rather than silently failing to open.",[317,1737,1739],{"id":1738},"setup-steps","Setup Steps",[1741,1742,1743,1839,1870],"ol",{},[325,1744,1745,1746],{},"Set required secrets using the SST CLI:",[345,1747,1751],{"className":1748,"code":1749,"language":1750,"meta":351,"style":351},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","# Stripe\nsst secret set STRIPE_SECRET_KEY sk_test_xxx\nsst secret set STRIPE_WEBHOOK_SECRET_KEY whsec_xxx\n\n# Paddle\nsst secret set PADDLE_API_KEY xxx\nsst secret set PADDLE_WEBHOOK_SECRET xxx\nsst secret set PADDLE_CLIENT_TOKEN xxx\n","bash",[353,1752,1753,1758,1776,1790,1794,1799,1813,1826],{"__ignoreMap":351},[356,1754,1755],{"class":358,"line":359},[356,1756,1757],{"class":695},"# Stripe\n",[356,1759,1760,1764,1767,1770,1773],{"class":358,"line":403},[356,1761,1763],{"class":1762},"sR7ES","sst",[356,1765,1766],{"class":390}," secret",[356,1768,1769],{"class":390}," set",[356,1771,1772],{"class":390}," STRIPE_SECRET_KEY",[356,1774,1775],{"class":390}," sk_test_xxx\n",[356,1777,1778,1780,1782,1784,1787],{"class":358,"line":465},[356,1779,1763],{"class":1762},[356,1781,1766],{"class":390},[356,1783,1769],{"class":390},[356,1785,1786],{"class":390}," STRIPE_WEBHOOK_SECRET_KEY",[356,1788,1789],{"class":390}," whsec_xxx\n",[356,1791,1792],{"class":358,"line":494},[356,1793,943],{"emptyLinePlaceholder":22},[356,1795,1796],{"class":358,"line":523},[356,1797,1798],{"class":695},"# Paddle\n",[356,1800,1801,1803,1805,1807,1810],{"class":358,"line":568},[356,1802,1763],{"class":1762},[356,1804,1766],{"class":390},[356,1806,1769],{"class":390},[356,1808,1809],{"class":390}," PADDLE_API_KEY",[356,1811,1812],{"class":390}," xxx\n",[356,1814,1815,1817,1819,1821,1824],{"class":358,"line":597},[356,1816,1763],{"class":1762},[356,1818,1766],{"class":390},[356,1820,1769],{"class":390},[356,1822,1823],{"class":390}," PADDLE_WEBHOOK_SECRET",[356,1825,1812],{"class":390},[356,1827,1828,1830,1832,1834,1837],{"class":358,"line":626},[356,1829,1763],{"class":1762},[356,1831,1766],{"class":390},[356,1833,1769],{"class":390},[356,1835,1836],{"class":390}," PADDLE_CLIENT_TOKEN",[356,1838,1812],{"class":390},[325,1840,1841,1842,1844,1845,1848,1849,1852,1856,1857,1860,1861,1864,1865,1463,1867,1869],{},"Create plans at ",[353,1843,1482],{}," — see ",[1846,1847,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.",[1850,1851],"br",{},[1853,1854,1855],"strong",{},"For a Stripe-managed plan, no price id is pasted by hand."," The admin form takes a decimal amount, currency, and billing period; ",[353,1858,1859],{},"layers\u002Fbilling\u002Fserver\u002Futils\u002Fstripe-catalog.ts","'s ",[353,1862,1863],{},"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. ",[353,1866,663],{},[353,1868,686],{}," are still a manual field for a Paddle-provider plan (Paddle's own catalog isn't managed from here) or for a Stripe plan predating this sync.",[325,1871,1872,1873],{},"Set up webhook endpoints in your provider dashboards:",[322,1874,1875,1881],{},[325,1876,1877,1878],{},"Stripe: ",[353,1879,1880],{},"\u002Fapi\u002Fwebhooks\u002Fstripe",[325,1882,1883,1884],{},"Paddle: ",[353,1885,1886],{},"\u002Fapi\u002Fwebhooks\u002Fpaddle",[317,1888,1890],{"id":1889},"buying-without-an-account-the-buy-cta","Buying Without an Account (the Buy CTA)",[332,1892,1893,1894,1897],{},"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 (",[353,1895,1896],{},"apps\u002Fweb",") Buy CTA.",[1741,1899,1900,1915,1944,1960],{},[325,1901,1902,1907,1908,1911,1912,1914],{},[1853,1903,1904],{},[353,1905,1906],{},"\u002Fbuy\u002F[planId]"," (",[353,1909,1910],{},"apps\u002Fapp",", public) — the buyer picks a ",[353,1913,391],{}," (by its id in the URL), enters an email, and pays via that plan's provider (Stripe Checkout or Paddle's overlay). No account or team exists yet at this point.",[325,1916,1917,1907,1922,1924,1925,1928,1929,1463,1932,1935,1936,1939,1940,1943],{},[1853,1918,1919],{},[353,1920,1921],{},"PendingPurchase",[353,1923,349],{},") — created at checkout start, confirmed by the provider's webhook once the payment\u002Fsubscription is active. States: ",[353,1926,1927],{},"PENDING → CLAIMED",", or ",[353,1930,1931],{},"EXPIRED",[353,1933,1934],{},"REJECTED",". An unclaimed purchase expires 30 days after creation. The webhook also sends the buyer a claim e-mail (",[353,1937,1938],{},"PurchaseClaim"," template, ",[353,1941,1942],{},"packages\u002Femails",").",[325,1945,1946,1907,1951,1953,1954,1956,1957,1959],{},[1853,1947,1948],{},[353,1949,1950],{},"\u002Fclaim\u002F[id]",[353,1952,1910],{},", 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 (",[353,1955,840],{},"), or membership in the chosen existing team as Member (",[353,1958,1478],{},"). 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.",[325,1961,1962,1967],{},[1853,1963,1964],{},[353,1965,1966],{},"\u002Fadmin\u002Fpending-purchases"," — every purchase, all statuses, for investigating a payment that never turned into access.",[340,1969,1971],{"id":1970},"wiring-the-landing-pages-cta-no-redeploy-needed","Wiring the landing page's CTA — no redeploy needed",[332,1973,1974,1975,1907,1977,1980,1981,1984,1985,1987,1988,428],{},"Which plan the Buy CTA on ",[353,1976,1896],{},[353,1978,1979],{},"CTAButton",", both the hero and the pricing card) sells is a database flag, not a deploy-time setting: ",[353,1982,1983],{},"Plan.isPublicOffer"," (at most one plan holds it at a time). Toggle it from ",[353,1986,1482],{}," — 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 ",[1853,1989,1990],{},"no env var and no redeploy",[332,1992,1993,1994,1996],{},"How the landing page (a separate Cloudflare Worker, its own domain) reads a database row that lives behind ",[353,1995,1910],{},"'s API without exposing that API publicly:",[1741,1998,1999,2015,2053,2066],{},[325,2000,2001,2004,2005,2007,2008,2011,2012,428],{},[353,2002,2003],{},"layers\u002Fbilling\u002Fserver\u002Fapi\u002Fpublic\u002Foffer.get.ts"," — a public, unauthenticated route on ",[353,2006,1910],{}," returning whichever plan has ",[353,2009,2010],{},"isPublicOffer: true"," (and isn't archived), or ",[353,2013,2014],{},"null",[325,2016,2017,2020,2021,2024,2025,2028,2029,2032,2033,2036,2037,2040,2041,2044,2045,2048,2049,2052],{},[353,2018,2019],{},"infra\u002Fweb.ts"," links the ",[353,2022,2023],{},"App"," Worker into the ",[353,2026,2027],{},"Web"," Worker's ",[353,2030,2031],{},"link"," array — the same Cloudflare ",[1853,2034,2035],{},"service binding"," pattern this project already uses for ",[353,2038,2039],{},"App → Auth"," (see the comment on ",[353,2042,2043],{},"link: [auth]"," in ",[353,2046,2047],{},"infra\u002Fapp.ts",", quoting Cloudflare's own best practices against a plain ",[353,2050,2051],{},"fetch()"," between Workers). No CORS, no public exposure: the call never leaves Cloudflare's network.",[325,2054,2055,2058,2059,2062,2063,2065],{},[353,2056,2057],{},"apps\u002Fweb\u002Fserver\u002Fapi\u002Foffer.get.ts"," calls that route through the binding (",[353,2060,2061],{},"event.context.cloudflare.env.AppWorker",", falling back to a plain ",[353,2064,2051],{}," for local dev where the binding may not be wired) and returns the plan JSON to the browser.",[325,2067,2068,2071,2072,2075,2076,2079,2080,2082],{},[353,2069,2070],{},"apps\u002Fweb\u002Fapp\u002Fcomposables\u002Fuse-offer.ts"," fetches ",[353,2073,2074],{},"\u002Fapi\u002Foffer"," and builds ",[353,2077,2078],{},"${appUrl}\u002Fbuy\u002F\u003CplanId>"," — the same URL shape as ",[353,2081,1906],{}," above — with no configuration of any kind.",[332,2084,2085],{},"No plan flagged yet: the CTA falls back to the same \"coming soon\" state it always had.",[340,2087,2089],{"id":2088},"the-free-plan","The Free plan",[332,2091,2092,2093,2096,2097,2100],{},"Exactly one plan is created automatically, by ",[353,2094,2095],{},"apps\u002Ffunctions\u002Fsrc\u002Fdatabase\u002Fseed\u002Findex.ts"," on every deploy (idempotent — it checks for an existing ",[353,2098,2099],{},"tier: \"FREE\""," row first):",[345,2102,2105],{"className":347,"code":2103,"filename":2104,"language":350,"meta":351,"style":351},"export const FREE_PLAN: InsertPlan = {\n  name: \"Free Tier\",\n  tier: \"FREE\",\n  \u002F\u002F ...\n};\n","apps\u002Ffunctions\u002Fsrc\u002Fdatabase\u002Fseed\u002Fdata.ts",[353,2106,2107,2126,2141,2155,2160],{"__ignoreMap":351},[356,2108,2109,2111,2113,2116,2118,2122,2124],{"class":358,"line":359},[356,2110,363],{"class":362},[356,2112,367],{"class":366},[356,2114,2115],{"class":370}," FREE_PLAN",[356,2117,410],{"class":374},[356,2119,2121],{"class":2120},"sKvfc"," InsertPlan",[356,2123,375],{"class":374},[356,2125,400],{"class":396},[356,2127,2128,2130,2132,2134,2137,2139],{"class":358,"line":403},[356,2129,468],{"class":406},[356,2131,410],{"class":396},[356,2133,1585],{"class":386},[356,2135,2136],{"class":390},"Free Tier",[356,2138,387],{"class":386},[356,2140,462],{"class":396},[356,2142,2143,2145,2147,2149,2151,2153],{"class":358,"line":465},[356,2144,526],{"class":406},[356,2146,410],{"class":396},[356,2148,1585],{"class":386},[356,2150,553],{"class":390},[356,2152,387],{"class":386},[356,2154,462],{"class":396},[356,2156,2157],{"class":358,"line":494},[356,2158,2159],{"class":695},"  \u002F\u002F ...\n",[356,2161,2162],{"class":358,"line":523},[356,2163,2164],{"class":396},"};\n",[332,2166,2167,2168,2170],{},"Every paid plan is deliberately left out of the seed — Stripe\u002FPaddle price ids are per-environment, so they're created through ",[353,2169,1482],{}," instead of being baked into a script.",[2172,2173,2174],"style",{},"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 .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 .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 .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 .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 .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}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 .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}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}",{"title":351,"searchDepth":403,"depth":403,"links":2176},[2177,2178,2183,2184],{"id":319,"depth":403,"text":320},{"id":337,"depth":403,"text":338,"children":2179},[2180,2181,2182],{"id":342,"depth":465,"text":343},{"id":1486,"depth":465,"text":1487},{"id":1510,"depth":465,"text":1511},{"id":1738,"depth":403,"text":1739},{"id":1889,"depth":403,"text":1890,"children":2185},[2186,2187],{"id":1970,"depth":465,"text":1971},{"id":2088,"depth":465,"text":2089},"Billing powered by Stripe and Paddle, configured to run in parallel.","md",{},{"title":102,"description":2188},"uuLNZ627hJ8ncZ2w-awj0HLz4werJdrZZjdD-89WzNw",1790707485288]