Stripe Checkout With Next.js - The Complete Guide

Stripe Checkout With Next.js - The Complete Guide

Learn how to use Stripe Checkout with Next.js. Step by step.

Hello world

My name is Ashik Chapagain.

  • A full-stack web developer
  • Content Creator
  • College student
  • Nepalese

I'll walk you through the entire process of using Stripe Checkout with Next.js in this article.

Index

Introduction

Let's see the brief introduction of Stripe and Stripe Checkout.

Stripe: Stripe is an online payment processing and credit card processing platform for businesses.

Stripe Checkout: Stripe Checkout is prebuilt, hosted payment page optimized for conversion.

Demo

Live Preview: https://stripe-checkout-next-js-demo.vercel.app/

Github: https://github.com/projectashik/stripe-checkout-next-js-demo

Let's Build

Installation

As mentioned in the title, we'll be using Next.js for building this.

For Next.js to work, we need to have Node.js and npm installed.

So, first, install Node.js if you haven't yet. You can follow the following guides to install Node.js.

Optional: If you want to use yarn, install it using npm.

npm install --global yarn

Now, create a new Next.js app.

npx create-next-app stripe-checkout

yarn create next-app stripe-checkout

I have used tailwindcss for styling the components. So, let's install tailwindcss too.

yarn add --dev tailwindcss@latest postcss@latest autoprefixer@latest

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

Then run the following command to create tailwindcss config file and postcss config file.

npx tailwindcss init -p

Now open the project in your favorite text editor or IDE (Integrated Development Environment).

For me, it's Visual Studio Code .

# Run the following command to open the project in VS Code.
code .

Now, open tailwind.config.js and update purge option and add mode: 'jit'.

// tailwind.config.js

module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  mode: 'jit',
  ...
};

Then, open styles/globals.css and remove all the content and add the following content.

@tailwind base;
@tailwind utilities;
@tailwind components;

Short Method: If you want to use the short process. Just run the following command.

npx create-next-app -e with-tailwindcss stripe-checkout
yarn create next-app -e with-tailwindcss stripe-checkout

Now, start the dev server and start coding.

yarn dev
npm run dev

Cleaning

Remove the main and footer tags from pages/index.js.

If you want to edit the title and description in the <Head> tag. Then you can.

// pages/index.js
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Stripe Checkout with Next.js</title>
        <meta name='description' content='Complete Step By Step Tutorial for integrating Stripe Checkout with Next.js' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
    </div>
  );
}

And remove all the styles except for .container in styles/Home.module.css.

/* styles/Home.module.css */
.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

Making UI

First, we'll build an index page in which we'll have one item that users can purchase.

Let's add details of an item to an object.

// pages/index.js

const [item, setItem] = useState({
    name: 'Apple AirPods',
    description: 'Latest Apple AirPods.',
    image:
      'https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1400&q=80',
    quantity: 0,
    price: 999,
  });

Don't forget to import the useState hook.

import { useState } from 'react';

Since we're using Unsplash for images, so we need to configure images.unsplash.com under the images section in next.config.js.

module.exports = {
  reactStrictMode: true,
  images: {
    domains: ['images.unsplash.com'],
  },
};

Now, let's display the following details in the UI and add buttons to increase and decrease the quantity users want to purchase.

In, pages/index.js

...
<main>
  <div className='shadow-lg border rounded p-2 '>
    <Image src={item.image} width={300} height={150} alt={item.name} />
    <h2 className='text-2xl'>$ {item.price}</h2>
    <h3 className='text-xl'>{item.name}</h3>
    <p className='text-gray-500'>{item.description}</p>
    <p className='text-sm text-gray-600 mt-1'>Quantity:</p>
    <div className='border rounded'>
      <button
        className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
      >
        -
      </button>
      <input
        type='number'
        className='p-2'
        defaultValue={item.quantity}
      />
      <button
        className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
      >
        +
      </button>
    </div>
    <p>Total: ${item.quantity * item.price}</p>
    <button
      disabled={item.quantity === 0}
      className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
    >
      Buy
    </button>
  </div>
</main>
...

Now your page should look like this.

UI demo

Now, let's make the button to increase and decrease the quantity working.

Now, we'll create the onQuantityPlus and onQuantityMinus functions to increase and decrease the item quantity on button clicks.

const changeQuantity = (value: number) => {
  // Don't allow the quantity less than 0, if the quantity is greater than value entered by user then the user entered quantity is used, else 0
  setItem({ ...item, quantity: Math.max(0, value) });
};

const onQuantityPlus = () => {
  changeQuantity(item.quantity + 1);
};

const onQuantityMinus = () => {
  changeQuantity(item.quantity - 1);
};

Now, add this function in the onClick event of the respective buttons.

...
<button
  onClick={onQuantityMinus}
  className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
>
  -
</button>
...
<button
  onClick={onQuantityPlus}
  className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
