/how-to-build-replit

How to Build a Billing system with Replit

Learn how to build a billing system with Replit using Python. Follow step-by-step tips to create, test, and deploy your own secure payment solution.

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 Billing system with Replit

A simple and reliable way to build a billing system in Replit is to use a small backend (for example, an Express.js app running on Node.js) that integrates with a payment provider like Stripe. You’ll create API endpoints in Replit that the frontend (a simple HTML/React page or a form) can call. Payment keys and sensitive information should be stored inside Replit’s Secrets tab, not hardcoded. The backend will receive requests, communicate securely with Stripe to create checkout sessions, and handle webhooks for payment confirmations. You can do all this within one Replit project.

 

Step 1: Create a new Replit project

 

Start a new Replit project using the “Node.js” template. This gives you a working server.js file and a package.json ready to use.

  • In your Replit dashboard, click "Create Repl" → choose "Node.js".
  • Name it something like billing-system.

 

Step 2: Add required dependencies

 

In the Replit shell at the bottom, install express and Stripe:

 

npm install express stripe

 

These will appear automatically in your package.json once installed.

 

Step 3: Add your Stripe credentials as Secrets

 

On the left sidebar in Replit, click the padlock icon (called Secrets or Environment Variables). Add:

  • Key: STRIPE_SECRET_KEY → Value: your Stripe secret key (from your Stripe dashboard).
  • Key: STRIPE_PUBLISHABLE_KEY → Value: your Stripe publishable key.

Secrets stay hidden and are available via process.env.STRIPE_SECRET_KEY in Node.

 

Step 4: Set up a simple Express backend

 

In your main file (likely index.js or server.js), paste and customize the following:

 

// Import dependencies
const express = require('express')
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
const app = express()
const PORT = process.env.PORT || 3000

// Parse JSON request bodies
app.use(express.json())

// Example product for demo purposes
const YOUR_DOMAIN = 'https://your-replit-username.your-repl-name.repl.co' // replace with your actual Replit URL

// Route to create a checkout session
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: 'Subscription Plan', // Change this as needed
            },
            unit_amount: 1000, // in cents ($10)
          },
          quantity: 1,
        },
      ],
      success_url: `${YOUR_DOMAIN}/success.html`,
      cancel_url: `${YOUR_DOMAIN}/cancel.html`,
    })
    res.json({ url: session.url })
  } catch (err) {
    res.status(500).json({ error: err.message })
  }
})

// Optional: handle Stripe webhooks (advanced)
// Create Stripe webhook endpoint in dashboard → point to /webhook
// Then validate events here for actual payment confirmation

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

 

This code should be inserted inside your main server file. If your Repl has no server.js or index.js, create one in the root folder and paste it there.

 

Step 5: Create a simple HTML page for checkout

 

Create a new file in the root directory called index.html and put:

 

<!DOCTYPE html>
<html>
  <head>
    <title>Billing Example</title>
    <script src="https://js.stripe.com/v3/"></script>
  </head>
  <body>
    <h1>Buy Subscription</h1>
    <button id="checkout">Checkout</button>

    <script>
      const checkoutButton = document.getElementById('checkout');
      checkoutButton.addEventListener('click', async () => {
        const response = await fetch('/create-checkout-session', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' }
        })
        const session = await response.json()
        window.location = session.url // Redirect user to Stripe Checkout
      });
    </script>
  </body>
</html>

 

This frontend calls your backend endpoint, which creates a checkout session, and then redirects the user to Stripe’s secure payment page.

 

Step 6: Run and test in Replit

 

  • Click the “Run” button in Replit. It will start your server.
  • Open the web preview — this is your app’s public URL.
  • Click the checkout button; you should be redirected to Stripe Checkout.

 

Step 7: Handle successful payments (optional but real-world)

 

Stripe will redirect users back to your success.html. You can create this file in the root with a friendly message like:

 

<!DOCTYPE html>
<html>
  <body>
    <h1>Payment Successful!</h1>
    <p>Thank you for your purchase.</p>
  </body>
</html>

 

For more complex billing systems (like subscriptions, invoices, or customer accounts), you can expand the backend to store user data in a hosted DB (like Replit DB, Supabase, or MongoDB Atlas) and handle webhooks with verification.

 

