DEV Community

Cover image for Accept payments through Stripe in a Next.js app
Avneesh Agarwal
Avneesh Agarwal

Posted on • Edited on • Originally published at blog.avneesh.tech

Accept payments through Stripe in a Next.js app

Introduction

Did you ever want to create an e-commerce platform and earn through it?

GIF

One of the most tricky parts would be accepting payments. So let's see how you can do it 😉

GIF

Setting up

Creating a Next app with TailwindCSS

I am going to use tailwind for the basic stylings needed in the app

npx create-next-app next-stripe-demo -e with-tailwindcss
Enter fullscreen mode Exit fullscreen mode

Cleanup

Delete everything in pages/index.js after the Head till the footer it should look like this

import Head from 'next/head'

export default function Home() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Starting the app

npm run dev # npm
yarn dev # yarn
Enter fullscreen mode Exit fullscreen mode

Installing the required dependencies

npm i stripe @stripe/stripe-js axios
yarn add stripe @stripe/stripe-js axios
Enter fullscreen mode Exit fullscreen mode

Creating a dummy product

I am going to create a MacBook component card as a product, you might already have many products for your app.

import Head from "next/head";
import Image from "next/image";

export default function Home() {
  const items = [
    {
      title: "Apple Macbook Pro",
      description: "Apple M1 Chip with 8‑Core CPU and 8‑Core GPU 256GB Storage",
      image:
        "https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/mbp-spacegray-select-202011_GEO_IN?wid=904&hei=840&fmt=jpeg&qlt=80&.v=1613672874000",
      price: 122900,
    },
  ];

  return (
    <div className="flex flex-col items-center justify-center min-h-screen  bg-green-400">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      {items?.map((item) => (
        <div className="bg-white rounded-2xl h-[500px] w-[400px] p-3 shadow-xl flex flex-col justify-center items-center">
          <Image
            width={300}
            height={300}
            objectFit="contain"
            src={item.image}
            alt={item.title}
          />
          <h2 className="text-center font-semibold">{item.title}</h2>
          <h2 className="text-center">{item.description}</h2>
          <h3>₹{item.price}</h3>
          <button role="link" className="bg-green-400 px-4 py-2 rounded-lg">
            Buy now
          </button>
        </div>
      ))}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Whitelisting the image

As we are using the next Image we will need to whitelist it in next.config.js

So, create a file next.config.js and add this

module.exports = {
  images: {
    domains: ["store.storeimages.cdn-apple.com"],
  },
};
Enter fullscreen mode Exit fullscreen mode

After adding it you will need to restart your server. Kill the old server and run

npm run dev # npm
yarn dev # yarn
Enter fullscreen mode Exit fullscreen mode

This will give a product card like this

image.png

Adding an onClick to the button

   <button
       onClick={createCheckOutSession}
       role="link"
       className="bg-green-400 px-4 py-2 rounded-lg"
   >
     Buy now
   </button>
Enter fullscreen mode Exit fullscreen mode

Creating the createCheckOutSessionfunction

const createCheckOutSession = async () => {};
Enter fullscreen mode Exit fullscreen mode

Getting the credentials

Go to Stripe and sign up for an account and create an application.
After it is created, click on developers in the header
image.png
then click on API keys in the sidebar.

You will see these your keys there.

image.png

Now, create a .env.local file and add your public and secret key there instead

STRIPE_PUBLIC_KEY=public_key
STRIPE_SECRET_KEY=secret_key
Enter fullscreen mode Exit fullscreen mode

We will also add the env key in next.config.js like this

module.exports = {
  images: {
    domains: ["store.storeimages.cdn-apple.com"],
  },
  env: {
    stripe_public_key: process.env.STRIPE_PUBLIC_KEY,
  },
};
Enter fullscreen mode Exit fullscreen mode

You will need to restart your server after you do this. So kill the server and run

npm run dev # npm
yarn dev # yarn
Enter fullscreen mode Exit fullscreen mode

Loading stripe

Add this snippet at the top of index.js.

import { loadStripe } from "@stripe/stripe-js";
const stripePromise = loadStripe(process.env.stripe_public_key);
Enter fullscreen mode Exit fullscreen mode

Creating the backend

Inside pages/api folder create a file named create-checkout-session.js
Add this in the file

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

export default async (req, res) => {
  const { item, email } = req.body;
};
Enter fullscreen mode Exit fullscreen mode

Here, we are getting the item and the email via a post method that we will call on our front end.

GIF

So, let's make a test post request and console log the details.

  const createCheckOutSession = async () => {
    const stripe = await stripePromise;

    const checkoutSession = await axios.post("/api/create-checkout-session", {
      items: items,
      email: "test@gmail.com",
    });
  };
Enter fullscreen mode Exit fullscreen mode

You will need to import Axios like this

import axios from "axios"
Enter fullscreen mode Exit fullscreen mode

This will make a post request to our backend and will pass the item, email along with it. I am hard coding the values here, but you should have an authentication system and you will need to pass the email here.

Console logging the data we get

export default async (req, res) => {
  const { item, email } = req.body;

  console.log(item);
  console.log(email);
};
Enter fullscreen mode Exit fullscreen mode

Now if you click on the button you will be able to see the details in the terminal like this

image.png

Creating the shape for the item needed by Stripe.

There is a particular type of object which Stripe expects to get, this is the object. I have created this in such a way that even if you add multiple items to the cart and make the request it would work. You should use your local currency instead of "inr".

    const transformedItem = {
    description: item.description,
    quantity: 1,
    price_data: {
      currency: "inr",
      unit_amount: item.price * 100,
      product_data: {
        name: item.title,
        image: [item.image],
      },
    },
  };
Enter fullscreen mode Exit fullscreen mode

Creating the Stripe Session in the backend

You will need to create a stripe session object where you need to define some data.

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    shipping_address_collection: {
      allowed_countries: ["IN"],
    },
    line_items: transformedItems,
    mode: "payment",
    success_url: "http://localhost:3000/success",
    cancel_url: "http://localhost:3000/cancel",
    metadata: {
      email,
      images: JSON.stringify(items.map((item) => item.image)),
    },
  });

  res.status(200).json({ id: session.id });
};
Enter fullscreen mode Exit fullscreen mode
  • Allowed countries: You can add as many and whichever allowed countries you want to add.

  • Success URL: In success URL you define where the user will go after the payment is successful.

  • CancelURL: In the cancel URL you define where the user will go if the user clicks the back button. It can be a cancel page or the checkout page as well.

  • Metadata: In metadata, we will add the email and images. You can add more metadata if you want.

