/how-to-build-replit

How to Build a Checkout flow with Replit

Learn how to build a smooth, secure checkout flow using Replit. Follow step-by-step guidance to create an efficient payment process for your web app.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Build a Checkout flow with Replit

A simple and real way to build a checkout flow in Replit is to use a Node.js + Express backend with a React or plain HTML frontend, connecting it to a payment service like Stripe. You’ll keep your API keys safe using Replit Secrets, create an endpoint that talks to Stripe, and then use that endpoint on the client side to start and complete a checkout session. The flow is: user clicks a "Checkout" button → client calls your backend endpoint → backend creates a Stripe Checkout Session → Stripe hosts the payment page → Stripe redirects the user back to your site when done.

 

Step 1: Create your Replit Project

 

Start with a new Node.js Repl. This gives you an Express-ready environment. Make sure your repl includes the "index.js" file (that’s your entry point) and that the "Run" button runs node index.js.

 

Step 2: Install Required Packages

 

npm install express stripe

 

The Express framework will handle routes (URLs on your app), and the Stripe package connects to Stripe’s API.

 

Step 3: Set Up Stripe Secret Key in Replit Secrets

 

In Replit’s left sidebar, click the padlock icon labeled Secrets. Add a new secret called STRIPE_SECRET_KEY with your real key from the Stripe dashboard (it usually starts with sk_live_ or sk_test_). Secrets stored there are never exposed publicly — that’s crucial for security.

 

Step 4: Backend Code (index.js)

 

Replace or extend your index.js file with the following. This creates a checkout session and returns its URL back to the client. If you already have an Express app running, just add the /create-checkout-session route.

 

const express = require("express");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.static("public")); // to serve your frontend files
app.use(express.json());

// Create a checkout session when user clicks checkout
app.post("/create-checkout-session", async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      mode: "payment",
      line_items: [
        {
          price_data: {
            currency: "usd",
            product_data: {
              name: "T-shirt",
            },
            unit_amount: 2000, // amount in cents (=$20)
          },
          quantity: 1,
        },
      ],
      success_url: "https://yourreplusername.yourreplname.repl.co/success.html", // where to go after payment
      cancel_url: "https://yourreplusername.yourreplname.repl.co/cancel.html",
    });
    res.json({ url: session.url });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => console.log("Server running on port 3000"));

 

Every time a POST request goes to /create-checkout-session, a new Stripe session is created and returned as a URL. You’ll use this URL to redirect the shopper.

 

Step 5: Frontend Code (public/index.html)

 

Create a new folder named public, then inside it, a file called index.html. This is the page your users will see. The button below sends a request to your backend route to start checkout.

 

<!DOCTYPE html>
<html>
  <head>
    <title>Checkout Example</title>
  </head>
  <body>
    <h1>Buy a T-shirt</h1>
    <button id="checkout-btn">Checkout</button>

    <script>
      document.getElementById("checkout-btn").addEventListener("click", async () => {
        // Send request to backend to create a checkout session
        const res = await fetch("/create-checkout-session", { method: "POST" });
        const data = await res.json();
        // Redirect the user to the Stripe Checkout page
        window.location.href = data.url;
      });
    </script>
  </body>
</html>

 

Step 6: Add Success and Cancel Pages

 

Still inside your public folder, create these two simple files so Stripe can redirect after payment:

 

<!-- success.html -->
<!DOCTYPE html>
<html>
  <body>
    <h2>Payment successful!</h2>
  </body>
</html>
<!-- cancel.html -->
<!DOCTYPE html>
<html>
  <body>
    <h2>Payment was canceled.</h2>
  </body>
</html>

 

Step 7: Run and Test

 

Click the Run button in Replit. If you open your webview (or the little “Open in new tab” icon), you should see your checkout page. Clicking "Checkout" now creates a Stripe session and redirects you to the hosted payment page. Use the Stripe test credit card number 4242 4242 4242 4242 with any valid date and CVC to test it.

 

Step 8: Common Pitfalls in Replit

 

  • Do not hardcode keys: always use Replit Secrets, or your Stripe account can be compromised.
  • Don’t store HTML in the root alongside index.js: Express’s static() call will correctly serve files under a public folder.
  • Serve routes explicitly: Replit’s built-in webserver timing can sometimes reload. Keeping everything in one Node server avoids confusion.
  • Check HTTPS when going live: Replit preview links are HTTPS by default, but make sure your Stripe Dashboard webhook and redirect URLs match exactly.
  • When sharing Repl: If others fork your project, their Repl won’t have your secrets — they’ll have to add their own keys.

 

Step 9: (Optional) Using Stripe Webhooks

 

