Back to insights
Stripe Payments and BillingJanuary 8, 2026

The Revenue Layer: Implementing Stripe Subscriptions in Next.js and MongoDB

KG

Karl Gusta

Instructor & Founder

From Developer to Founder: The Billing Milestone

There is a specific psychological shift that happens when a developer moves from building a project to launching a SaaS. It occurs the moment you add a Buy Now button. Suddenly, your code is not just a collection of logic; it is a value proposition.

Integrating payments is often the most intimidating part of the SaaS journey. You are dealing with real money, tax compliance, and the fear of a user being charged twice or not gaining access after paying. Many developers postpone this step indefinitely, but in the Zero to SaaS methodology, we treat billing as a core pillar of development.

In this lesson, we are going to implement the subscription lifecycle. We will move beyond simple one-time payments and build a recurring revenue system using Stripe Checkout and Webhooks, perfectly synced with your MongoDB user profiles.

The Problem: The Synchronization Nightmare

The biggest technical challenge in SaaS billing is keeping your database in sync with your payment processor. If a user upgrades their plan on Stripe, your database needs to know immediately. If their credit card fails and the subscription is canceled, your app must automatically revoke their access.

Many beginners try to solve this by manually checking the Stripe API every time a user logs in. This is slow, inefficient, and will quickly hit Stripe rate limits. The solution is an event-driven architecture where Stripe tells your app when something happens.

Stripe payment integration checkout page illustration

The Shift: Event-Driven Billing

The shift is moving from a pull model to a push model. Instead of asking Stripe if a user is subscribed, we listen for Stripe Webhooks. Stripe acts as the source of truth for payment status, and our MongoDB database acts as a mirror of that truth for fast, local access.

We will use Stripe Checkout to handle the actual payment UI. This is a massive advantage because it handles SCA (Strong Customer Authentication), Apple Pay, Google Pay, and localized currencies out of the box. Your app never touches a credit card number, which significantly reduces your PCI compliance burden.

Stripe Subscriptions in Next.js 2 dives deeper into why this architectural choice is the safest for solo founders.


Deep Dive: The Billing Workflow

1. The Product Catalog Strategy

Before writing code, you must define your products in the Stripe Dashboard. For a standard SaaS, you typically need two Price IDs: a Monthly and a Yearly tier.

The Workflow:

  • Create a Product in Stripe (e.g., Pro Plan).
  • Create a Price for that product (e.g., $19/month).
  • Store these Price IDs in your .env.local file.
  • Never hardcode these IDs in your components; always use environment variables to make switching between Test and Live modes easier.

2. Creating the Checkout Session

When a user clicks Upgrade, you call a Server Action that creates a Stripe Checkout Session. This session is a temporary URL where the user completes their purchase.

The Logic: You must pass a client_reference_id to the session. This should be the user's MongoDB _id. This is the single most important piece of the puzzle; it is the link that allows you to identify which user just paid when the webhook hits your server later.

typescript
// app/actions/stripeActions.ts "use server" import Stripe from "stripe"; import { getServerSession } from "next-auth"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function createCheckoutSession(priceId: string) { const session = await getServerSession(); if (!session) throw new Error("Unauthorized"); const checkoutSession = await stripe.checkout.sessions.create({ mode: "subscription", payment_method_types: ["card"], line_items: [{ price: priceId, quantity: 1 }], success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`, cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`, client_reference_id: session.user.id, customer_email: session.user.email, }); return { url: checkoutSession.url }; }

3. The Webhook Handler: The Source of Truth

A webhook is a dedicated API route in your Next.js app that Stripe calls whenever a subscription is created, updated, or deleted. This route must be public and must verify the Stripe signature to ensure the request actually came from Stripe.

The Workflow:

  1. Stripe sends a checkout.session.completed event.
  2. Your webhook extracts the client_reference_id (the MongoDB User ID).
  3. Your webhook updates the User document in MongoDB with the stripeCustomerId and sets isSubscribed: true.
  4. Your app is now in sync.

Code snippet of Next.js and Tailwind project

4. The Customer Portal

