[{"data":1,"prerenderedAt":444},["ShallowReactive",2],{"\u002Fblog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing-data":3},{"post":4,"surround":433},{"id":5,"title":6,"alternates":7,"authors":8,"badge":14,"body":16,"date":381,"dateModified":382,"description":26,"extension":383,"head":382,"hero_image_url":25,"json_ld":384,"meta":416,"navigation":417,"ogImage":382,"outbound_links":418,"path":419,"primary_keyword":420,"related_articles":421,"robots":382,"schemaOrg":382,"search_intent":422,"seo":423,"sitemap":424,"stem":425,"supporting_keywords":426,"tags":431,"__hash__":432},"blog_en\u002Fblog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing.md","Nuxt Stripe payments for one-time and subscription billing",[],[9],{"name":10,"to":11,"avatar":12},"Tom Han","https:\u002F\u002Fx.com\u002Ftomhan245",{"src":13},"https:\u002F\u002Fcdn.shipahe.ad\u002Ftomhan.webp",{"label":15},"How-to",{"type":17,"value":18,"toc":364},"minimark",[19,28,32,35,40,45,58,62,76,80,83,99,136,151,170,185,189,192,206,222,242,246,250,276,280,294,298,336,340,343,347],[20,21,22],"figure",{},[23,24],"img",{"src":25,"alt":26,"style":27},"https:\u002F\u002Fshipahe.ad\u002Fimages\u002Fblog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing\u002Fpost-496.webp","Set up Stripe payments in Nuxt: one-time checkout, subscriptions, trials, webhooks, testing, and launch steps with concrete examples you can ship today.","max-width:100%;border-radius:12px",[29,30,31],"p",{},"You want to accept money in your Nuxt app without duct-taping code together. Stripe is a solid choice, but mixing one-time purchases, subscriptions, trials, and the webhook glue gets confusing fast. This guide gives you a clear, repeatable pattern for Nuxt Stripe payments that you can put in production.",[29,33,34],{},"We will use Stripe Checkout because it is quick to implement, handles Strong Customer Authentication, and works for both one-time and recurring billing. The same structure applies whether you are selling a small digital product, an AI tool, or a full SaaS.",[36,37,39],"h2",{"id":38},"_1-choose-your-payment-flow","1) Choose your payment flow",[41,42,44],"h3",{"id":43},"pick-a-product-model","Pick a product model",[46,47,48,52,55],"ul",{},[49,50,51],"li",{},"One-time purchase. A single payment that unlocks a file, feature, or credit pack. Good for add-ons and downloadable assets.",[49,53,54],{},"Subscription. Recurring billing tied to plans. Add free trials or intro pricing if needed. Good for SaaS tiers and usage that resets monthly.",[49,56,57],{},"Hybrid. Mix both. For example, a monthly plan plus a one-time add-on that boosts limits. If you are building something like CoinDrop, you might sell a monthly tier and let users buy a one-time boost for a promotion.",[41,59,61],{"id":60},"choose-stripe-primitives","Choose Stripe primitives",[46,63,64,67,70,73],{},[49,65,66],{},"Define Products and Prices in the Stripe Dashboard. Use one-time prices for single charges and recurring prices for subscriptions.",[49,68,69],{},"Use Stripe Checkout for the hosted payment page. Always create the Checkout Session on your server and redirect the browser to it. This keeps PCI scope low and handles 3D Secure.",[49,71,72],{},"Use Webhooks to confirm payment and flip access in your app. Do not grant entitlements on a client-only success page.",[49,74,75],{},"Create a Stripe Customer for each user and store customer_id on your User row. That makes upgrades, refunds, and future purchases consistent.",[36,77,79],{"id":78},"_2-one-time-payments-with-stripe-checkout","2) One-time payments with Stripe Checkout",[29,81,82],{},"Your flow: user clicks Buy, your server creates a Checkout Session with mode set to payment, Stripe collects the card, Stripe pings your webhook, and your app grants access.",[84,85,86,93],"ol",{},[49,87,88,92],{},[89,90,91],"strong",{},"Define the catalog."," In Stripe, create a Product with a one-time Price. Record the price_id in your app config. Keep an allowlist of valid price ids on the server.",[49,94,95,98],{},[89,96,97],{},"Server route to create a session."," In Nuxt 3, add a POST route like \u002Fapi\u002Fcheckout that validates input, attaches user metadata, and returns session.url. Include success and cancel URLs that route back to your app.",[100,101,102,131],"pre",{},[103,104,105,106,113,116],"code",{},"\u002F\u002F server\u002Fapi\u002Fcheckout.post.ts\nimport Stripe from 'stripe'\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { apiVersion: '2023-10-16' })\n",[29,107,108,109],{},"const ALLOWED_PRICE_IDS = ",[110,111,112],"span",{},"'price_123', 'price_456'",[29,114,115],{},"export default defineEventHandler(async (event) => {\nconst body = await readBody(event)\nconst user = await getUserFromSession(event) \u002F\u002F your auth\nif (!ALLOWED_PRICE_IDS.includes(body.priceId)) throw createError({ statusCode: 400 })",[29,117,118,119,122,123,126,127,130],{},"const session = await stripe.checkout.sessions.create({\nmode: 'payment',\nline_items: ",[110,120,121],{},"{ price: body.priceId, quantity: body.quantity || 1 }",",\ncustomer: user.stripeCustomerId || undefined,\ncustomer_email: user.email,\nsuccess_url: ",[103,124,125],{},"${process.env.PUBLIC_BASE_URL}\u002Fpurchase\u002Fsuccess?session_id={CHECKOUT_SESSION_ID}",",\ncancel_url: ",[103,128,129],{},"${process.env.PUBLIC_BASE_URL}\u002Fpurchase\u002Fcancel",",\nmetadata: { user_id: String(user.id), product_key: body.productKey || '' },\nallow_promotion_codes: true,\nautomatic_tax: { enabled: true }\n}, {\nidempotencyKey: crypto.randomUUID()\n})",[29,132,133],{},[103,134,135],{},"return { url: session.url }\n})",[84,137,139,145],{"start":138},3,[49,140,141,144],{},[89,142,143],{},"Redirect from the client."," On your product page, call \u002Fapi\u002Fcheckout and redirect to the returned URL. Keep the UI clean. One Buy button, a short explainer, and a price.",[49,146,147,150],{},[89,148,149],{},"Flip access on webhook, not on the success page."," In your webhook handler, verify the signature and on checkout.session.completed mark the purchase as paid. Create a Purchase row that includes payment_intent id, user_id from metadata, and the product key. Grant access by inserting a record into a UserEntitlements table or toggling a feature flag.",[100,152,153,165],{},[103,154,155,156,159,162],{},"\u002F\u002F server\u002Fapi\u002Fstripe-webhook.post.ts\nimport Stripe from 'stripe'\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { apiVersion: '2023-10-16' })",[29,157,158],{},"export default defineEventHandler(async (event) => {\nconst sig = getHeader(event, 'stripe-signature') as string\nconst buf = await readRawBody(event)\nlet stripeEvent: Stripe.Event\ntry {\nstripeEvent = stripe.webhooks.constructEvent(buf!, sig, process.env.STRIPE_WEBHOOK_SECRET as string)\n} catch (err) {\nthrow createError({ statusCode: 400 })\n}",[29,160,161],{},"\u002F\u002F Idempotency: do nothing if we have processed this event.id before\nif (await alreadyHandled(stripeEvent.id)) return 'ok'",[29,163,164],{},"if (stripeEvent.type === 'checkout.session.completed') {\nconst session = stripeEvent.data.object as Stripe.Checkout.Session\nconst userId = Number(session.metadata?.user_id)\nawait grantOneTimeEntitlement({\nuserId,\npaymentIntentId: String(session.payment_intent),\nproductKey: String(session.metadata?.product_key)\n})\n}",[29,166,167],{},[103,168,169],{},"await markHandled(stripeEvent.id)\nreturn 'ok'\n})",[84,171,173,179],{"start":172},5,[49,174,175,178],{},[89,176,177],{},"Send receipts."," Stripe can send receipts automatically. If you send your own email, include a link to the protected page or download, and a VAT invoice link if you collect tax IDs.",[49,180,181,184],{},[89,182,183],{},"Protect the content."," Gate access server-side. For downloads, generate a short-lived signed URL after you confirm the paid Purchase record.",[36,186,188],{"id":187},"_3-subscriptions-and-trials","3) Subscriptions and trials",[29,190,191],{},"Subscriptions add lifecycle events. Plan for upgrades, downgrades, renewals, cancellations, and expired trials. Stripe Checkout creates the subscription and emits consistent events you can trust.",[84,193,194,200],{},[49,195,196,199],{},[89,197,198],{},"Create recurring prices."," In Stripe, set up monthly or yearly Prices. If you want a trial, either set a trial period on the Price or set trial_end when creating the Checkout Session to control the exact date.",[49,201,202,205],{},[89,203,204],{},"Subscription checkout route."," Similar to one-time, but set mode to subscription and pass the recurring price id. Keep a server-side allowlist of tier price ids, and add user_id and plan to metadata.",[100,207,208],{},[103,209,210,211,214,215,126,218,221],{},"\u002F\u002F server\u002Fapi\u002Fsubscribe.post.ts\nconst session = await stripe.checkout.sessions.create({\nmode: 'subscription',\nline_items: ",[110,212,213],{},"{ price: body.priceId, quantity: 1 }",",\ncustomer: user.stripeCustomerId || undefined,\nsuccess_url: ",[103,216,217],{},"${process.env.PUBLIC_BASE_URL}\u002Faccount\u002Fbilling?session_id={CHECKOUT_SESSION_ID}",[103,219,220],{},"${process.env.PUBLIC_BASE_URL}\u002Fpricing",",\nsubscription_data: {\ntrial_end: body.trialEnd || undefined,\nmetadata: { user_id: String(user.id), plan: body.planKey }\n},\nallow_promotion_codes: true\n})",[84,223,224,230,236],{"start":138},[49,225,226,229],{},[89,227,228],{},"Persist the subscription."," On checkout.session.completed, read session.subscription to get the subscription id. Store a Subscription row with status active, current_period_end, plan id, and the Stripe customer id. Use invoice.paid to extend access and invoice.payment_failed to start dunning with a short grace period.",[49,231,232,235],{},[89,233,234],{},"Upgrades and downgrades."," For upgrades mid-cycle, update the subscription item with proration_behavior set to create_prorations so users pay the difference. For downgrades, schedule the change at period end and reflect it in your UI with a plan_change_requested flag.",[49,237,238,241],{},[89,239,240],{},"Cancellations and trials."," On customer.subscription.updated or deleted, sync status to past_due, canceled, or paused. If a trial ends without payment, remove entitlements when the subscription becomes incomplete_expired.",[36,243,245],{"id":244},"_4-webhooks-testing-and-launch","4) Webhooks, testing, and launch",[41,247,249],{"id":248},"webhook-fundamentals","Webhook fundamentals",[46,251,252,258,264,270],{},[49,253,254,257],{},[89,255,256],{},"Verify signatures."," Use the signing secret from your Stripe Dashboard. Reject any event that fails verification.",[49,259,260,263],{},[89,261,262],{},"Idempotency and retries."," Store processed event ids. Stripe retries on failures and timeouts. Make handlers side-effect safe.",[49,265,266,269],{},[89,267,268],{},"Map events to users."," Put your internal user_id into Checkout Session metadata or into the Customer object. Do not rely on email lookups that can change.",[49,271,272,275],{},[89,273,274],{},"Choose the right events."," For one-time, rely on checkout.session.completed. For subscriptions, also listen to invoice.paid, invoice.payment_failed, customer.subscription.updated, and customer.subscription.deleted. Handle charge.refunded to revoke one-time access when needed.",[41,277,279],{"id":278},"receipts-and-emails","Receipts and emails",[46,281,282,288],{},[49,283,284,287],{},[89,285,286],{},"Stripe receipts."," Turn on email receipts in Stripe for payment confirmations and refunds.",[49,289,290,293],{},[89,291,292],{},"Your transactional emails."," Send welcomes, payment confirmations, dunning messages, and cancellation notices from your app. Include a Manage billing link in your account area.",[41,295,297],{"id":296},"testing","Testing",[46,299,300,306,312,318,324,330],{},[49,301,302,305],{},[89,303,304],{},"Use test keys and env vars."," Keep STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in .env. Never mix test and live data.",[49,307,308,311],{},[89,309,310],{},"Test 3D Secure and failures."," Use Stripe’s test cards to cover success, authentication required, insufficient funds, and generic declines. Verify your UI messages.",[49,313,314,317],{},[89,315,316],{},"Run webhooks locally."," Use the Stripe CLI to forward events to your machine: stripe listen --forward-to localhost:3000\u002Fapi\u002Fstripe-webhook. Replay an event to confirm idempotency.",[49,319,320,323],{},[89,321,322],{},"Validate entitlements."," After each test purchase, check database rows and confirm protected pages are gated. Revoke access and test again to catch race conditions.",[49,325,326,329],{},[89,327,328],{},"Refunds and disputes."," Add a simple admin action to refund and revoke access. For subscriptions, document whether you prorate on mid-cycle refunds.",[49,331,332,335],{},[89,333,334],{},"Go live safely."," Switch production to live keys, set the live webhook signing secret, and verify success_url and cancel_url use your live domain. Run a small live charge on your own card to sanity check.",[41,337,339],{"id":338},"where-a-nuxt-saas-starter-kit-helps","Where a Nuxt SaaS starter kit helps",[29,341,342],{},"If you want to ship fast, a solid Nuxt SaaS starter kit cuts weeks from setup. Shipahe.ad includes authentication, protected pages for paid features, subscription and one-time checkout flows, webhooks wired to entitlements, transactional emails, an admin panel to view users and ban spammers, multi-language support with an in-app switch, deployment presets, a prebuilt landing page you can customize, built-in analytics to watch signups and conversions, and SEO automation for meta tags and sitemaps. It also works cleanly with AI coding tools like Cursor or Claude, which helps you write server routes and handlers faster without fighting the stack.",[36,344,346],{"id":345},"key-takeaways","Key takeaways",[46,348,349,352,355,358,361],{},[49,350,351],{},"Stripe Checkout plus webhooks is the fastest, reliable path for Nuxt Stripe payments across one-time and subscriptions.",[49,353,354],{},"Model entitlements in your database and flip them only on verified webhook events, not on client redirects.",[49,356,357],{},"Keep a Stripe customer_id on each user and an allowlist of price ids on the server.",[49,359,360],{},"Test every branch in test mode, including authentication-required flows and failures, before going live.",[49,362,363],{},"A Nuxt starter kit with payments, protected pages, emails, and admin removes setup friction so you can focus on your product.",{"title":365,"searchDepth":366,"depth":366,"links":367},"",2,[368,372,373,374,380],{"id":38,"depth":366,"text":39,"children":369},[370,371],{"id":43,"depth":138,"text":44},{"id":60,"depth":138,"text":61},{"id":78,"depth":366,"text":79},{"id":187,"depth":366,"text":188},{"id":244,"depth":366,"text":245,"children":375},[376,377,378,379],{"id":248,"depth":138,"text":249},{"id":278,"depth":138,"text":279},{"id":296,"depth":138,"text":297},{"id":338,"depth":138,"text":339},{"id":345,"depth":366,"text":346},"2026-08-10",null,"md",{"@context":385,"@graph":386},"https:\u002F\u002Fschema.org",[387,391],{"@type":388,"headline":6,"description":26,"image":25,"inLanguage":389,"datePublished":390},"BlogPosting","en","2026-08-10 03:25:30",{"@type":392,"mainEntity":393},"FAQPage",[394,400,404,408,412],{"@type":395,"name":396,"acceptedAnswer":397},"Question","Should I use Stripe Checkout or the Payment Element in a Nuxt app?",{"@type":398,"text":399},"Answer","Use Stripe Checkout to ship faster with less PCI scope. Use the Payment Element if you need a fully embedded, highly customized form. Both work with Nuxt.",{"@type":395,"name":401,"acceptedAnswer":402},"How do I connect Stripe events to my Nuxt users?",{"@type":398,"text":403},"Include your internal user_id in Checkout Session metadata or on the Stripe customer. In your webhook, read that value to update the right user and entitlements.",{"@type":395,"name":405,"acceptedAnswer":406},"How do I handle free trials with subscriptions?",{"@type":398,"text":407},"Create a recurring price and set a trial period on the price or when creating the Checkout Session. Activate access on checkout.session.completed, then track current_period_end.",{"@type":395,"name":409,"acceptedAnswer":410},"How do I build and sell an AI tool online with Nuxt and Stripe?",{"@type":398,"text":411},"Treat AI features as paid entitlements. Use subscriptions for ongoing access or one-time purchases for credit packs, then protect AI routes and pages based on active payments.",{"@type":395,"name":413,"acceptedAnswer":414},"What do I test before going live with Stripe?",{"@type":398,"text":415},"Test one-time and subscription success, 3D Secure, failed payments, renewals, refunds, cancellations, and webhook retries. Finally run a small live charge to verify production.",{},true,[],"\u002Fblog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing","nuxt",[],"Informational",{"title":6,"description":26},{"loc":419},"blog\u002Fnuxt-stripe-payments-for-one-time-and-subscription-billing",[420,427,428,429,430],"stripe","payments","saas","vue",[420,427,428,429,430],"DnIgfWsnRZqV-2tZXDnIUyHlSLfU7afBCwF7TxPsdoo",[434,439],{"title":435,"path":436,"stem":437,"description":438,"children":-1},"Nuxt SEO Best Practices – How to Get Your SaaS Ranked in 2026","\u002Fblog\u002Fnuxt-seo-best-practices-saas","blog\u002Fnuxt-seo-best-practices-saas","Stop building for ghosts. Learn the essential Nuxt SEO best practices to grow your SaaS traffic. A simple guide to metadata, sitemaps, and core web vitals.",{"title":440,"path":441,"stem":442,"description":443,"children":-1},"Open source Nuxt starter alternatives and when to buy","\u002Fblog\u002Fopen-source-nuxt-starter-alternatives-and-when-to-buy","blog\u002Fopen-source-nuxt-starter-alternatives-and-when-to-buy","Build, fork, or buy a Nuxt starter? See concrete trade-offs, real costs, and a simple decision map to ship a SaaS or AI app fast without weeks of glue work.",1786348944743]