>
  +
</button>
...

Now, let's add the onChange event on the input field to update the value of the item quantity and also don't forget to change defaultValue to value.

...
<input
  type='number'
  className='p-2'
  onChange={onInputChange}
  value={item.quantity}
/>
...

Now, let's create onInputChange function.

...
//
const onInputChange = (e) => {
    changeQuantity(parseInt(e.target.value));
};
...

Demo of working item plus and minus

Creating the Backend

First, we'll start by installing the stripe library.

yarn add stripe
npm install stripe

Now, let's add the required environment variables. Create a new file .env.local in the root directory with the following data.

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=YOUR_STRIPE_PUBLISHABLE_KEY
STRIPE_SECRET_KEY=YOUR_STRIPE_SECRET_KEY

You can get these credentials from Dashboard -> Developers -> API Keys.

Getting Stripe Credentials

Now, we need to build an API to get the session id that is required for redirecting the user to the checkout page.

Create a new file in api/create-stripe-session.js. And add the following.

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

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

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

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. You should use your local currency instead of "usd" if you want.

const transformedItem = {
    price_data: {
      currency: 'usd',
      product_data: {
        images: [item.image],
        name: item.name,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: item.quantity,
  };

Creating 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'],
  line_items: [transformedItem],
  mode: 'payment',
  success_url: redirectURL + '?status=success',
  cancel_url: redirectURL + '?status=cancel',
  metadata: {
    images: item.image,
  },
});

res.json({ id: session.id });
  • payment_method_type: In this, we add the payment methods to pay the price of the product. Click here to know more payment methods.

  • success_url: In success_url, you define where the user will go after the payment is successful.

  • cancel_url: 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 images of the product, if you want you can add other options too.

For other options, click here to know.

Now, the final create-stripe-session.js file should look like this.

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

async function CreateStripeSession(req, res) {
  const { item } = req.body;

  const redirectURL =
    process.env.NODE_ENV === 'development'
      ? 'http://localhost:3000'
      : 'https://stripe-checkout-next-js-demo.vercel.app';

  const transformedItem = {
    price_data: {
      currency: 'usd',
      product_data: {
        images: [item.image],
        name: item.name,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: item.quantity,
  };

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [transformedItem],
    mode: 'payment',
    success_url: redirectURL + '?status=success',
    cancel_url: redirectURL + '?status=cancel',
    metadata: {
      images: item.image,
    },
  });

  res.json({ id: session.id });
}

export default CreateStripeSession;

Now, our backend is ready, now we have to send a POST request to API to get the session.

Redirecting to Stripe Checkout Page

For redirecting to Stripe Checkout Page. We have to install the following libraries.

yarn add @stripe/stripe-js axios
npm install @stripe/stripe-js axios

First, we'll first create a stripePromise variable.

const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(publishableKey);

Now, we'll create createCheckoutSession function to get the Stripe Session for the checkout.

const createCheckOutSession = async () => {
  const stripe = await stripePromise;
  const checkoutSession = await axios.post('/api/create-stripe-session', {
    item: item,
  });
  const result = await stripe.redirectToCheckout({
    sessionId: checkoutSession.data.id,
  });
  if (result.error) {
    alert(result.error.message);
  }
};

Don't forget to import loadStripe and axios.

import { loadStripe } from '@stripe/stripe-js';
import axios from 'axios';

Now, we have to call this function while the user clicks the Buy button. And onClick={createCheckoutSession}

<button
  disabled={item.quantity === 0}
  onClick={createCheckOutSession}
  className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
>
  Buy
</button>

Now, let's test the checkout.

Since we have updated next.config.js and added the .env.local file, so restart the dev server.

yarn dev
npm run dev

Now, the result should look like this.

Credit Card Payment Demo

Displaying Success and Cancel Message

If you guys remember, then we have used ?status= query parameter in success_url and cancel_url.

// pages/api/create-stripe-session.js
...
const session = await stripe.checkout.sessions.create({
    ...
    success_url: redirectURL + '?status=success',
    cancel_url: redirectURL + '?status=cancel',
    ...
  });

...

So, with this query parameter, we'll know, whether the payment is successful or canceled. And show the respective message.

First, let's fetch the status.

// pages/index.js

import { useRouter } from 'next/router';

...
const router = useRouter();
const { status } = router.query;

Now, add the following below the starting main tag, to display the message.

// pages/index.js

{status && status === 'success' && (
  <div className='bg-green-100 text-green-700 p-2 rounded border mb-2 border-green-700'>
    Payment Successful
  </div>
)}
{status && status === 'cancel' && (
  <div className='bg-red-100 text-red-700 p-2 rounded border mb-2 border-red-700'>
    Payment Unsuccessful
  </div>
)}

Now, the result should be like this.

Alert Message Demo