A professional SaaS must allow users to manage their own billing. You should not build a UI for updating credit cards or canceling plans. Instead, use the Stripe Customer Portal. You create a link, the user is redirected to a Stripe-hosted page to manage their subscription, and they are sent back to your dashboard when they are finished.


Key Benefits and Learning Outcomes

  • Automated Revenue: Your app makes money while you sleep without manual intervention.
  • Professional Trust: Using Stripe Checkout gives your brand instant credibility with users.
  • Data Consistency: Webhooks ensure your database always reflects the actual payment state.
  • Low Maintenance: Stripe handles tax, receipts, and card expirations automatically.

Common Mistakes to Avoid

  1. Ignoring the Trial State: If you offer a free trial, ensure your webhook handles the customer.subscription.updated event so you know when the trial ends and the first payment clears.
  2. Local Webhook Testing: Webhooks cannot reach your localhost. You must use the Stripe CLI to forward events to your local machine during development.
  3. Missing Secret Keys: Forgetting to set the STRIPE_WEBHOOK_SECRET is the number one cause of webhook 500 errors.
  4. Hardcoding Prices: If you hardcode price IDs, you will have a nightmare updating your pricing tiers in the future. Always use a config file or environment variables.

Project roadmap checklist for building SaaS app

Pro Tips and Best Practices

  • The Grace Period: When a subscription is canceled, do not revoke access immediately. Check the period_end date from Stripe and allow the user to keep access until the end of their current billing cycle.
  • Stripe Tax: Enable Stripe Tax in your dashboard. It automatically calculates and collects sales tax based on the user's location, saving you a massive legal headache later.
  • Idempotency: Stripe might send the same webhook twice. Ensure your webhook logic is idempotent (meaning running it twice has the same effect as running it once) to avoid duplicate data entries.
  • Tiered Access: Use a helper function in your Next.js layouts to check the user's plan. This allows you to easily hide or show features based on whether the user is on a Free, Pro, or Enterprise tier.

How This Fits Into the Zero to SaaS Journey

We have built the foundation, secured the gates, and established the database. Now, we have connected the revenue engine. In the Zero to SaaS journey, this is the point of no return. You are no longer just building an app; you are building a commercial product.

The integration you build today is what transforms your technical skills into a business asset. From here, every feature you add increases the value of the subscription you have just enabled.

Real-World Use Case: The 'Premium Template' Library

Imagine a SaaS that provides high-quality landing page templates.

  • Free Tier: Users can browse templates and download one per month.
  • Pro Tier: Users click Subscribe, pay via Stripe Checkout, and the webhook updates their MongoDB profile to plan: "pro".
  • The Result: The download button in the Next.js frontend now checks the user's plan field in MongoDB. If it says Pro, the download starts instantly. If not, they are redirected back to the pricing page.

This logic is simple to implement once the Stripe-to-MongoDB bridge is built.

SaaS app live on web with success banner

Action Plan: What to Build Next

It is time to get paid. Follow these steps to integrate Stripe:

  1. Setup: Create a Stripe account and toggle on Test Mode.
  2. Products: Create one subscription product with a monthly price.
  3. Integrate: Implement the Server Action to redirect a user to Stripe Checkout.
  4. Listen: Install the Stripe CLI and start listening for webhooks on your local machine.
  5. Update: Write the webhook handler logic to update your MongoDB user document when a payment succeeds.

Completing these steps is the difference between a developer and a founder.


Launch Your Revenue Engine Today

The transition from $0 to $1 in revenue is the hardest part of any SaaS journey. By implementing this workflow, you have removed the technical barriers to that first dollar. You now have a production-ready billing system that can scale with your ambitions.

If you want the full Stripe webhook boilerplate and a pre-built pricing table that connects directly to your backend, you can find it in our comprehensive curriculum.

Ready to show the world? Follow the Deploy SaaS on Vercel guide to take your application live and start accepting real payments.

Back to all posts

Keep reading

Related articles

View all posts

Build next

Turn this into a plan.

Use the free tools to validate, price, forecast, and shape the SaaS idea before you build.

Explore free tools