💳 Step-by-Step Guide to Building a Full-Stack Stripe Subscription System Using Node.js and React

Table of Contents

If you’re a developer student looking to implement seamless subscription billing in your web app, you’ve come to the right place. In this guide, we’ll walk you through creating a complete subscription system using Node.js, Stripe, and React—from backend APIs to the frontend UI. 💡

By the end of this blog, you’ll be able to:

✅ Create and manage subscription plans on Stripe
✅ Set up a secure backend with webhooks
✅ Build a beautiful subscription UI in React
✅ Handle real-time Stripe checkout flows
✅ Manage subscription success and failure states

Let’s dive into it! 🌊


🧠 What You’ll Learn

  • Why Stripe for subscriptions?
  • Setting up Stripe account and plans
  • Building the Node.js backend (with Express)
  • Configuring Stripe webhooks securely
  • Designing a modern React frontend
  • Connecting frontend to backend
  • Handling success and cancellation flows
  • Deployment & security best practices

🔧 Prerequisites

Before we jump in, make sure you have:

  • Basic knowledge of JavaScript and Node.js
  • Node.js and npm installed on your system
  • A Stripe account (https://stripe.com)
  • React app scaffolded (using Vite or Create React App)
  • A code editor like VSCode

🌟 Why Use Stripe?

Stripe is one of the most popular payment gateways for developers. Here’s why:

  • 🛠️ Developer-friendly SDKs
  • 💼 Built-in support for subscriptions & invoicing
  • 🌎 Global payments support
  • 🔐 PCI-compliant and secure
  • ⚙️ Powerful dashboard for analytics

🚀 Step 1: Set Up Your Stripe Dashboard

  1. Create a Stripe account at stripe.com
  2. Go to Products > Prices, and create recurring prices for your subscription plans:
    • Basic Plan – ₹1,000/month
    • Premium Plan – ₹2,000/month
  3. Copy the Price ID for each plan—this will be used in your backend.

🔐 Don’t forget to grab your Secret Key from the Developers > API Keys section.


🛠️ Step 2: Building the Backend with Node.js + Stripe

We’ll now build a backend server using Express.js that will:

  • Create a Stripe Checkout session
  • Handle webhooks securely
  • Accept subscription details

📁 Backend Project Structure

stripe-backend/
├── .env
├── index.js
├── package.json

📦 Install Dependencies

npm init -y
npm install express cors dotenv stripe body-parser

📄 .env (Store secrets securely)

PORT=5000
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

🧩 Backend Code (index.js)

const express = require("express");
const cors = require("cors");
const dotenv = require("dotenv");
const bodyParser = require("body-parser");

dotenv.config();
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

const app = express();
const PORT = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());
app.use(bodyParser.raw({ type: "application/json" }));

🔗 Create Checkout Session API

app.post("/stripe-checkout-session", async (req, res) => {
  try {
    const { priceId } = req.body;

    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      mode: "subscription",
      line_items: [{ price: priceId, quantity: 1 }],
      success_url: "http://localhost:5173/success",
      cancel_url: "http://localhost:5173/cancel",
      customer_email: "customer@example.com",
      billing_address_collection: "required",
      shipping_address_collection: { allowed_countries: ["IN"] },
      custom_fields: [
        {
          key: "customer_name",
          label: { type: "custom", custom: "Full Name" },
          type: "text",
          optional: false,
        },
      ],
    });

    res.status(200).json({ url: session.url });
  } catch (error) {
    console.error("Error creating session", error);
    res.status(500).json({ error: "Internal server error" });
  }
});

📡 Handling Stripe Webhooks

Webhooks allow your server to react to events such as successful payments or subscription cancellations.