If you need to confirm payment server-side, you can use Stripe webhooks. In Replit, you can expose your running server using the web URL at the top of your Repl (the one that ends in .repl.co) when you set up a webhook endpoint in Stripe.

 

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["stripe-signature"];
  let event;

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

  // Handle checkout.session.completed event
  if (event.type === "checkout.session.completed") {
    const session = event.data.object;
    // Fulfill the purchase, e.g., update DB or send email
  }

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

 

Again, store STRIPE_WEBHOOK_SECRET in Replit Secrets.

 

This approach is fully real and works entirely in Replit’s environment. It’s simple, reliable, and uses Replit’s hosting + Secrets correctly. You’ll have a working checkout flow that’s secure and production-grade once configured with your live Stripe keys.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us

How to Create a Stripe Checkout Endpoint with Express on Replit



import express from "express";
import bodyParser from "body-parser";
import fetch from "node-fetch";

const app = express();
app.use(bodyParser.json());

app.post("/api/checkout", async (req, res) => {
  try {
    const { cart, email } = req.body;
    const total = cart.reduce((sum, item) => sum + item.price \* item.quantity, 0);

    const session = await fetch("https://api.stripe.com/v1/checkout/sessions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        success\_url: `${process.env.CLIENT_URL}/success`,
        cancel\_url: `${process.env.CLIENT_URL}/cancel`,
        mode: "payment",
        customer\_email: email,
        currency: "usd",
        line\_items: cart.map(item => 
          `price_data[${item.id}][currency]=usd&price_data[${item.id}][product_data][name]=${item.name}&price_data[${item.id}][unit_amount]=${item.price * 100}&quantity=${item.quantity}`
        ).join("&")
      }),
    }).then(r => r.json());

    res.json({ url: session.url });
  } catch (error) {
    console.error("Checkout error:", error);
    res.status(500).json({ error: "Something went wrong" });
  }
});

app.listen(3000, () => console.log("Server running on port 3000"));

How to Handle Stripe Webhooks in Your Checkout Flow



import express from "express";
import crypto from "crypto";
import bodyParser from "body-parser";

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

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

  let event;
  try {
    const payload = req.body.toString("utf-8");
    const hmac = crypto.createHmac("sha256", endpointSecret)
      .update(payload)
      .digest("hex");

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac))) {
      return res.status(400).send("Invalid signature");
    }

    event = JSON.parse(payload);
  } catch (err) {
    console.error("Webhook error:", err);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case "checkout.session.completed":
      const session = event.data.object;
      // Example: update user order in your DB
      console.log(`Payment succeeded for ${session.customer_email}`);
      break;
    case "payment_intent.payment_failed":
      console.log("Payment failed", event.data.object.last_payment_error);
      break;
  }

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

app.listen(3001, () => console.log("Listening for Stripe webhooks on port 3001"));

How to Create and Confirm Orders in Your Checkout Flow with Replit



import express from "express";
import fetch from "node-fetch";
import { v4 as uuidv4 } from "uuid";

const app = express();
app.use(express.json());

const orders = new Map();

app.post("/api/checkout/create-order", async (req, res) => {
  const { cart, email } = req.body;
  const total = cart.reduce((sum, item) => sum + item.price \* item.quantity, 0);
  const orderId = uuidv4();
  orders.set(orderId, { cart, email, total, status: "pending" });
  const ephemToken = process.env.EPHEMERAL\_KEY;
  const paymentIntent = await fetch("https://api.stripe.com/v1/payment\_intents", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      amount: total \* 100,
      currency: "usd",
      metadata: JSON.stringify({ orderId }),
    }),
  }).then(r => r.json());

  res.json({ clientSecret: paymentIntent.client\_secret, orderId });
});

app.post("/api/checkout/confirm", (req, res) => {
  const { orderId, status } = req.body;
  const order = orders.get(orderId);
  if (!order) return res.status(404).json({ error: "Order not found" });
  order.status = status === "succeeded" ? "paid" : "failed";
  orders.set(orderId, order);
  console.log(`Order ${orderId} updated:`, order.status);
  res.json({ success: true });
});

app.listen(3000, () => console.log("Checkout microservice running on port 3000"));

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Best Practices for Building a Checkout flow with Replit

When building a checkout flow on Replit, the best practice is to handle the payment logic (anything related to sensitive info or API keys) on the server side using Node.js (not directly from the React or client code), and use environment Secrets for API keys. Stripe is a great choice since it integrates easily. You’ll build two parts: a simple backend endpoint (for creating a checkout session securely) and a front-end button or form that calls this API. Store your API keys in Replit’s “Secrets” panel, not directly in code. Also, be mindful that Replit deployments don’t keep persistent local files unless you use a real database (like Replit Database, Supabase, or external service); never rely on writing local JSON for orders. Keep your API logic in server.js, use fetch on the client to call your backend route, and test securely using HTTPS (Replit automatically provides this).

 