Key Replit-specific advice

 

  • Secrets: Always put API keys in Secrets; never commit them to code.
  • Live URL: Every time your Repl restarts, your domain stays the same — useful for webhooks. But make sure to keep the Repl running or use “Always on” if you have Replit Hacker plan.
  • Persistence: Replit’s filesystem resets on some restarts — use an external DB for transactions or logs.
  • Security: Never trust user input; validate data server-side even for demo billing systems.

 

This setup gives you a real, working billing system using Replit’s free environment, with Stripe taking care of the heavy lifting (secure card handling, compliance, and receipts) while your app stays simple and easy to deploy and maintain inside Replit.

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 Build a Simple Invoice API with Express and Replit Database



import express from "express";
import bodyParser from "body-parser";
import Database from "@replit/database";
import "dotenv/config";

const app = express();
const db = new Database();

app.use(bodyParser.json());

// Automatically calculate invoice totals and persist to Replit DB
app.post("/api/invoices", async (req, res) => {
  const { userId, items } = req.body;
  if (!userId || !Array.isArray(items)) return res.status(400).json({ error: "Invalid data" });

  const subtotal = items.reduce((sum, item) => sum + item.price \* item.qty, 0);
  const taxRate = 0.07;
  const total = +(subtotal \* (1 + taxRate)).toFixed(2);

  const invoice = {
    id: `inv_${Date.now()}`,
    userId,
    items,
    subtotal,
    taxRate,
    total,
    createdAt: new Date().toISOString(),
    status: "unpaid"
  };

  await db.set(invoice.id, invoice);

  res.json(invoice);
});

// Example secure endpoint to fake mark invoices as paid (simulate integrating with Stripe later)
app.post("/api/invoices/:id/pay", async (req, res) => {
  const invoice = await db.get(req.params.id);
  if (!invoice) return res.status(404).json({ error: "Invoice not found" });
  invoice.status = "paid";
  invoice.paidAt = new Date().toISOString();
  await db.set(invoice.id, invoice);
  res.json(invoice);
});

app.listen(3000, () => console.log("Billing API running on Replit"));

How to Securely Handle Stripe Webhooks in Your Replit Billing System



import express from "express";
import crypto from "crypto";
import "dotenv/config";

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