This is how the final create-checkout-session.js looks like

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

export default async (req, res) => {
  const { items, email } = req.body;

  console.log(items);

  const transformedItems = items.map((item) => ({
    price_data: {
      currency: "inr",
      product_data: {
        images: [item.image],
        name: item.title,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: 1,
  }));

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    shipping_address_collection: {
      allowed_countries: ["IN"],
    },
    line_items: transformedItems,
    mode: "payment",
    success_url: "http://localhost:3000/success",
    cancel_url: "http://localhost:3000/cancel",
    metadata: {
      email,
      images: JSON.stringify(items.map((item) => item.image)),
    },
  });

  res.status(200).json({ id: session.id });
};
Enter fullscreen mode Exit fullscreen mode

Redirecting the user to the checkout page

Inside the createCheckOutSession function add this below the post request

    const result = await stripe.redirectToCheckout({
      sessionId: checkoutSession.data.id,
    });

    if (result.error) {
      alert(result.error.message);
    }
Enter fullscreen mode Exit fullscreen mode

The final createCheckOutSession should look like this

  const createCheckOutSession = async () => {
    const stripe = await stripePromise;

    const checkoutSession = await axios.post("/api/create-checkout-session", {
      items: items,
      email: "test@gmail.com",
    });

    const result = await stripe.redirectToCheckout({
      sessionId: checkoutSession.data.id,
    });

    if (result.error) {
      alert(result.error.message);
    }
  };
Enter fullscreen mode Exit fullscreen mode

The index.js file should be similar to this

import Head from "next/head";
import Image from "next/image";
import axios from "axios";
import { loadStripe } from "@stripe/stripe-js";
const stripePromise = loadStripe(process.env.stripe_public_key);

export default function Home() {
  const items = [
    {
      title: "Apple Macbook Pro",
      description: "Apple M1 Chip with 8‑Core CPU and 8‑Core GPU 256GB Storage",
      image:
        "https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/mbp-spacegray-select-202011_GEO_IN?wid=904&hei=840&fmt=jpeg&qlt=80&.v=1613672874000",
      price: 122900,
    },
  ];

  const createCheckOutSession = async () => {
    const stripe = await stripePromise;

    const checkoutSession = await axios.post("/api/create-checkout-session", {
      items: items,
      email: "test@gmail.com",
    });

    const result = await stripe.redirectToCheckout({
      sessionId: checkoutSession.data.id,
    });

    if (result.error) {
      alert(result.error.message);
    }
  };

  return (
    <div className="flex flex-col items-center justify-center min-h-screen  bg-green-400">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      {items?.map((item) => (
        <div
          key={item.title}
          className="bg-white rounded-2xl h-[500px] w-[400px] p-3 shadow-xl flex flex-col justify-center items-center"
        >
          <Image
            width={300}
            height={300}
            objectFit="contain"
            src={item.image}
            alt={item.title}
          />
          <h2 className="text-center font-semibold">{item.title}</h2>
          <h2 className="text-center">{item.description}</h2>
          <h3>₹{item.price}</h3>
          <button
            onClick={createCheckOutSession}
            role="link"
            className="bg-green-400 px-4 py-2 rounded-lg"
          >
            Buy now
          </button>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now let's see if it works.
Stripe gives us a test card where you need to enter 4242 everywhere.

https://www.loom.com/embed/7869990b6aa64721945ca2f5076c7839

GIF

If you now go to the payments in the header you can see that the order came in.

Creating the success and the cancel page

Creating the success page
Create success.js in the pages folder
I am creating a very simple page, feel free to customize it as needed.

function success() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen  bg-green-400">
      <h2 className="text-4xl font-semibold">Thanks for shopping with us</h2>
    </div>
  );
}

export default success;
Enter fullscreen mode Exit fullscreen mode

Creating the cancel page
Create cancel.js in the pages folder
Add this snippet to get a simple cancel page.

import Link from "next/link";

function cancel() {
  return (
    <div className="flex flex-col text-4xl font-semibold items-center justify-center min-h-screen  bg-green-400">
      <h2>Looks like you canceled the order.</h2>
      <Link href="/" className=" bg-white bg-opacity-30 px-4 py-2 rounded-2xl">
        Go to home page
      </Link>
    </div>
  );
}

export default cancel;
Enter fullscreen mode Exit fullscreen mode

Looking at the pages in action

https://www.loom.com/share/413deed251e344658fad4028e8d1abad

Customizing the checkout page

If you want to change the colors of the checkout page follow these steps-

  • Click on settings
  • Scroll down and you will find branding under Your business

image.png

  • You can add your brand colors, logo, and icon here. After changing the color click on save and you will be able to see a customized login screen!

image.png

image.png

You have added stripe payment to your app!

Let me know what you want to see next 😉👇🏻

Useful links -

Github repository

NextJS docs

Stripe

All socials

Top comments (0)