app.post("/webhook", (req, res) => {
  const sig = req.headers["stripe-signature"];
  const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error("Webhook Error:", err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case "checkout.session.completed":
      const session = event.data.object;
      console.log("Subscription successful:", session);
      break;
    case "invoice.payment_failed":
      const failedInvoice = event.data.object;
      console.warn("Payment failed:", failedInvoice);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  res.json({ received: true });
});

▶️ Start the Server

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Run it with:

node index.js

✅ Your backend is ready!


🧑‍🎨 Step 3: Build the React Frontend

Now let’s build a stunning frontend in React with Stripe integration.

📁 Frontend Folder Structure

react-app/
├── src/
│   ├── pages/
│   │   ├── Plans.jsx
│   │   ├── Success.jsx
│   │   └── Cancel.jsx
│   ├── App.jsx
│   └── index.css

📦 Install React Router

npm install react-router-dom

🎨 Plans.jsx

import React from 'react';

const Plans = () => {
  const plans = [
    {
      name: 'Basic',
      price: '₹1,000',
      features: ['1 project', 'Basic support', '24/7 access', 'Basic analytics'],
      priceId: 'price_1RUP0USF61vTyQk0InchPf2z'
    },
    {
      name: 'Premium',
      price: '₹2,000',
      features: ['Unlimited projects', 'Premium support', '24/7 priority access', 'Advanced analytics', 'Custom integrations'],
      priceId: 'price_1RSAIeSF61vTyQk0Ny0Czz08'
    }
  ];

  const handleSubscribe = async (priceId) => {
    try {
      const res = await fetch("http://localhost:5000/stripe-checkout-session", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ priceId })
      });
      const data = await res.json();
      if (data?.url) window.location.href = data.url;
      else alert("Failed to redirect. Please try again.");
    } catch (error) {
      console.error("Subscription error", error);
      alert("An error occurred during checkout.");
    }
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12">
      <div className="max-w-7xl mx-auto text-center mb-12">
        <h2 className="text-4xl font-extrabold text-gray-900">Choose Your Plan</h2>
        <p className="mt-4 text-xl text-gray-500">Select the perfect plan for your needs</p>
      </div>
      <div className="grid sm:grid-cols-2 gap-6 lg:max-w-4xl mx-auto">
        {plans.map((plan) => (
          <div key={plan.name} className="bg-white rounded-2xl shadow-xl p-8 hover:scale-105 transition-transform">
            <h3 className="text-2xl font-semibold">{plan.name}</h3>
            <p className="text-4xl mt-4">{plan.price}<span className="text-base text-gray-500">/month</span></p>
            <ul className="mt-6 text-left">
              {plan.features.map((feature) => (
                <li key={feature} className="flex items-center gap-2 text-gray-700">
                  ✅ {feature}
                </li>
              ))}
            </ul>
            <button onClick={() => handleSubscribe(plan.priceId)} className="mt-6 w-full bg-indigo-600 text-white py-2 rounded-lg hover:bg-indigo-700 transition">
              Subscribe Now
            </button>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Plans;

🛬 Success.jsx & Cancel.jsx

// Success.jsx
export default function Success() {
  return <div className="p-10 text-green-600 text-center text-2xl">🎉 Subscription successful!</div>;
}

// Cancel.jsx
export default function Cancel() {
  return <div className="p-10 text-red-600 text-center text-2xl">❌ Payment cancelled. Try again.</div>;
}

🔀 App.jsx

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Plans from './pages/Plans';
import Success from './pages/Success';
import Cancel from './pages/Cancel';
import './index.css';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Plans />} />
        <Route path="/success" element={<Success />} />
        <Route path="/cancel" element={<Cancel />} />
      </Routes>
    </Router>
  );
}

export default App;

🔒 Security Tips

  • Use HTTPS in production
  • Never expose your STRIPE_SECRET_KEY on the frontend
  • Use env variables on both frontend and backend
  • Validate webhook events with Stripe’s constructEvent

🌍 Ready to Deploy?

Use services like:

  • Render, Railway, or Heroku for backend
  • Vercel or Netlify for React frontend

Make sure to:

  • Point the success_url and cancel_url to your deployed frontend
  • Secure webhook endpoint URLs
  • Monitor usage and invoices in the Stripe dashboard

🧪 Testing Subscriptions

  • Use Stripe test cards (e.g., 4242 4242 4242 4242)
  • Enable test mode in dashboard
  • Simulate different scenarios: success, failure, cancellation

💼 Real-World Use Cases

This setup is ideal for:

  • SaaS platforms
  • Developer tools
  • Membership-based portals
  • E-learning platforms
  • API monetization

DOWNLOAD SOURCE CODE


🏁 Wrapping Up

Building a full-stack subscription system doesn’t have to be a headache. With the power of Stripe and the flexibility of React and Node.js, you can implement a secure, scalable solution in no time.

Whether you’re building the next indie SaaS or a global product, this guide gives you the roadmap to succeed. 🚀

Happy coding, developers! 🧑‍💻💖

Share the Post:
Picture of Web Codder

Web Codder

Vikas Sankhla is a seasoned Full Stack Developer with over 7 years of experience in web development. He is the founder of Web Codder, a platform dedicated to providing comprehensive web development tutorials and resources. Vikas specializes in the MERN stack (MongoDB, Express.js, React.js, Node.js) and has been instrumental in mentoring aspiring developers through his online courses and content. His commitment to simplifying complex web technologies has made him a respected figure in the developer community.

Related Posts