// Verify incoming Stripe webhooks securely on Replit
app.post("/webhook/stripe", async (req, res) => {
  const signature = req.headers["stripe-signature"];
  const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

  let event;
  try {
    const expectedSig = crypto
      .createHmac("sha256", endpointSecret)
      .update(req.body)
      .digest("hex");

    if (expectedSig !== signature) {
      return res.status(400).send("Invalid signature");
    }

    event = JSON.parse(req.body.toString());
  } catch (err) {
    console.error("⚠️  Webhook signature verification failed.", err);
    return res.sendStatus(400);
  }

  // Handle a successful payment event
  if (event.type === "checkout.session.completed") {
    const session = event.data.object;
    console.log(`✅ Payment received for session ${session.id}`);
    // TODO: update your Replit DB invoice as paid
  }

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

app.listen(3001, () => console.log("Stripe webhook listener running on Replit"));

How to Track and Bill API Usage in Replit with Express



import express from "express";
import Database from "@replit/database";
import "dotenv/config";

const app = express();
const db = new Database();

app.use(express.json());

// Middleware to track user operation count (for metered billing)
app.use(async (req, res, next) => {
  const userId = req.headers["x-user-id"];
  if (!userId) return res.status(401).json({ error: "User ID required" });

  const key = `usage_${userId}`;
  const usage = (await db.get(key)) || { requests: 0, lastReset: new Date().toISOString() };
  usage.requests += 1;

  // auto reset usage every 30 days
  const last = new Date(usage.lastReset);
  const now = new Date();
  if (now - last > 1000 _ 60 _ 60 _ 24 _ 30) {
    usage.requests = 1;
    usage.lastReset = now.toISOString();
  }

  await db.set(key, usage);
  req.userId = userId;
  req.usage = usage;
  next();
});

// Endpoint to get current usage and calculate due charges dynamically
app.get("/api/billing/usage", async (req, res) => {
  const { userId, usage } = req;
  const unitPrice = 0.002; // $0.002 per request
  const totalDue = parseFloat((usage.requests \* unitPrice).toFixed(4));

  const invoice = {
    userId,
    requests: usage.requests,
    totalDue,
    lastReset: usage.lastReset,
  };
  res.json(invoice);
});

app.listen(3000, () => console.log("Usage-based billing API running on Replit"));

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 Billing system with Replit

To build a billing system on Replit, use a secure backend (like Node.js with Express), store API keys in Replit Secrets, and integrate a real payment provider such as Stripe. Replit can fully handle the logic, routes, and webhooks you need; the only step you should move outside Replit is storing sensitive data or dealing with heavy workloads (use external DB or Stripe’s Dashboard for that). Never hardcode secrets or depend on local file storage for invoices—Replit instances aren’t persistent that way. Use a small Express server that serves routes for payments and webhook events, set your Stripe secret keys in Replit’s “Secrets” panel, and deploy it. Handle callbacks inside a dedicated webhook route, tested via Replit’s web preview URL.

 

1. Project Structure

 

Create these files in your Replit project (if not already existing):

  • index.js — your main Express server file.
  • .replit and replit.nix — automatically managed by Replit for running Node.js.
  • package.json — define dependencies like express and stripe.

 

npm init -y  
npm install express stripe body-parser

 

2. Setup Environment Variables (Secrets)

 

Go to the padlock icon (Secrets panel) in Replit sidebar and add:

  • STRIPE_SECRET_KEY: your real Stripe secret key
  • STRIPE_WEBHOOK_SECRET: webhook signing secret (from Stripe dashboard)

Your API keys will be stored safely by Replit; they will not appear in version control.

 

3. Server Setup (index.js)

 

This file initializes Express, sets up Stripe, creates a payment route, and listens for webhooks. It’s simple, reliable, and works fine on Replit’s always-on web server.

 

// index.js
import express from "express"
import Stripe from "stripe"
import bodyParser from "body-parser"

const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)  // access the secret in Replit

app.use(bodyParser.json())

// Create a checkout session for payment
app.post("/create-checkout-session", async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      line_items: [
        {
          price_data: {
            currency: "usd",
            product_data: { name: "Monthly Subscription" },
            unit_amount: 1000, // $10.00
          },
          quantity: 1,
        },
      ],
      mode: "subscription",
      success_url: `${req.headers.origin}/success`,
      cancel_url: `${req.headers.origin}/cancel`,
    })
    res.json({ url: session.url })
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
})

// Stripe webhook for events like successful payments
app.post("/webhook", bodyParser.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) {
    console.log("Webhook signature verification failed.", err.message)
    return res.sendStatus(400)
  }

  if (event.type === "checkout.session.completed") {
    const session = event.data.object
    console.log("Payment success:", session.id)
    // here you can update user record in DB or send confirmation email
  }

  res.send()
})

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

 

4. Where to Place This Code

 

  • index.js in the root folder of your Replit project. That’s your app entry file.
  • Secrets in Replit’s built-in Secrets manager, not in code.
  • package.json handles dependencies automatically. When you install packages, Replit updates it for you.

 

5. Running and Testing

 

Click the “Run” button. Replit starts your web server and gives you a preview URL (something like https://your-repl-name.username.repl.co). In Stripe Dashboard, configure this URL plus /webhook as your endpoint.

If you want to test payments without going live, enable Stripe test mode and use their test cards (they always start with 4242…).

 

6. Data Handling & Pitfalls

 

  • Don’t store invoices or user payment info in Replit files; instead, use a hosted database (e.g., Supabase, MongoDB Atlas, or Firebase).
  • Keep your secret keys only in Replit Secrets — any collaborator you invite can access them only if you explicitly share them.
  • Webhook testing tip: Replit’s public URL works fine for receiving real webhook requests. You can re-run your Repl anytime; the URL remains the same, which is perfect for Stripe testing.
  • Don’t rely on Replit filesystem — all changes not committed to Git disappear when the Repl restarts, so persist billing data externally.

 

7. Honest Replit Notes

 

Replit is great for quick deployments, small SaaS tools, or startups validating billing flows. But remember that Replit servers sleep when inactive (unless you’re on Hacker/Pro plan or deploy as a Replit “Deployment”). If your billing system needs guaranteed uptime or webhook reliability, use Replit Deployments or proxy webhooks through a reliable server.

This setup will run perfectly for development, testing, and small production-scale payments. Keep your code modular and secrets isolated — and Replit will handle the rest nicely.

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