Building Seamless Payment Interfaces with Stripe and FL0
Whether it be an e-commerce or a SaaS application, payment gateways are a central component of our projects. ๐ณ
In this guide, we will explore how to simplify these integrations, specifically focusing on Stripe Checkout for online payment processing.
Stripe's developer-friendly API ensures secure and efficient transactions while cutting down on our development time.
Just for example, we have taken the case of a SaaS applications payment page.
We would be using NodeJs
for the backend and Postgres as our database. On the frontend we are using ReactJs
with vite
.
Later we would go ahead and effortlessly host our project on FL0. โฌ๏ธ
So, let's start with a pinch of humor:
Overview
๐งโ๐ป In this tutorial, we will create a simple demo application where a user could sign up, select their plan, and checkout with their credit card.
For this we would need to create 2 seperate repositories, one for our backend
and another one for frontend
.
Folder Structure
๐๏ธ Here's how both of our folder structures would look like, just for reference:
Now, let's get started.
Step 1: Setting Up the Backend
For the sake of efficiency, in this tutorial, we'll leverage the "fl0zone/blog-express-pg-sequelize" template.
Then we would remove any files or folders not important for our project. ๐งโ๐ป
For a more comprehensive understanding of the tutorial, you may want to refer to this blog post:
https://blog.fl0.com/building-a-software-startup-on-fl0-part-1-f0624174e738
Our template encapsulates a basic Node.js
application and a dockerized PostgreSQL
database.
Here is the corresponding docker-compose.yaml
file for our setup ๐ณ:
version: "3"
services:
app:
build:
context: .
target: development
env_file: .env
volumes:
- ./src:/usr/src/app/src
ports:
- 8081:80
depends_on:
- db
db:
image: postgres:14
restart: always
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: admin
POSTGRES_DB: my-startup-db
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- 5432:5432
volumes:
postgres-data:
Now we would go ahead and install some essential packages ๐ฆ
npm install bcrypt cookie-parser cors jsonwebtoken pg-hstore stripe
Now, we would need to get our Stripe API keys ๐. For this we would need to create a new account on Stripe.
Here we would be using Test Mode
for demo.
Here are the list of environment variables we would need for this project.
.env.example
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
POSTGRES_DB_URI=
secretKey=
CLIENT_URL=
Step 2: Creating Database Models
Let's begin by setting up our database now. ๐
Since we're utilizing the Sequelize ORM, we'll need to create a model for our user data.
Here's the code for our model ๐
models/userModel.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"user",
{
email: {
type: DataTypes.STRING,
unique: true,
isEmail: true, //checks for email format
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
tier: {
type: DataTypes.STRING,
allowNull: true,
},
},
{ timestamps: true }
);
return User;
};
Step 2: Setting up the Routes
Now, let's go ahead and create our routes
POST /login
- Helps to log in user and store the session
POST /signup
- Helps create a new account
POST /create-checkout-session
- Generates and Returns the stripe checkout page link
These 3 routes are separated into 2 files as follows:
routes/userRoutes.js
const express = require("express");
const userController = require("../controllers/userController");
const { signup, login } = userController;
const userAuth = require("../middleware/userAuth");
const router = express.Router();
router.post("/signup", userAuth.saveUser, signup);
router.post("/login", login);
module.exports = router;
routes/stripeRoute.js
const express = require("express");
const { updatePlan } = require("../controllers/stripeController");
const router = express.Router();
router.post("/create-checkout-session", updatePlan);
module.exports = router;
Step 3: Setting Up User Profile
๐งโ๐ป For setting up the user profile, first we will define a middleware to check if the email address of a new user already exists in the database during signup.
middleware/userAuth.js
//importing modules
const express = require("express");
const db = require("../models");
const User = db.users;
const saveUser = async (req, res, next) => {
console.log("here");
try {
const checkEmail = await User.findOne({
where: {
email: req.body.email,
},
});
if (checkEmail) {
return res.json(409).send("Authentication failed");
}
next();
} catch (error) {
console.log(error);
}
};
module.exports = {
saveUser,
};
Then we would go ahead and define our login and signup functions ๐
controllers/userController.js
const bcrypt = require("bcrypt");
const db = require("../models");
const jwt = require("jsonwebtoken");
const User = db.users;
const signup = async (req, res) => {
try {
const { email, password } = req.body;
console.log(email);
const data = {
email,
password: await bcrypt.hash(password, 10),
};
//saving the user
const user = await User.create(data);
if (user) {
let token = jwt.sign({ id: user.id }, process.env.secretKey, {
expiresIn: 1 * 24 * 60 * 60 * 1000,
});
res.cookie("jwt", token, { maxAge: 1 * 24 * 60 * 60, httpOnly: true });
console.log("user", JSON.stringify(user, null, 2));
console.log(token);
return res.status(201).send(user);
} else {
return res.status(409).send("Details are not correct");
}
} catch (error) {
console.log(error);
}
};
// Login Authentication
const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({
where: {
email: email,
},
});
if (user) {
const isSame = await bcrypt.compare(password, user.password);
if (isSame) {
let token = jwt.sign({ id: user.id }, process.env.secretKey, {
expiresIn: 1 * 24 * 60 * 60 * 1000,
});
res.cookie("jwt", token, { maxAge: 1 * 24 * 60 * 60, httpOnly: true });
//send user data
return res.status(201).send(user);
} else {
return res.status(401).send("Authentication failed");
}
} else {
return res.status(401).send("Authentication failed");
}
} catch (error) {
console.log(error);
}
};
module.exports = {
signup,
login,
};
Step 4: Setting Up Stripe Checkout
This is where we will integrate Stripe Checkout
into our application.
We will use the Stripe API
to manage payments and handle user subscriptions.
The following code creates a new Stripe checkout session. ๐ณ
We will provide it with the payment method type, the product data, and the quantity.
We also need to specify the URLs where the user will be redirected upon a successful payment or if they cancel the transaction.
And, the server will respond back with the URL for the Stripe Session if everything is fine. โ
controllers/stripeController.js
const db = require("../models");
const Stripe = require("stripe");
const User = db.users;
require("dotenv").config();
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
const updatePlan = async (req, res) => {
try {
const { email, product } = req.body;
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: product.name,
},
unit_amount: product.price * 100,
},
quantity: product.quantity,
},
],
mode: "payment",
success_url: `${process.env.CLIENT_URL}/success`,
cancel_url: `${process.env.CLIENT_URL}/`,
});
//find a user by their email
const user = await User.findOne({
where: {
email: email,
},
});
if (user) {
await user.update({ tier: product.name });
return res.send({ url: session.url });
} else {
return res.status(401).send("User not found");
}
} catch (error) {
console.log(error);
}
};
module.exports = {
updatePlan,
};
At last, we would need to add all our routes to our entry point, which is server.js
server.js
const cors = require("cors");
const express = require("express");
require("dotenv").config();
const cookieParser = require("cookie-parser");
const db = require("./models");
const userRoutes = require("./routes/userRoutes");
const PORT = process.env.PORT || 8080;
const stripeRoute = require("./routes/stripeRoute");
const app = express();
// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(cors());
// Routes
app.use("/api/v1/users", userRoutes);
app.use("/api/v1/stripe", stripeRoute);
app.listen(PORT, () => {
console.log("Server started at port 8080");
try {
db.sequelize.sync({ force: true }).then(() => {
console.log("db has been re sync");
});
} catch (error) {}
});
And we are done with the backend โ
Now let's go ahead and try to deploy it on FL0
. ๐ผ
Step 5: Deploying with FL0
๐ For deploying our project to FL0 we will start with pushing our repo to a new GitHub repository first.
This is the link to our repository for reference: https://github.com/dalefl0/stripe-fl0-backend
Now we would head on to app.fl0.dev to start deploying.
Here we would need to create a new project, let's name it
stripe-fl0
for example.Now we would need to create a new Postgres instance. With Fl0, this takes less than 30 seconds! โณ
After we have our database all set up, we would need to go ahead and deploy our backend in the same project.
After the backend is deployed we would need to import our database connection string as shown above โ๏ธ
๐ Now we have our backend up and running.
Time for the UI โจ
Step 6: Setting up the Frontend
For setting up the frontend we would get started with the template-react-vite. โก๏ธ
This includes everything we need to get our React-Vite
project up and running.
Now we would go ahead and install a few packages.
npm install @heroicons/react axios react-router-dom
npm install postcss tailwindcss autoprefixer --save-dev
Step 7: Setting up the Frontend
To build our UI components quickly we would take help of the Pricing Section Component and Sign-in and Registration Component from tailwind UI.
For the sake of brevity, we will only look at the important functions of the frontend.
The complete project could be found at: https://github.com/dalefl0/stripe-fl0-frontend
Now, we would need to add a function to handle stripe checkouts
src/components/PricingPlans.jsx
...
const handleCheckout = (product) => {
axios
.post(
`https://stripe-fl0-backend-dev.fl0.io/api/v1/stripe/create-checkout-session`,
{
email,
product,
}
)
.then((res) => {
if (res.data.url) {
setTier(product.name);
localStorage.setItem("tier", product.name);
window.location.href = res.data.url;
}
})
.catch((err) => navigate("/cancel"));
};
...
This function calls our backend's /create-checkout-session
route, receives a link, and redirects the user to the checkout page. ๐
Apart from this, we need to also connect our signup
and login
pages to respective routes and store the user data in localstorage
.
Step 8: Deploying the Frontend
For frontend we would need to again create a new repository and deploy it in the same project in a similar manner.
We would then need to add the VITE_APP_API_BASE_URL
environment variable to the frontend deployment which should be set to the URL of our backend.
We would also need to set the CLIENT_URL
environment variable in the backend to the hosted URL of the frontend.
Once done, our FL0 project would look like this ๐
Now, let's go ahead and try our application using this live demo link: https://stripe-fl0-frontend-q8oo-dev.fl0.io/
Wrapping up
Thanks for sticking by till the end!
In this tutorial, we learnt how to build payment pages by integrating Stripe Checkout
easily into our full-stack applications. ๐
We also did blazingly-fast deployments of our project using FL0.
To get started with building your own applications with payment capabilities, head on to fl0.com ๐
Subscribe to my newsletter
Read articles from Dale Brett directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by