Project Structure

 

Assume you have a Replit Node.js + React (or vanilla JS) setup. In your root folder:

  • server.js: Handles backend Express app and payment routes.
  • client/: Contains your front-end (React, HTML, CSS).
  • .replit and replit.nix: Auto-managed by Replit.

Server runs automatically when you hit “Run”. Replit exposes the port via HTTPS URL.

 

Step 1: Install Stripe and Express

 

npm install express stripe

 

Step 2: Add Server Code in server.js

 

At your project root, create or open server.js. This file will securely handle checkouts.

import express from "express"   // If you use CommonJS, replace with: const express = require('express')
import Stripe from "stripe"

const app = express()
app.use(express.json()) // Allows parsing JSON body requests

// Initialize Stripe with your secret key from Replit Secrets
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY) 

// Endpoint for creating Checkout Sessions
app.post("/create-checkout-session", async (req, res) => {
  try {
    // Example: products sent from frontend
    const { items } = req.body  

    // Create Stripe checkout session
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      mode: "payment",
      line_items: items.map(item => ({
        price_data: {
          currency: "usd",
          product_data: { name: item.name },
          unit_amount: item.price * 100, // Stripe works in cents
        },
        quantity: item.quantity,
      })),
      success_url: `${process.env.CLIENT_URL}/success`, 
      cancel_url: `${process.env.CLIENT_URL}/cancel`,
    })

    res.json({ url: session.url })
  } catch (err) {
    console.error(err)
    res.status(500).json({ error: "Something went wrong creating checkout session" })
  }
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Server running on port ${port}`))

Where to insert this: This goes directly inside your main server.js. If you already have an Express app, simply add the app.post("/create-checkout-session"...) block inside it. Make sure you have your app.listen at the bottom.

 

Step 3: Add Environment Secrets

 

  • STRIPE_SECRET_KEY: Your Stripe Secret Key (starts with sk_test_...)
  • CLIENT\_URL: Your Replit public front-end URL (from “Webview → Open in new tab”)

You add these in Replit via the left sidebar Tools → Secrets icon (lock symbol). They’ll be securely stored and available as process.env.KEY\_NAME.

 

Step 4: Client Code (React or plain JavaScript)

 

If you use a React front-end inside client/src/CheckoutButton.jsx:

import React from "react"

function CheckoutButton() {
  const handleCheckout = async () => {
    // Example cart data
    const items = [
      { name: "Cool T-shirt", price: 20, quantity: 1 },
      { name: "Mug", price: 10, quantity: 2 }
    ]

    // Call your backend
    const response = await fetch("/create-checkout-session", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ items }),
    })

    const data = await response.json()

    // Redirect to Stripe Checkout
    window.location.href = data.url
  }

  return (
    <button onClick={handleCheckout}>
      Checkout
    </button>
  )
}

export default CheckoutButton

Where to insert this: Place this button inside your front-end component (like App.js), or even in index.html if not using React. Ensure your React dev server proxies requests to Express if you run both together, or run only the Express app on Replit that serves both static files and API.

 

Step 5: Handle Deployment and HTTPS

 

  • Replit automatically provides HTTPS; Stripe requires it for checkout.
  • You don’t need manual SSL setup — just use your Replit public URL.
  • If your checkout redirects fail due to mixed content or CORS, make sure you use relative paths (like /create-checkout-session) and your CLIENT\_URL matches your live Replit front-end URL.

 

Practical Tips and Pitfalls in Replit

 

  • Secrets Safety: Never hardcode Stripe secret keys in JS files — always store them in Replit Secrets.
  • Persistence: Local file writes don’t persist after deployment; save orders in a DB if needed (e.g., Replit DB or external).
  • Collaborators: Avoid sharing Replit with untrusted users unless you trust them fully — secrets are not visible to collaborators, which is good, but they can modify your checkout logic.
  • Testing: Stripe test mode lets you use fake cards like 4242 4242 4242 4242.
  • Logs: Use Replit’s “Shell” or “Console” to debug server output. Logs update live.

 

With this setup, you’ve got a fully working Stripe checkout flow running securely on Replit: front-end calls server → server creates session → Stripe handles payments → redirects back to success/cancel pages. This is production-safe if you move keys to live mode and use real HTTPS Replit URLs.

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022