[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-navigation":3,"docs-page-payments":166},[4,38,57,65,114,138,157],{"id":5,"group":6,"pages":7},"get-started","Get started",[8,13,18,23,28,33],{"path":9,"title":10,"description":11,"navTitle":10,"group":6,"order":12},"\u002Fdocs\u002Fgetting-started","Getting started","Create your app from the kit, run it locally with no credentials, make yourself superadmin and find where your own code goes.",1,{"path":14,"title":15,"description":16,"navTitle":15,"group":6,"order":17},"\u002Fdocs\u002Flayout","Project structure","Where code lives by where it runs, and which server\u002F directories turn a new file into a route, a job or a provider.",2,{"path":19,"title":20,"description":21,"navTitle":20,"group":6,"order":22},"\u002Fdocs\u002Fconfiguration","Configuration","starter.config.ts switches each part of the kit and its providers, environment variables hold the secrets: every key and what it changes.",3,{"path":24,"title":25,"description":26,"navTitle":25,"group":6,"order":27},"\u002Fdocs\u002Ftemplate","Updating from the kit","Keep the kit as a git remote and take each update as one commit you review, with pnpm template:update, then resolve what conflicts.",4,{"path":29,"title":30,"description":31,"navTitle":30,"group":6,"order":32},"\u002Fdocs\u002Fdeployment","Deployment","Ship the app as one Cloudflare Worker: create the resources, set the secrets, deploy main from GitHub Actions or your machine.",5,{"path":34,"title":35,"description":36,"navTitle":35,"group":6,"order":37},"\u002Fdocs\u002Fmcp","MCP server","Connect Claude Code, Cursor or VS Code to https:\u002F\u002Fnuxter.app\u002Fmcp, so your agent searches and reads these docs and their design notes.",6,{"id":39,"group":40,"pages":41},"accounts","Accounts",[42,47,52],{"path":43,"title":44,"description":45,"navTitle":44,"group":40,"order":46},"\u002Fdocs\u002Fauth","Auth","Pick the sign-in methods, add an OAuth provider, protect a route, and handle sessions, second factors and account deletion.",7,{"path":48,"title":49,"description":50,"navTitle":49,"group":40,"order":51},"\u002Fdocs\u002Ftenancy","Organizations","Scope routes and pages to an organization, and work with its members, roles, seats, invitations and a second level such as branches.",8,{"path":53,"title":54,"description":55,"navTitle":54,"group":40,"order":56},"\u002Fdocs\u002Fpermissions","Roles and permissions","Routes check permissions that an account’s role or its organization’s plan grants: checking one, adding a role, and the staff tools.",9,{"id":58,"group":59,"pages":60},"payments","Payments",[61],{"path":62,"title":59,"description":63,"navTitle":59,"group":59,"order":64},"\u002Fdocs\u002Fpayments","Sell plans through Stripe, Polar, Lemon Squeezy or Creem: define plans, connect a provider, gate features, trials, coupons and refunds.",10,{"id":66,"group":67,"pages":68},"building-blocks","Building blocks",[69,74,79,84,89,94,99,104,109],{"path":70,"title":71,"description":72,"navTitle":71,"group":67,"order":73},"\u002Fdocs\u002Fdatabase","Database","Postgres through Drizzle, on Hyperdrive in production and PGlite locally: add a table, query, transactions, migrations and seed data.",11,{"path":75,"title":76,"description":77,"navTitle":76,"group":67,"order":78},"\u002Fdocs\u002Fbackground-jobs","Background jobs","Run work after the response on Cloudflare Queues with retries: add and enqueue a job, run jobs locally, and schedule cron tasks.",12,{"path":80,"title":81,"description":82,"navTitle":81,"group":67,"order":83},"\u002Fdocs\u002Fevents","Events","React to what the kit does by adding a file to server\u002Flisteners\u002F, fire events of your own, and look up every event’s payload.",13,{"path":85,"title":86,"description":87,"navTitle":86,"group":67,"order":88},"\u002Fdocs\u002Femail","Email","Set up sending, send your own mail with deliverEmail, change the templates and wording, handle unsubscribes and add a provider.",14,{"path":90,"title":91,"description":92,"navTitle":91,"group":67,"order":93},"\u002Fdocs\u002Fcontact","Contact","A \u002Fcontact page that mails you a message or shows your address after a Turnstile check: choose the channels, set the address, know the limits.",15,{"path":95,"title":96,"description":97,"navTitle":96,"group":67,"order":98},"\u002Fdocs\u002Fnotifications","Notifications","The dashboard bell: add a notification type, send one with notify(), and how categories, opt-outs and retention apply.",16,{"path":100,"title":101,"description":102,"navTitle":101,"group":67,"order":103},"\u002Fdocs\u002Fstorage","Storage","Uploads in R2 with one policy per purpose: add a purpose, accept an upload, replace or delete a file, and serve it to its owner.",17,{"path":105,"title":106,"description":107,"navTitle":106,"group":67,"order":108},"\u002Fdocs\u002Ffeature-flags","Feature flags","Declare typed flags in starter.config.ts, read them with flag() or useFlag(), roll them out by rule or percentage, and add a provider.",18,{"path":110,"title":111,"description":112,"navTitle":111,"group":67,"order":113},"\u002Fdocs\u002Fai","AI","Call models from server code by role: text, images, speech, transcription, embeddings and video, with provider fallbacks.",19,{"id":115,"group":116,"pages":117},"web","Web",[118,123,128,133],{"path":119,"title":120,"description":121,"navTitle":120,"group":116,"order":122},"\u002Fdocs\u002Fsecurity-headers","Security headers","The Content-Security-Policy and hardening headers on every response: allow another origin, keep tokens out of Referer, accept cross-site posts.",20,{"path":124,"title":125,"description":126,"navTitle":125,"group":116,"order":127},"\u002Fdocs\u002Frate-limiting","Rate limiting","Cap how often a caller hits a route with requireRateLimit, count only failures, space out sends, and protect the zone.",21,{"path":129,"title":130,"description":131,"navTitle":130,"group":116,"order":132},"\u002Fdocs\u002Fanalytics","Analytics","Product analytics and ad conversions sent from the server with no browser script: add an event, connect a provider, and handle consent.",22,{"path":134,"title":135,"description":136,"navTitle":135,"group":116,"order":137},"\u002Fdocs\u002Fobservability","Observability","Find a failed request by its id, send faults to Sentry or Better Stack, report an error you caught, and change the error page.",23,{"id":139,"group":140,"pages":141},"content-and-look","Content and look",[142,147,152],{"path":143,"title":144,"description":145,"navTitle":144,"group":140,"order":146},"\u002Fdocs\u002Fblog","Blog","Markdown posts with an RSS feed per language, and workflows that draft posts with an AI model and open each one as a pull request.",24,{"path":148,"title":149,"description":150,"navTitle":149,"group":140,"order":151},"\u002Fdocs\u002Flanguages","Languages","Add or remove a language, write copy in the locale files, and write code, refusals and links that follow the reader’s language.",25,{"path":153,"title":154,"description":155,"navTitle":154,"group":140,"order":156},"\u002Fdocs\u002Ftheme","Theme","Change the brand colour and font, and build dashboard sections, list pages, loading placeholders and charts that match the kit.",26,{"id":158,"group":159,"pages":160},"quality","Quality",[161],{"path":162,"title":163,"description":164,"navTitle":163,"group":159,"order":165},"\u002Fdocs\u002Ftesting","Testing","Unit tests in Node, component tests in a real Nuxt app and Playwright end to end: test a route or a component, and what CI runs.",27,{"page":167,"surround":2369,"linksToKit":1256},{"path":62,"title":59,"description":63,"navTitle":59,"group":59,"seo":168,"body":170},{"title":169,"description":63},"Nuxt payments: Stripe, Polar, Lemon Squeezy, Creem",{"type":171,"value":172,"toc":2345},"minimark",[173,176,195,200,203,222,314,318,362,373,377,396,556,589,609,614,660,663,667,674,729,732,747,754,758,765,827,838,842,872,1013,1016,1096,1100,1106,1109,1128,1145,1163,1166,1170,1198,1400,1424,1459,1463,1473,1512,1515,1518,1535,1722,1734,1780,1805,1845,1849,1857,1860,1864,1890,1894,1905,1908,1912,1923,1927,1933,1936,1946,1950,1960,1971,1983,1987,2002,2005,2194,2297,2301,2341],[174,175,59],"h1",{"id":58},[177,178,179,180,183,184,194],"p",{},"The kit sells your plans through Stripe, Polar, Lemon Squeezy or Creem, using\neach provider's hosted checkout and billing portal. You work here when you\nchange what you sell, connect a provider, or put a feature behind a plan. It is\nall switched from the ",[181,182,58],"code",{}," block in\n",[185,186,191],"a",{"href":187,"rel":188,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fstarter.config.ts",[189],"nofollow","Licensees only",[181,192,193],{},"starter.config.ts",".",[196,197,199],"h2",{"id":198},"what-is-built","What is built",[177,201,202],{},"Subscriptions with monthly and yearly prices, one-time and repeatable purchases,\ntrials, coupon links, a grace period for failed renewals, guest checkout,\nrefunds and disputes, staff grants, permissions sold per plan, and an optional\nplan gate that locks an organization's pages until it pays.",[177,204,205,206,209,210,217,218,221],{},"Only the providers listed in ",[181,207,208],{},"payments.providers"," are bundled into the Worker.\nThe adapters in ",[185,211,214],{"href":212,"rel":213,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Ftree\u002Fmain\u002Fserver\u002Fpayments\u002Fadapters",[189],[181,215,216],{},"server\u002Fpayments\u002Fadapters\u002F","\ncall each provider's REST API with ",[181,219,220],{},"fetch"," and verify webhooks with Web Crypto,\nwith no vendor SDK.",[223,224,225,246],"table",{},[226,227,228],"thead",{},[229,230,231,234,237,240,243],"tr",{},[232,233],"th",{},[232,235,236],{},"Stripe",[232,238,239],{},"Polar",[232,241,242],{},"Lemon Squeezy",[232,244,245],{},"Creem",[247,248,249,273,288,301],"tbody",{},[229,250,251,255,261,264,267],{},[252,253,254],"td",{},"What a ref is",[252,256,257,258],{},"price id, ",[181,259,260],{},"price_…",[252,262,263],{},"product id, a UUID",[252,265,266],{},"variant id, a number",[252,268,269,270],{},"product id, ",[181,271,272],{},"prod_…",[229,274,275,278,281,283,286],{},[252,276,277],{},"Sets the trial length at checkout",[252,279,280],{},"yes",[252,282,280],{},[252,284,285],{},"no",[252,287,285],{},[229,289,290,293,295,297,299],{},[252,291,292],{},"Merchant of record (handles sales tax)",[252,294,285],{},[252,296,280],{},[252,298,280],{},[252,300,280],{},[229,302,303,306,308,310,312],{},[252,304,305],{},"Can hide its own coupon box",[252,307,280],{},[252,309,280],{},[252,311,280],{},[252,313,285],{},[196,315,317],{"id":316},"how-a-purchase-works","How a purchase works",[319,320,321,336,339,349,356],"ol",{},[322,323,324,325,328,329,332,333,194],"li",{},"The buyer presses a ",[181,326,327],{},"PaymentsCheckoutButton"," on ",[181,330,331],{},"\u002Fpricing"," or on an\norganization's billing page. It posts the plan id, the interval and the\norganization to ",[181,334,335],{},"POST \u002Fapi\u002Fpayments\u002Fcheckout",[322,337,338],{},"The route reads the provider's price id from your environment, refuses a\nduplicate purchase, applies any coupon and trial, and returns the hosted\ncheckout URL. The browser never names a price.",[322,340,341,342,345,346,194],{},"After payment the provider calls ",[181,343,344],{},"POST \u002Fwebhooks\u002Fpayments\u002F:provider",". The\nadapter checks the signature and turns the payload into a normalized event,\nstored once in ",[181,347,348],{},"payment_events",[322,350,351,352,355],{},"The event becomes a row in ",[181,353,354],{},"entitlements"," on the organization's root. That\nrow is the grant every paid check reads.",[322,357,358,361],{},[181,359,360],{},"payments:settled"," fires for your listeners. The page the buyer returns to\npolls until the grant shows.",[177,363,364,365,368,369,372],{},"A plan belongs to an organization (",[185,366,367],{"href":48},"Tenancy","), and the\norganization is fixed at checkout. With organizations off, it lands on the\naccount's own organization, created on the first purchase. An organization\nholds one live subscription per provider, so plan and billing-cycle changes go\nthrough the provider's portal (the Change plan button,\n",[181,370,371],{},"POST \u002Fapi\u002Fpayments\u002Fportal","). The next subscription event names the new price,\nand the kit maps it back to a plan through your refs.",[196,374,376],{"id":375},"define-your-plans","Define your plans",[177,378,379,380,383,384,387,388,391,392,395],{},"The catalogue is ",[181,381,382],{},"payments.plans",". The kit ships ",[181,385,386],{},"pro",", ",[181,389,390],{},"promax"," and\n",[181,393,394],{},"lifetime","; two of them look like this:",[397,398,403],"pre",{"className":399,"code":400,"language":401,"meta":402,"style":402},"language-ts shiki shiki-themes github-light github-light github-dark","plans: {\n  pro: {\n    mode: 'subscription',\n    currency: 'usd',\n    trialDays: 14,\n    intervals: {\n      month: { amountMinor: 499 },\n      year: { amountMinor: 4990 },\n    },\n  },\n  lifetime: { mode: 'one-time', currency: 'usd', amountMinor: 39900 },\n},\n","ts","",[181,404,405,417,424,439,451,464,471,490,506,511,516,551],{"__ignoreMap":402},[406,407,409,413],"span",{"class":408,"line":12},"line",[406,410,412],{"class":411},"snPdu","plans",[406,414,416],{"class":415},"sxrX7",": {\n",[406,418,419,422],{"class":408,"line":17},[406,420,421],{"class":411},"  pro",[406,423,416],{"class":415},[406,425,426,429,432,436],{"class":408,"line":22},[406,427,428],{"class":411},"    mode",[406,430,431],{"class":415},": ",[406,433,435],{"class":434},"sIIMD","'subscription'",[406,437,438],{"class":415},",\n",[406,440,441,444,446,449],{"class":408,"line":27},[406,442,443],{"class":411},"    currency",[406,445,431],{"class":415},[406,447,448],{"class":434},"'usd'",[406,450,438],{"class":415},[406,452,453,456,458,462],{"class":408,"line":32},[406,454,455],{"class":411},"    trialDays",[406,457,431],{"class":415},[406,459,461],{"class":460},"sBjJW","14",[406,463,438],{"class":415},[406,465,466,469],{"class":408,"line":37},[406,467,468],{"class":411},"    intervals",[406,470,416],{"class":415},[406,472,473,476,479,482,484,487],{"class":408,"line":46},[406,474,475],{"class":411},"      month",[406,477,478],{"class":415},": { ",[406,480,481],{"class":411},"amountMinor",[406,483,431],{"class":415},[406,485,486],{"class":460},"499",[406,488,489],{"class":415}," },\n",[406,491,492,495,497,499,501,504],{"class":408,"line":51},[406,493,494],{"class":411},"      year",[406,496,478],{"class":415},[406,498,481],{"class":411},[406,500,431],{"class":415},[406,502,503],{"class":460},"4990",[406,505,489],{"class":415},[406,507,508],{"class":408,"line":56},[406,509,510],{"class":415},"    },\n",[406,512,513],{"class":408,"line":64},[406,514,515],{"class":415},"  },\n",[406,517,518,521,523,526,528,531,533,536,538,540,542,544,546,549],{"class":408,"line":73},[406,519,520],{"class":411},"  lifetime",[406,522,478],{"class":415},[406,524,525],{"class":411},"mode",[406,527,431],{"class":415},[406,529,530],{"class":434},"'one-time'",[406,532,387],{"class":415},[406,534,535],{"class":411},"currency",[406,537,431],{"class":415},[406,539,448],{"class":434},[406,541,387],{"class":415},[406,543,481],{"class":411},[406,545,431],{"class":415},[406,547,548],{"class":460},"39900",[406,550,489],{"class":415},[406,552,553],{"class":408,"line":78},[406,554,555],{"class":415},"},\n",[557,558,559,567,580,586],"ul",{},[322,560,561,563,564,566],{},[181,562,481],{}," is the price the pricing page shows, in minor units, for the\nwhole interval: ",[181,565,503],{}," is $49.90 a year.",[322,568,569,572,573,387,576,579],{},[181,570,571],{},"intervals"," takes ",[181,574,575],{},"month",[181,577,578],{},"year"," or both. Each is a separate price at the\nprovider.",[322,581,582,585],{},[181,583,584],{},"trialDays"," applies to every interval of the plan.",[322,587,588],{},"The free tier is not in the catalogue. A free account is one with no grant.",[177,590,591,592,595,596,598,599,602,603,605,606,194],{},"The build refuses a plan id outside ",[181,593,594],{},"\u002F^[a-z][a-z0-9]*$\u002F"," (",[181,597,390],{},", never\n",[181,600,601],{},"proMax","), plans in more than one currency, a year listed cheaper than a month,\nand a plan whose ",[181,604,525],{}," is missing from ",[181,607,608],{},"payments.modes",[610,611,613],"h3",{"id":612},"add-a-plan","Add a plan",[319,615,616,621,632,657],{},[322,617,618,619,194],{},"Add the entry to ",[181,620,382],{},[322,622,623,624,627,628,631],{},"Give it an entry in ",[181,625,626],{},"access.planPermissions"," and ",[181,629,630],{},"orgs.seatLimits",". Both are\ntyped over every plan id, so typecheck fails until you do.",[322,633,634,635,387,638,391,641,644,645,648,649,656],{},"Add its copy to every locale: ",[181,636,637],{},"pricing.plans.\u003Cid>.title",[181,639,640],{},".description",[181,642,643],{},".feature1",", up to ",[181,646,647],{},".feature8",".\n",[185,650,653],{"href":651,"rel":652,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Ftest\u002Funit\u002Fplan-copy.test.ts",[189],[181,654,655],{},"test\u002Funit\u002Fplan-copy.test.ts"," fails on a\nmissing key.",[322,658,659],{},"Create the price at each provider and set its refs.",[177,661,662],{},"The pricing page builds its cards from the catalogue in declaration order, so\nthere is no page to edit. The monthly\u002Fyearly toggle appears once some plan\ndeclares both intervals.",[610,664,666],{"id":665},"set-the-refs","Set the refs",[177,668,669,670,673],{},"Every price has a ref slot: ",[181,671,672],{},"\u003Cplan>_\u003Cinterval>"," for a subscription and the bare\nplan id for a one-time plan. Each slot takes one variable per enabled provider.\nThe shipped catalogue has five:",[397,675,679],{"className":676,"code":677,"language":678,"meta":402,"style":402},"language-sh shiki shiki-themes github-light github-light github-dark","NUXT_PAYMENTS_STRIPE_REFS_PRO_MONTH=price_...\nNUXT_PAYMENTS_STRIPE_REFS_PRO_YEAR=price_...\nNUXT_PAYMENTS_STRIPE_REFS_PROMAX_MONTH=price_...\nNUXT_PAYMENTS_STRIPE_REFS_PROMAX_YEAR=price_...\nNUXT_PAYMENTS_STRIPE_REFS_LIFETIME=price_...\n","sh",[181,680,681,693,702,711,720],{"__ignoreMap":402},[406,682,683,686,690],{"class":408,"line":12},[406,684,685],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS_PRO_MONTH",[406,687,689],{"class":688},"s8jYJ","=",[406,691,692],{"class":434},"price_...\n",[406,694,695,698,700],{"class":408,"line":17},[406,696,697],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS_PRO_YEAR",[406,699,689],{"class":688},[406,701,692],{"class":434},[406,703,704,707,709],{"class":408,"line":22},[406,705,706],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS_PROMAX_MONTH",[406,708,689],{"class":688},[406,710,692],{"class":434},[406,712,713,716,718],{"class":408,"line":27},[406,714,715],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS_PROMAX_YEAR",[406,717,689],{"class":688},[406,719,692],{"class":434},[406,721,722,725,727],{"class":408,"line":32},[406,723,724],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS_LIFETIME",[406,726,689],{"class":688},[406,728,692],{"class":434},[177,730,731],{},"Or the whole map in one variable:",[397,733,735],{"className":676,"code":734,"language":678,"meta":402,"style":402},"NUXT_PAYMENTS_STRIPE_REFS='{\"pro_month\":\"price_1\",\"pro_year\":\"price_2\",\"promax_month\":\"price_3\",\"promax_year\":\"price_4\",\"lifetime\":\"price_5\"}'\n",[181,736,737],{"__ignoreMap":402},[406,738,739,742,744],{"class":408,"line":12},[406,740,741],{"class":415},"NUXT_PAYMENTS_STRIPE_REFS",[406,743,689],{"class":688},[406,745,746],{"class":434},"'{\"pro_month\":\"price_1\",\"pro_year\":\"price_2\",\"promax_month\":\"price_3\",\"promax_year\":\"price_4\",\"lifetime\":\"price_5\"}'\n",[177,748,749,750,753],{},"Set every declared interval, including one a customer only reaches by switching\nin the portal. A subscription that moves onto a price no slot holds records no\nplan. Each slot needs its own id: checkout refuses two slots sharing one and\nnames both variables. Outside ",[181,751,752],{},"pnpm dev",", the Worker logs every unset ref at\nboot.",[610,755,757],{"id":756},"sell-something-repeatable","Sell something repeatable",[177,759,760,761,764],{},"A one-time plan with ",[181,762,763],{},"repeatable: true"," can be bought again and again:",[397,766,768],{"className":399,"code":767,"language":401,"meta":402,"style":402},"credits: {\n  mode: 'one-time',\n  currency: 'usd',\n  amountMinor: 1900,\n  repeatable: true,\n},\n",[181,769,770,777,788,799,811,823],{"__ignoreMap":402},[406,771,772,775],{"class":408,"line":12},[406,773,774],{"class":411},"credits",[406,776,416],{"class":415},[406,778,779,782,784,786],{"class":408,"line":17},[406,780,781],{"class":411},"  mode",[406,783,431],{"class":415},[406,785,530],{"class":434},[406,787,438],{"class":415},[406,789,790,793,795,797],{"class":408,"line":22},[406,791,792],{"class":411},"  currency",[406,794,431],{"class":415},[406,796,448],{"class":434},[406,798,438],{"class":415},[406,800,801,804,806,809],{"class":408,"line":27},[406,802,803],{"class":411},"  amountMinor",[406,805,431],{"class":415},[406,807,808],{"class":460},"1900",[406,810,438],{"class":415},[406,812,813,816,818,821],{"class":408,"line":32},[406,814,815],{"class":411},"  repeatable",[406,817,431],{"class":415},[406,819,820],{"class":460},"true",[406,822,438],{"class":415},[406,824,825],{"class":408,"line":37},[406,826,555],{"class":415},[177,828,829,830,832,833,837],{},"It writes no grant, so it unlocks no permission and never opens the plan gate.\nEach purchase is still recorded and fires ",[181,831,360],{},", which is where\nyou add credits to a balance of your own\n(",[185,834,836],{"href":835},"#react-to-a-purchase","React to a purchase","). For volume discounts, declare\none repeatable plan per pack size, each with its own price.",[196,839,841],{"id":840},"connect-a-provider","Connect a provider",[319,843,844,850,859,866,869],{},[322,845,846,847,849],{},"Add the provider to ",[181,848,208],{},". The first entry is the default\nwhen a checkout names none.",[322,851,852,853,391,856,194],{},"Set ",[181,854,855],{},"NUXT_PAYMENTS_\u003CPROVIDER>_SECRET_KEY",[181,857,858],{},"NUXT_PAYMENTS_\u003CPROVIDER>_WEBHOOK_SECRET",[322,860,861,862,865],{},"Register ",[181,863,864],{},"https:\u002F\u002F\u003Cyour-domain>\u002Fwebhooks\u002Fpayments\u002F\u003Cprovider>"," with the\nprovider and subscribe it to the events below. That endpoint's signing\nsecret is the webhook secret.",[322,867,868],{},"Set a ref for every slot.",[322,870,871],{},"In the provider's portal settings, list every plan and interval a customer\nmay switch to.",[223,873,874,884],{},[226,875,876],{},[229,877,878,881],{},[232,879,880],{},"Provider",[232,882,883],{},"Subscribe the endpoint to",[247,885,886,913,940,970],{},[229,887,888,890],{},[252,889,236],{},[252,891,892,387,895,387,898,387,901,387,904,387,907,387,910],{},[181,893,894],{},"checkout.session.completed",[181,896,897],{},"checkout.session.async_payment_succeeded",[181,899,900],{},"customer.subscription.created",[181,902,903],{},"customer.subscription.updated",[181,905,906],{},"customer.subscription.deleted",[181,908,909],{},"charge.refunded",[181,911,912],{},"charge.dispute.closed",[229,914,915,917],{},[252,916,239],{},[252,918,919,387,922,387,925,387,928,387,931,387,934,387,937],{},[181,920,921],{},"order.paid",[181,923,924],{},"order.refunded",[181,926,927],{},"subscription.created",[181,929,930],{},"subscription.active",[181,932,933],{},"subscription.updated",[181,935,936],{},"subscription.canceled",[181,938,939],{},"subscription.revoked",[229,941,942,944],{},[252,943,242],{},[252,945,946,387,949,387,952,387,955,387,958,387,961,387,964,387,967],{},[181,947,948],{},"order_created",[181,950,951],{},"order_refunded",[181,953,954],{},"subscription_created",[181,956,957],{},"subscription_updated",[181,959,960],{},"subscription_resumed",[181,962,963],{},"subscription_cancelled",[181,965,966],{},"subscription_expired",[181,968,969],{},"subscription_payment_refunded",[229,971,972,974],{},[252,973,245],{},[252,975,976,387,979,387,981,387,984,387,987,387,990,387,993,387,996,387,999,387,1002,387,1005,387,1007,387,1010],{},[181,977,978],{},"checkout.completed",[181,980,930],{},[181,982,983],{},"subscription.trialing",[181,985,986],{},"subscription.paid",[181,988,989],{},"subscription.update",[181,991,992],{},"subscription.scheduled_cancel",[181,994,995],{},"subscription.paused",[181,997,998],{},"subscription.unpaid",[181,1000,1001],{},"subscription.past_due",[181,1003,1004],{},"subscription.expired",[181,1006,936],{},[181,1008,1009],{},"refund.created",[181,1011,1012],{},"dispute.created",[177,1014,1015],{},"What each provider needs on top:",[557,1017,1018,1025,1075,1082],{},[322,1019,1020,1021,1024],{},"Stripe: pin the webhook endpoint's API version to ",[181,1022,1023],{},"2025-03-31.basil",", the\nversion the adapter calls with, or payload fields move under it. A restricted\nkey also needs read access to promotion codes and prices and write access to\nrefunds.",[322,1026,1027,1028,1031,1032,1035,1036,1039,1040,1043,1044,1047,1048,627,1051,1054,1055,1058,1059,1062,1063,1066,1067,1070,1071,1074],{},"Polar: set ",[181,1029,1030],{},"NUXT_PAYMENTS_POLAR_ENVIRONMENT"," to ",[181,1033,1034],{},"sandbox"," or ",[181,1037,1038],{},"production",".\nUnset means production. Give the webhook endpoint API version ",[181,1041,1042],{},"2026-10",", the\nversion the adapter calls with. Polar's dashboard also offers the next\nversion, which Polar can still change. The access token needs\n",[181,1045,1046],{},"checkouts:write"," to open checkouts, ",[181,1049,1050],{},"webhooks:read",[181,1052,1053],{},"orders:read"," for the\nrepair when a webhook never arrives, ",[181,1056,1057],{},"customer_sessions:write"," for the\nportal, ",[181,1060,1061],{},"customers:write"," to keep the billing email in step,\n",[181,1064,1065],{},"subscriptions:write"," to cancel on account deletion, ",[181,1068,1069],{},"discounts:read"," for\ncoupons and ",[181,1072,1073],{},"refunds:write"," for staff refunds.",[322,1076,1077,1078,1081],{},"Lemon Squeezy: set ",[181,1079,1080],{},"NUXT_PAYMENTS_LEMONSQUEEZY_STORE_ID"," to the numeric store\nid.",[322,1083,1084,1085,1031,1088,1035,1090,1092,1093,1095],{},"Creem: set ",[181,1086,1087],{},"NUXT_PAYMENTS_CREEM_ENVIRONMENT",[181,1089,1034],{},[181,1091,1038],{},".\nUnset means the test API. Subscribe to the whole list above, which is longer\nthan Creem's own guide: without ",[181,1094,930],{},", a customer who\nresubscribes never gets access back.",[196,1097,1099],{"id":1098},"test-locally","Test locally",[177,1101,1102,1103,1105],{},"Payments ship on with Stripe, and checkout answers 500 until the secret key and\nwebhook secret are set. Use Stripe's test keys, ",[181,1104,1034],{}," on Polar and Creem,\nand Lemon Squeezy's test mode.",[177,1107,1108],{},"Stripe's CLI forwards webhooks to the dev server:",[397,1110,1112],{"className":676,"code":1111,"language":678,"meta":402,"style":402},"stripe listen --forward-to localhost:3000\u002Fwebhooks\u002Fpayments\u002Fstripe\n",[181,1113,1114],{"__ignoreMap":402},[406,1115,1116,1119,1122,1125],{"class":408,"line":12},[406,1117,1118],{"class":411},"stripe",[406,1120,1121],{"class":434}," listen",[406,1123,1124],{"class":460}," --forward-to",[406,1126,1127],{"class":434}," localhost:3000\u002Fwebhooks\u002Fpayments\u002Fstripe\n",[177,1129,1130,1131,1134,1135,1138,1139,1141,1142,194],{},"Put the ",[181,1132,1133],{},"whsec_…"," it prints in ",[181,1136,1137],{},"NUXT_PAYMENTS_STRIPE_WEBHOOK_SECRET",", restart\n",[181,1140,752],{},", and pay with the test card ",[181,1143,1144],{},"4242 4242 4242 4242",[177,1146,1147,1148,1151,1152,1155,1156,1159,1160,194],{},"The other providers need a public URL. ",[181,1149,1150],{},"pnpm tunnel"," opens a\n",[181,1153,1154],{},"*.trycloudflare.com"," tunnel to port 3000, which the dev server already accepts.\nRegister ",[181,1157,1158],{},"https:\u002F\u002F\u003Ctunnel-host>\u002Fwebhooks\u002Fpayments\u002F\u003Cprovider>"," in the provider's\nsandbox. A tunnel on a host of your own needs ",[181,1161,1162],{},"NUXT_DEV_TUNNEL_HOSTS",[177,1164,1165],{},"If a webhook never arrives, the page the buyer returned to asks the provider\ndirectly when its confirmation poll runs out, and writes the grant if the\ncheckout was paid. That repair covers signed-in checkouts on Stripe and Polar,\nand one-time purchases on Creem. Lemon Squeezy can't be repaired this way.",[196,1167,1169],{"id":1168},"gate-a-feature-on-a-plan","Gate a feature on a plan",[177,1171,1172,1173,1175,1176,328,1179,387,1181,627,1183,1185,1186,1189,1190,1197],{},"Sell a permission, then check the permission. ",[181,1174,626],{}," maps\neach plan to the permissions it sells; the shipped config sells\n",[181,1177,1178],{},"insights:read",[181,1180,386],{},[181,1182,390],{},[181,1184,394],{},", and nothing on ",[181,1187,1188],{},"free",". The\nroute checks the permission against the organization it acts on. This is\n",[185,1191,1194],{"href":1192,"rel":1193,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fapi\u002Fexample-paid.get.ts",[189],[181,1195,1196],{},"server\u002Fapi\u002Fexample-paid.get.ts",":",[397,1199,1201],{"className":399,"code":1200,"language":401,"meta":402,"style":402},"export default defineEventHandler(async (event) => {\n  const org = getQuery(event).org\n\n  const scope =\n    starter.orgs.enabled && typeof org === 'string' && org.length > 0\n      ? await requireOrgScope(event, org)\n      : undefined\n\n  await requirePlanGate(event, scope)\n\n  const session = await requirePermission(\n    event,\n    'insights:read',\n    scope?.rootOrgId,\n  )\n\n  return { email: session.user.email }\n})\n",[181,1202,1203,1235,1252,1258,1268,1303,1317,1325,1329,1340,1344,1361,1366,1373,1378,1383,1387,1395],{"__ignoreMap":402},[406,1204,1205,1208,1211,1214,1217,1220,1222,1226,1229,1232],{"class":408,"line":12},[406,1206,1207],{"class":688},"export",[406,1209,1210],{"class":688}," default",[406,1212,1213],{"class":411}," defineEventHandler",[406,1215,1216],{"class":415},"(",[406,1218,1219],{"class":688},"async",[406,1221,595],{"class":415},[406,1223,1225],{"class":1224},"sP4rz","event",[406,1227,1228],{"class":415},") ",[406,1230,1231],{"class":688},"=>",[406,1233,1234],{"class":415}," {\n",[406,1236,1237,1240,1243,1246,1249],{"class":408,"line":17},[406,1238,1239],{"class":688},"  const",[406,1241,1242],{"class":460}," org",[406,1244,1245],{"class":688}," =",[406,1247,1248],{"class":411}," getQuery",[406,1250,1251],{"class":415},"(event).org\n",[406,1253,1254],{"class":408,"line":22},[406,1255,1257],{"emptyLinePlaceholder":1256},true,"\n",[406,1259,1260,1262,1265],{"class":408,"line":27},[406,1261,1239],{"class":688},[406,1263,1264],{"class":460}," scope",[406,1266,1267],{"class":688}," =\n",[406,1269,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300],{"class":408,"line":32},[406,1271,1272],{"class":415},"    starter.orgs.enabled ",[406,1274,1275],{"class":688},"&&",[406,1277,1278],{"class":688}," typeof",[406,1280,1281],{"class":415}," org ",[406,1283,1284],{"class":688},"===",[406,1286,1287],{"class":434}," 'string'",[406,1289,1290],{"class":688}," &&",[406,1292,1293],{"class":415}," org.",[406,1295,1296],{"class":460},"length",[406,1298,1299],{"class":688}," >",[406,1301,1302],{"class":460}," 0\n",[406,1304,1305,1308,1311,1314],{"class":408,"line":37},[406,1306,1307],{"class":688},"      ?",[406,1309,1310],{"class":688}," await",[406,1312,1313],{"class":411}," requireOrgScope",[406,1315,1316],{"class":415},"(event, org)\n",[406,1318,1319,1322],{"class":408,"line":46},[406,1320,1321],{"class":688},"      :",[406,1323,1324],{"class":460}," undefined\n",[406,1326,1327],{"class":408,"line":51},[406,1328,1257],{"emptyLinePlaceholder":1256},[406,1330,1331,1334,1337],{"class":408,"line":56},[406,1332,1333],{"class":688},"  await",[406,1335,1336],{"class":411}," requirePlanGate",[406,1338,1339],{"class":415},"(event, scope)\n",[406,1341,1342],{"class":408,"line":64},[406,1343,1257],{"emptyLinePlaceholder":1256},[406,1345,1346,1348,1351,1353,1355,1358],{"class":408,"line":73},[406,1347,1239],{"class":688},[406,1349,1350],{"class":460}," session",[406,1352,1245],{"class":688},[406,1354,1310],{"class":688},[406,1356,1357],{"class":411}," requirePermission",[406,1359,1360],{"class":415},"(\n",[406,1362,1363],{"class":408,"line":78},[406,1364,1365],{"class":415},"    event,\n",[406,1367,1368,1371],{"class":408,"line":83},[406,1369,1370],{"class":434},"    'insights:read'",[406,1372,438],{"class":415},[406,1374,1375],{"class":408,"line":88},[406,1376,1377],{"class":415},"    scope?.rootOrgId,\n",[406,1379,1380],{"class":408,"line":93},[406,1381,1382],{"class":415},"  )\n",[406,1384,1385],{"class":408,"line":98},[406,1386,1257],{"emptyLinePlaceholder":1256},[406,1388,1389,1392],{"class":408,"line":103},[406,1390,1391],{"class":688},"  return",[406,1393,1394],{"class":415}," { email: session.user.email }\n",[406,1396,1397],{"class":408,"line":108},[406,1398,1399],{"class":415},"})\n",[177,1401,1402,1405,1406,1409,1410,1412,1413,1416,1417,1420,1421,194],{},[181,1403,1404],{},"requirePermission"," answers 402 with ",[181,1407,1408],{},"data.reason: 'upgrade-required'"," when no\nlive grant on that organization includes the permission. A tier you add later\nthat includes ",[181,1411,1178],{}," unlocks the route without touching it.\n",[181,1414,1415],{},"requireEntitlement(event, ['pro'], rootOrgId)"," checks plan ids instead, and\n",[181,1418,1419],{},"requireEntitlement(event, undefined, rootOrgId)"," accepts any live grant. With\norganizations on, pass the organization id: without it these throw a 500. With\npayments off, every paid check passes. Roles and permissions are covered in\n",[185,1422,1423],{"href":53},"Permissions",[177,1425,1426,1427,1430,1431,387,1434,387,1437,1440,1441,1444,1445,1447,1448,1450,1451,1458],{},"In the browser, ",[181,1428,1429],{},"useEntitlement(org)"," returns ",[181,1432,1433],{},"active",[181,1435,1436],{},"hasPlan",[181,1438,1439],{},"pending","\nand ",[181,1442,1443],{},"failed"," for rendering. Wait for ",[181,1446,1439],{}," before you show an upsell, and\ncheck ",[181,1449,1443],{}," before you tell a customer they are on the free plan.\n",[185,1452,1455],{"href":1453,"rel":1454,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fapp\u002Fcomponents\u002FPaidFeatureCard.vue",[189],[181,1456,1457],{},"PaidFeatureCard.vue"," and the route\nabove are the worked example; point them at something real or delete them.",[610,1460,1462],{"id":1461},"lock-the-whole-product-behind-a-plan","Lock the whole product behind a plan",[177,1464,852,1465,1468,1469,1472],{},[181,1466,1467],{},"payments.gate: 'plan'"," and an organization's dashboard pages, plus every\nroute that calls ",[181,1470,1471],{},"requirePlanGate",", open only while its root holds a live\ngrant.",[557,1474,1475,1494],{},[322,1476,1477,1478,1481,1482,1485,1486,1489,1490,1493],{},"On the server, call ",[181,1479,1480],{},"requirePlanGate(event, scope)"," after ",[181,1483,1484],{},"requireOrgScope","\nin each product route. It answers 402 with ",[181,1487,1488],{},"data.reason: 'plan-required'",".\nUnder ",[181,1491,1492],{},"'none'"," it only checks the session, so you can add it now and turn the\ngate on later.",[322,1495,1426,1496,1499,1500,1507,1508,1511],{},[181,1497,1498],{},"PlanGate"," wraps the page slot in\n",[185,1501,1504],{"href":1502,"rel":1503,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fapp\u002Flayouts\u002Fdashboard.vue",[189],[181,1505,1506],{},"app\u002Flayouts\u002Fdashboard.vue"," and shows a lock\nthat links to pricing. A page that must stay reachable opts out with\n",[181,1509,1510],{},"definePageMeta({ planGate: false })",". Billing, members, general, audit,\naccount settings, the invitation page and the staff pages already do.",[177,1513,1514],{},"A live grant is an active or trialing subscription, a failed one inside its\ngrace, a cancelled one with paid time left, an unrefunded one-time purchase, or\na staff grant. Staff are gated in their own organizations like anyone else.",[196,1516,836],{"id":1517},"react-to-a-purchase",[177,1519,1520,1521,1528,1529,1531,1532,1197],{},"Add a listener file to ",[185,1522,1525],{"href":1523,"rel":1524,"title":190},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Ftree\u002Fmain\u002Fserver\u002Flisteners",[189],[181,1526,1527],{},"server\u002Flisteners\u002F"," (the bus is\ncovered in ",[185,1530,81],{"href":80},"). It runs after the grant is committed.\n",[181,1533,1534],{},"server\u002Flisteners\u002Fgrant-credits.ts",[397,1536,1538],{"className":399,"code":1537,"language":401,"meta":402,"style":402},"import { defineListener } from '#server\u002Fevents\u002Ftypes'\n\nexport default defineListener({\n  event: 'payments:settled',\n\n  async handle({ userId, eventRowId, normalized }, { event }) {\n    if (userId === null || eventRowId === undefined) return\n    if (normalized.type !== 'payment.succeeded') return\n\n    await useDrizzle(event)\n      .insert(tables.creditGrants)\n      .values({ userId, eventRowId, amount: 500 })\n      .onConflictDoNothing()\n  },\n})\n",[181,1539,1540,1554,1558,1570,1580,1584,1616,1645,1662,1666,1677,1688,1704,1714,1718],{"__ignoreMap":402},[406,1541,1542,1545,1548,1551],{"class":408,"line":12},[406,1543,1544],{"class":688},"import",[406,1546,1547],{"class":415}," { defineListener } ",[406,1549,1550],{"class":688},"from",[406,1552,1553],{"class":434}," '#server\u002Fevents\u002Ftypes'\n",[406,1555,1556],{"class":408,"line":17},[406,1557,1257],{"emptyLinePlaceholder":1256},[406,1559,1560,1562,1564,1567],{"class":408,"line":22},[406,1561,1207],{"class":688},[406,1563,1210],{"class":688},[406,1565,1566],{"class":411}," defineListener",[406,1568,1569],{"class":415},"({\n",[406,1571,1572,1575,1578],{"class":408,"line":27},[406,1573,1574],{"class":415},"  event: ",[406,1576,1577],{"class":434},"'payments:settled'",[406,1579,438],{"class":415},[406,1581,1582],{"class":408,"line":32},[406,1583,1257],{"emptyLinePlaceholder":1256},[406,1585,1586,1589,1592,1595,1598,1600,1603,1605,1608,1611,1613],{"class":408,"line":37},[406,1587,1588],{"class":688},"  async",[406,1590,1591],{"class":411}," handle",[406,1593,1594],{"class":415},"({ ",[406,1596,1597],{"class":1224},"userId",[406,1599,387],{"class":415},[406,1601,1602],{"class":1224},"eventRowId",[406,1604,387],{"class":415},[406,1606,1607],{"class":1224},"normalized",[406,1609,1610],{"class":415}," }, { ",[406,1612,1225],{"class":1224},[406,1614,1615],{"class":415}," }) {\n",[406,1617,1618,1621,1624,1626,1629,1632,1635,1637,1640,1642],{"class":408,"line":46},[406,1619,1620],{"class":688},"    if",[406,1622,1623],{"class":415}," (userId ",[406,1625,1284],{"class":688},[406,1627,1628],{"class":460}," null",[406,1630,1631],{"class":688}," ||",[406,1633,1634],{"class":415}," eventRowId ",[406,1636,1284],{"class":688},[406,1638,1639],{"class":460}," undefined",[406,1641,1228],{"class":415},[406,1643,1644],{"class":688},"return\n",[406,1646,1647,1649,1652,1655,1658,1660],{"class":408,"line":51},[406,1648,1620],{"class":688},[406,1650,1651],{"class":415}," (normalized.type ",[406,1653,1654],{"class":688},"!==",[406,1656,1657],{"class":434}," 'payment.succeeded'",[406,1659,1228],{"class":415},[406,1661,1644],{"class":688},[406,1663,1664],{"class":408,"line":56},[406,1665,1257],{"emptyLinePlaceholder":1256},[406,1667,1668,1671,1674],{"class":408,"line":64},[406,1669,1670],{"class":688},"    await",[406,1672,1673],{"class":411}," useDrizzle",[406,1675,1676],{"class":415},"(event)\n",[406,1678,1679,1682,1685],{"class":408,"line":73},[406,1680,1681],{"class":415},"      .",[406,1683,1684],{"class":411},"insert",[406,1686,1687],{"class":415},"(tables.creditGrants)\n",[406,1689,1690,1692,1695,1698,1701],{"class":408,"line":78},[406,1691,1681],{"class":415},[406,1693,1694],{"class":411},"values",[406,1696,1697],{"class":415},"({ userId, eventRowId, amount: ",[406,1699,1700],{"class":460},"500",[406,1702,1703],{"class":415}," })\n",[406,1705,1706,1708,1711],{"class":408,"line":83},[406,1707,1681],{"class":415},[406,1709,1710],{"class":411},"onConflictDoNothing",[406,1712,1713],{"class":415},"()\n",[406,1715,1716],{"class":408,"line":88},[406,1717,515],{"class":415},[406,1719,1720],{"class":408,"line":93},[406,1721,1399],{"class":415},[177,1723,1724,1727,1728,1731,1732,194],{},[181,1725,1726],{},"creditGrants"," is your own table in ",[181,1729,1730],{},"server\u002Fdatabase\u002Fcustom\u002Fschema.ts",", with a\nunique ",[181,1733,1602],{},[557,1735,1736,1751,1759,1764,1770],{},[322,1737,1738,1740,1741,1744,1745,1748,1749,194],{},[181,1739,360],{}," fires once for every provider event the kit applies:\npurchases, subscription starts and changes, cancellations and refunds. Filter\non ",[181,1742,1743],{},"normalized.type",". A one-time purchase is ",[181,1746,1747],{},"payment.succeeded",", a new\nsubscription ",[181,1750,927],{},[322,1752,1753,1755,1756,1758],{},[181,1754,1602],{}," names one ",[181,1757,348],{}," row per provider event, so a unique\nconstraint on it survives redelivery.",[322,1760,1761,1763],{},[181,1762,1597],{}," is null for a purchase the kit did not start, such as one made from\nthe provider's dashboard.",[322,1765,1766,1769],{},[181,1767,1768],{},"payments:refunded"," fires when money goes back. Take back there whatever you\ngranted.",[322,1771,1772,1773,1776,1777,1779],{},"A listener that throws is logged and never retried. Work that must not be\nlost goes on the queue with ",[181,1774,1775],{},"enqueue()","\n(",[185,1778,76],{"href":75},").",[177,1781,1782,1783,1786,1787,1790,1791,431,1794,1797,1798,627,1801,1804],{},"A guest, or a signed-in buyer of a one-time plan who named no organization,\nreturns to ",[181,1784,1785],{},"\u002Fcheckout\u002Fcomplete",". That page polls the purchase and shows what a\nlistener recorded with ",[181,1788,1789],{},"recordDelivery"," from ",[181,1792,1793],{},"#server\u002Fpayments\u002Fsettled",[181,1795,1796],{},"url","\nbecomes a button, ",[181,1799,1800],{},"title",[181,1802,1803],{},"message"," are shown, and any other key is listed.",[397,1806,1808],{"className":399,"code":1807,"language":401,"meta":402,"style":402},"await recordDelivery(event, eventRowId, {\n  title: 'Invitation sent',\n  message: 'Check your inbox for the GitHub invitation.',\n})\n",[181,1809,1810,1821,1831,1841],{"__ignoreMap":402},[406,1811,1812,1815,1818],{"class":408,"line":12},[406,1813,1814],{"class":688},"await",[406,1816,1817],{"class":411}," recordDelivery",[406,1819,1820],{"class":415},"(event, eventRowId, {\n",[406,1822,1823,1826,1829],{"class":408,"line":17},[406,1824,1825],{"class":415},"  title: ",[406,1827,1828],{"class":434},"'Invitation sent'",[406,1830,438],{"class":415},[406,1832,1833,1836,1839],{"class":408,"line":22},[406,1834,1835],{"class":415},"  message: ",[406,1837,1838],{"class":434},"'Check your inbox for the GitHub invitation.'",[406,1840,438],{"class":415},[406,1842,1843],{"class":408,"line":27},[406,1844,1399],{"class":415},[196,1846,1848],{"id":1847},"trials","Trials",[177,1850,852,1851,1853,1854,1856],{},[181,1852,584],{}," on a subscription plan. Stripe and Polar take the length at\ncheckout. Creem and Lemon Squeezy can only run a trial set on the product, so\nthe build refuses ",[181,1855,584],{}," while either is enabled. The provider takes the\ncard up front and charges it when the trial ends.",[177,1858,1859],{},"Each buyer gets one trial. Checkout drops it when the payer, or the\norganization being billed, has ever held a subscription on any provider. On\nPolar, also turn on Prevent trial abuse in the dashboard to catch the same\nperson on a second account.",[196,1861,1863],{"id":1862},"coupons","Coupons",[177,1865,1866,1867,1870,1871,1874,1875,387,1878,1881,1882,1885,1886,1889],{},"Create codes in the provider's dashboard; on Stripe that is a promotion code on\na coupon. Link to any page with ",[181,1868,1869],{},"?coupon=CODE",". The kit keeps the code in a\n",[181,1872,1873],{},"coupon"," cookie for seven days, shows it on the pricing page and sends it with\nthe next checkout. Checkout asks the provider whether the code applies before it\nopens a session, and answers 422 with ",[181,1876,1877],{},"coupon-unknown",[181,1879,1880],{},"coupon-not-for-plan","\nor ",[181,1883,1884],{},"coupon-not-for-buyer"," when it does not. Checkouts carrying a code are\nlimited to 20 an hour per account, or per IP for a guest.\n",[181,1887,1888],{},"payments.couponField"," decides whether the hosted page also offers its own code\nbox.",[196,1891,1893],{"id":1892},"failed-renewals-refunds-and-disputes","Failed renewals, refunds and disputes",[177,1895,1896,1897,1900,1901,1904],{},"A subscription whose renewal charge fails goes ",[181,1898,1899],{},"past_due"," and keeps access for\n",[181,1902,1903],{},"payments.pastDueGraceDays",", counted from the first failed charge, while the\nprovider retries. Everyone who can bill the organization sees a banner linking\nto the portal. Match the grace to your provider's retry window: 14 days is\nStripe's and Lemon Squeezy's default, Polar retries for 21. The kit sends no\ndunning email of its own, so turn on Stripe's emails for failed card payments.",[177,1906,1907],{},"A full refund of a one-time purchase revokes that purchase's grant, and a\npartial refund revokes nothing. A refunded subscription loses access at once,\nbut the provider keeps billing it until you cancel it there. A lost Stripe\ndispute revokes, a Creem dispute revokes as soon as it opens, and Polar and\nLemon Squeezy send no dispute events.",[196,1909,1911],{"id":1910},"tax","Tax",[177,1913,1914,1915,1918,1919,1922],{},"Polar, Lemon Squeezy and Creem are the merchant of record and handle sales tax.\nOn Stripe you are the seller. ",[181,1916,1917],{},"NUXT_PAYMENTS_STRIPE_TAX_ENABLED=true"," turns on\nStripe Tax and collects a billing address. Tax is added on top only when each\nPrice has ",[181,1920,1921],{},"tax_behavior: exclusive",", which you set in Stripe and cannot change\nlater. Without tax registrations Stripe calculates zero and reports no error.",[196,1924,1926],{"id":1925},"guest-checkout","Guest checkout",[177,1928,1929,1932],{},[181,1930,1931],{},"payments.identity: 'guest'"," lets someone without an account buy a one-time\nplan. They give an email and pass a Turnstile check. When the webhook lands,\nthe kit creates an unverified account for the address the provider recorded\nand grants the purchase to it. Subscriptions still need a signed-in, verified\naccount.",[177,1934,1935],{},"A new account takes the name the payer gave the provider, and its first\norganization is named after it. Invisible and control characters are stripped,\nand a name that is then blank, longer than 100 characters or holds no letter or\ndigit is dropped, which leaves the address's local part as before. An account\nthat already exists is never renamed.",[177,1937,1938,1939,1942,1943,1779],{},"An app that signs the paying browser in to that account holds a session nobody\nhas proven the address for. Under ",[181,1940,1941],{},"auth.primary: 'passwordless'"," the kit holds\nit back until a sign-in by email link, which signs that session out\n(",[185,1944,44],{"href":1945},"\u002Fdocs\u002Fauth#verify-email-addresses",[196,1947,1949],{"id":1948},"grant-a-plan-or-refund-from-the-customers-page","Grant a plan or refund from the customers page",[177,1951,1952,1953,1956,1957,1197],{},"Staff holding ",[181,1954,1955],{},"users:manage"," get two row actions on ",[181,1958,1959],{},"\u002Fdashboard\u002Fcustomers",[557,1961,1962,1968],{},[322,1963,1964,1965,1967],{},"Grant a plan gives one of the customer's organizations a plan without\npayment, forever or until a date. It opens everything the paid plan opens and\nis written to the organization's audit log. Take back removes it. A grant\nfires no ",[181,1966,360],{},", so listeners that hand out credits give none.",[322,1969,1970],{},"Refund a purchase asks the provider to return what is left of a one-time\npurchase. Access ends when the refund webhook arrives, the same as a refund\nmade in the provider's dashboard. Refund subscriptions in the provider's\ndashboard.",[177,1972,1973,1974,387,1977,627,1980,194],{},"You can't change the plan of an organization that you or another administrator\nbelongs to, and you can't refund your own or another administrator's purchase.\nThe routes are ",[181,1975,1976],{},"POST \u002Fapi\u002Fadmin\u002Fbilling\u002Fgrant",[181,1978,1979],{},"\u002Frevoke",[181,1981,1982],{},"\u002Frefund",[196,1984,1986],{"id":1985},"turn-payments-off","Turn payments off",[177,1988,852,1989,1992,1993,1995,1996,1998,1999,2001],{},[181,1990,1991],{},"payments.enabled: false",". No adapter is bundled, ",[181,1994,331],{}," and the\ncheckout and webhook routes answer 404, and every paid check passes:\n",[181,1997,1404],{}," grants purchasable permissions to everyone. The build\nrefuses ",[181,2000,1467],{}," with payments off.",[196,2003,20],{"id":2004},"configuration",[223,2006,2007,2020],{},[226,2008,2009],{},[229,2010,2011,2014,2017],{},[232,2012,2013],{},"Key",[232,2015,2016],{},"Default",[232,2018,2019],{},"What it does",[247,2021,2022,2036,2054,2068,2082,2099,2115,2132,2149,2165,2182],{},[229,2023,2024,2029,2033],{},[252,2025,2026],{},[181,2027,2028],{},"payments.enabled",[252,2030,2031],{},[181,2032,820],{},[252,2034,2035],{},"Off bundles no adapter and lets every paid check pass.",[229,2037,2038,2043,2048],{},[252,2039,2040],{},[181,2041,2042],{},"payments.identity",[252,2044,2045],{},[181,2046,2047],{},"'account'",[252,2049,2050,2053],{},[181,2051,2052],{},"'guest'"," allows one-time purchases without an account.",[229,2055,2056,2060,2065],{},[252,2057,2058],{},[181,2059,608],{},[252,2061,2062],{},[181,2063,2064],{},"['one-time', 'subscription']",[252,2066,2067],{},"Purchase shapes the app sells. A plan in any other mode fails the build.",[229,2069,2070,2074,2079],{},[252,2071,2072],{},[181,2073,208],{},[252,2075,2076],{},[181,2077,2078],{},"['stripe']",[252,2080,2081],{},"Providers to bundle. The first is the default.",[229,2083,2084,2088,2092],{},[252,2085,2086],{},[181,2087,1888],{},[252,2089,2090],{},[181,2091,820],{},[252,2093,2094,2095,2098],{},"Whether the hosted checkout shows its own code box. ",[181,2096,2097],{},"false"," fails the build with Creem.",[229,2100,2101,2105,2109],{},[252,2102,2103],{},[181,2104,1903],{},[252,2106,2107],{},[181,2108,461],{},[252,2110,2111,2112,2114],{},"Days a failed subscription keeps access after its first failed charge. ",[181,2113,2097],{}," ends access at once.",[229,2116,2117,2122,2126],{},[252,2118,2119],{},[181,2120,2121],{},"payments.gate",[252,2123,2124],{},[181,2125,1492],{},[252,2127,2128,2131],{},[181,2129,2130],{},"'plan'"," locks an organization's pages until it holds a live grant.",[229,2133,2134,2138,2146],{},[252,2135,2136],{},[181,2137,382],{},[252,2139,2140,387,2142,387,2144],{},[181,2141,386],{},[181,2143,390],{},[181,2145,394],{},[252,2147,2148],{},"The catalogue.",[229,2150,2151,2156,2159],{},[252,2152,2153],{},[181,2154,2155],{},"payments.adapters",[252,2157,2158],{},"none",[252,2160,2161,2162,194],{},"Capabilities and credentials of an adapter you add in ",[181,2163,2164],{},"server\u002Fpayments\u002Fadapters\u002Fcustom\u002F",[229,2166,2167,2171,2176],{},[252,2168,2169],{},[181,2170,626],{},[252,2172,2173,2175],{},[181,2174,1178],{}," on every paid plan",[252,2177,2178,2179,2181],{},"Permissions each plan sells. ",[181,2180,1188],{}," is the tier with no grant.",[229,2183,2184,2188,2191],{},[252,2185,2186],{},[181,2187,630],{},[252,2189,2190],{},"free 3, pro 10, promax unlimited, lifetime 10",[252,2192,2193],{},"Members each plan admits.",[223,2195,2196,2205],{},[226,2197,2198],{},[229,2199,2200,2203],{},[232,2201,2202],{},"Variable",[232,2204,2019],{},[247,2206,2207,2216,2225,2238,2248,2264,2273,2285],{},[229,2208,2209,2213],{},[252,2210,2211],{},[181,2212,855],{},[252,2214,2215],{},"API key.",[229,2217,2218,2222],{},[252,2219,2220],{},[181,2221,858],{},[252,2223,2224],{},"Webhook signing secret.",[229,2226,2227,2232],{},[252,2228,2229],{},[181,2230,2231],{},"NUXT_PAYMENTS_\u003CPROVIDER>_REFS_\u003CSLOT>",[252,2233,2234,2235,194],{},"The provider's id for one ref slot, such as ",[181,2236,2237],{},"REFS_PRO_MONTH",[229,2239,2240,2245],{},[252,2241,2242],{},[181,2243,2244],{},"NUXT_PAYMENTS_\u003CPROVIDER>_REFS",[252,2246,2247],{},"The whole ref map as JSON, keyed by slot.",[229,2249,2250,2256],{},[252,2251,2252,387,2254],{},[181,2253,1030],{},[181,2255,1087],{},[252,2257,2258,2259,1035,2261,2263],{},"Exactly ",[181,2260,1034],{},[181,2262,1038],{},". Unset, Polar is live and Creem is test.",[229,2265,2266,2270],{},[252,2267,2268],{},[181,2269,1080],{},[252,2271,2272],{},"Numeric store id.",[229,2274,2275,2280],{},[252,2276,2277],{},[181,2278,2279],{},"NUXT_PAYMENTS_STRIPE_TAX_ENABLED",[252,2281,2282,2284],{},[181,2283,820],{}," turns on Stripe Tax. Off otherwise.",[229,2286,2287,2292],{},[252,2288,2289],{},[181,2290,2291],{},"NUXT_PAYMENTS_STRIPE_TAX_ID_COLLECTION",[252,2293,2294,2296],{},[181,2295,820],{}," asks for a VAT or GST number at checkout. Off otherwise.",[196,2298,2300],{"id":2299},"good-to-know","Good to know",[557,2302,2303,2308,2315,2321,2324,2327,2338],{},[322,2304,2305,2307],{},[181,2306,481],{}," is only shown. The ref is what charges the card, and nothing\nchecks that the two agree, so change them together.",[322,2309,2310,2311,2314],{},"An empty line such as ",[181,2312,2313],{},"NUXT_PAYMENTS_STRIPE_REFS_PRO_MONTH="," overrides the\ndefault with an empty string. Comment unused lines out.",[322,2316,2317,2318,194],{},"Deleting an interval from a plan stops its ref variable being read, and\nsubscribers still on that price lose their plan on the next event. To stop\nselling it, delete it and keep its id in the JSON form, for example\n",[181,2319,2320],{},"NUXT_PAYMENTS_STRIPE_REFS='{\"pro_year\":\"price_…\"}'",[322,2322,2323],{},"A purchase can't be moved to another organization afterwards. The pricing\npage names the organization being billed before the buyer pays.",[322,2325,2326],{},"A subscription whose period end has passed grants nothing until the renewal\nwebhook moves the period on. A broken webhook endpoint locks paying customers\nout at their next renewal, and with the plan gate on that is the whole\nproduct.",[322,2328,2329,2330,2332,2333,2337],{},"The Polar adapter pins API version ",[181,2331,1042],{},". By Polar's published schedule,\nPolar stops serving it at its April 2027 release. From then on the adapter\nrepeats each refused request on Polar's current version, which it was never\nchecked against. Once per Worker isolate it logs the refusal and reports it\nto your\n",[185,2334,2336],{"href":2335},"\u002Fdocs\u002Fobservability#send-faults-to-sentry-or-better-stack","error trackers",".\nUpdate the kit before then.",[322,2339,2340],{},"Polar signs with either the secret's own bytes or its base64 decoding, the\nStandard Webhooks key. Secrets Polar creates from 8 September 2026 use the\nsecond, and so did secrets set through the API until 11 September. The\nadapter tries both, so paste the secret exactly as Polar shows it.",[2342,2343,2344],"style",{},"html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":402,"searchDepth":17,"depth":17,"links":2346},[2347,2348,2349,2354,2355,2356,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368],{"id":198,"depth":17,"text":199},{"id":316,"depth":17,"text":317},{"id":375,"depth":17,"text":376,"children":2350},[2351,2352,2353],{"id":612,"depth":22,"text":613},{"id":665,"depth":22,"text":666},{"id":756,"depth":22,"text":757},{"id":840,"depth":17,"text":841},{"id":1098,"depth":17,"text":1099},{"id":1168,"depth":17,"text":1169,"children":2357},[2358],{"id":1461,"depth":22,"text":1462},{"id":1517,"depth":17,"text":836},{"id":1847,"depth":17,"text":1848},{"id":1862,"depth":17,"text":1863},{"id":1892,"depth":17,"text":1893},{"id":1910,"depth":17,"text":1911},{"id":1925,"depth":17,"text":1926},{"id":1948,"depth":17,"text":1949},{"id":1985,"depth":17,"text":1986},{"id":2004,"depth":17,"text":20},{"id":2299,"depth":17,"text":2300},[2370,2371],{"path":53,"title":54,"description":55,"navTitle":54,"group":40,"order":56},{"path":70,"title":71,"description":72,"navTitle":71,"group":67,"order":73}]