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.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
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.
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.
npm install express stripe
The Express framework will handle routes (URLs on your app), and the Stripe package connects to Stripe’s API.
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.
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.
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>
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>
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.
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.
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"));
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"));
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"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
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).
Assume you have a Replit Node.js + React (or vanilla JS) setup. In your root folder:
Server runs automatically when you hit “Run”. Replit exposes the port via HTTPS URL.
npm install express stripe
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.
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.
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.
/create-checkout-session) and your CLIENT\_URL matches your live Replit front-end URL.
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.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.