Our app took some time to assign a stripe session. So, during that time, let's display Processing... instead of Buy inside the button as shown in the demo.

For that, Create a new state named loading with default value false.

const [loading, setLoading] = useState(false);

Then, update loading value to true, while creating Stripe Session. and again update loading value to false when the stripe session is created.

const createCheckOutSession = async () => {
  setLoading(true);
  ...
  setLoading(false);
};

And now, update the Buy Button.

<button
  disabled={item.quantity === 0 || loading}
  onClick={createCheckOutSession}
  className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
>
  {loading ? 'Processing...' : 'Buy'}
</button>

Here, we're disabling the button while loading and displaying Processing... while loading.

Now, the result should be following:

Button Processing Demo

All done.

Files

At last, your pages/index.js file should look like below:

import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import axios from 'axios';
import { useRouter } from 'next/router';

export default function Home() {
  const router = useRouter();
  const { status } = router.query;

  const [loading, setLoading] = useState(false);

  const [item, setItem] = useState({
    name: 'Apple AirPods',
    description: 'Latest Apple AirPods.',
    image:
      'https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1400&q=80',
    quantity: 0,
    price: 999,
  });

  const changeQuantity = (value) => {
    // Don't allow the quantity less than 0, if the quantity is greater than value entered by user then the user entered quantity is used, else 0
    setItem({ ...item, quantity: Math.max(0, value) });
  };

  const onInputChange = (e) => {
    changeQuantity(parseInt(e.target.value));
  };

  const onQuantityPlus = () => {
    changeQuantity(item.quantity + 1);
  };

  const onQuantityMinus = () => {
    changeQuantity(item.quantity - 1);
  };

  const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
  const stripePromise = loadStripe(publishableKey);
  const createCheckOutSession = async () => {
    setLoading(true);
    const stripe = await stripePromise;
    const checkoutSession = await axios.post('/api/create-stripe-session', {
      item: item,
    });
    const result = await stripe.redirectToCheckout({
      sessionId: checkoutSession.data.id,
    });
    if (result.error) {
      alert(result.error.message);
    }
    setLoading(false);
  };
  return (
    <div className={styles.container}>
      <Head>
        <title>Stripe Checkout with Next.js</title>
        <meta
          name='description'
          content='Complete Step By Step Tutorial for integrating Stripe Checkout with Next.js'
        />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <main>
        {status && status === 'success' && (
          <div className='bg-green-100 text-green-700 p-2 rounded border mb-2 border-green-700'>
            Payment Successful
          </div>
        )}
        {status && status === 'cancel' && (
          <div className='bg-red-100 text-red-700 p-2 rounded border mb-2 border-red-700'>
            Payment Unsuccessful
          </div>
        )}
        <div className='shadow-lg border rounded p-2 '>
          <Image src={item.image} width={300} height={150} alt={item.name} />
          <h2 className='text-2xl'>$ {item.price}</h2>
          <h3 className='text-xl'>{item.name}</h3>
          <p className='text-gray-500'>{item.description}</p>
          <p className='text-sm text-gray-600 mt-1'>Quantity:</p>
          <div className='border rounded'>
            <button
              onClick={onQuantityMinus}
              className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
            >
              -
            </button>
            <input
              type='number'
              className='p-2'
              onChange={onInputChange}
              value={item.quantity}
            />
            <button
              onClick={onQuantityPlus}
              className='bg-blue-500 py-2 px-4 text-white rounded hover:bg-blue-600'
            >
              +
            </button>
          </div>
          <p>Total: ${item.quantity * item.price}</p>
          <button
            disabled={item.quantity === 0 || loading}
            onClick={createCheckOutSession}
            className='bg-blue-500 hover:bg-blue-600 text-white block w-full py-2 rounded mt-2 disabled:cursor-not-allowed disabled:bg-blue-100'
          >
            {loading ? 'Processing...' : 'Buy'}
          </button>
        </div>
      </main>
    </div>
  );
}

And pages/api/create-stripe-sessoin.js should look like this.

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

async function CreateStripeSession(req, res) {
  const { item } = req.body;

  const redirectURL =
    process.env.NODE_ENV === 'development'
      ? 'http://localhost:3000'
      : 'https://stripe-checkout-next-js-demo.vercel.app';

  const transformedItem = {
    price_data: {
      currency: 'usd',
      product_data: {
        images: [item.image],
        name: item.name,
      },
      unit_amount: item.price * 100,
    },
    description: item.description,
    quantity: item.quantity,
  };

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [transformedItem],
    mode: 'payment',
    success_url: redirectURL + '?status=success',
    cancel_url: redirectURL + '?status=cancel',
    metadata: {
      images: item.image,
    },
  });

  res.json({ id: session.id });
}

export default CreateStripeSession;

Conclusion

So, we have came to end. Hope you like this article. Please consider giving feedback on my writing.

Credits

Connect with me: