DEV Community

Cover image for Building Seamless Payment Interfaces with Stripe and FL0 πŸ’³
Dale Brett for FL0

Posted on • Updated on

Building Seamless Payment Interfaces with Stripe and FL0 πŸ’³

TLDR;

In this tutorial, we'll explore how to seamlessly integrate Stripe payment gateway into our full-stack applications, and effortlessly host them on FL0. πŸš€

Introduction

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:

Comic Strip - Online Shopping

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.

User Journey Diagram

For this we would need to create 2 seperate repositories, one for our backend and another one for frontend.

High Level Overview

Folder Structure

πŸ—‚οΈ Here's how both of our folder structures would look like, just for reference:

Folder Structure

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:
Enter fullscreen mode Exit fullscreen mode

Now we would go ahead and install some essential packages πŸ“¦

npm install bcrypt cookie-parser cors jsonwebtoken pg-hstore stripe
Enter fullscreen mode Exit fullscreen mode

Installing Packages

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.

https://i.imgur.com/pP5qFIr.gif

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=
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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,
};

Enter fullscreen mode Exit fullscreen mode

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,
};

Enter fullscreen mode Exit fullscreen mode

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,
};
Enter fullscreen mode Exit fullscreen mode

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) {}
});

Enter fullscreen mode Exit fullscreen mode

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! ⏳

Creating a postgres instance

  • After we have our database all set up, we would need to go ahead and deploy our backend in the same project.

Deploying backend

  • 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
Enter fullscreen mode Exit fullscreen mode

Installing packages

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"));
  };

  ...
Enter fullscreen mode Exit fullscreen mode

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 πŸ‘‡

FL0 project dashboard

Now, let's go ahead and try our application using this live demo link: https://stripe-fl0-frontend-q8oo-dev.fl0.io/

Live Demo

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 πŸš€

Building your own applications with stripe

Top comments (0)