Stripe Checkout With Next.js - The Complete Guide
Tags: javascript, stripe, nextjs, node
Published: 2021-09-22 05:44:58

## 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](https://stripe.com/docs/payments/checkout)  with [Next.js](https://nextjs.org/) in this article.

## Index 
- [Introduction](#introduction)
- [Demo](#demo)
- [Let's Build](#lets-build)
  - [Installation](#installation)
  - [Cleaning](#cleaning)
  - [Making UI](#making-ui)
  - [Creating the Backend](#creating-the-backend)
  - [Redirecting to Stripe Checkout Page](#redirecting-to-stripe-checkout-page)
  - [Displaying Success and Cancel Message](#displaying-success-and-cancel-message)
- [Files](#files)
- [Conclusion](#conclusion)


## Introduction

Let's see the brief introduction of [Stripe](https://stripe.com) and [Stripe Checkout](https://stripe.com/docs/payments/checkout).

**[Stripe](https://stripe.com):** [Stripe](https://stripe.com) is an online payment processing and credit card processing platform for businesses.

**[Stripe Checkout](https://stripe.com/docs/payments/checkout):** [Stripe Checkout](https://stripe.com/docs/payments/checkout) is prebuilt, hosted payment page optimized for conversion.

## Demo
<div style="position: relative; padding-bottom: 56.25%; height: 0;"><iframe src="https://www.loom.com/embed/1014873d64e84c4c9c9aa15624d980fe" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="width: 100%; height: 100%; aspect-ratio: 16 / 9;"></iframe></div>

**Live Preview:**  [https://stripe-checkout-next-js-demo.vercel.app/](https://stripe-checkout-next-js-demo.vercel.app/) 

**Github:**  [https://github.com/projectashik/stripe-checkout-next-js-demo](https://github.com/projectashik/stripe-checkout-next-js-demo) 

## Let's Build

### Installation

As mentioned in the title, we'll be using [Next.js](https://nextjs.org/) for building this.

For [Next.js](https://nextjs.org/) to work, we need to have [`Node.js`](https://nodejs.org/en/) and `npm` installed.

So, first, install [Node.js](https://nodejs.org/en/) if you haven't yet.
You can follow the following guides to install [Node.js](https://nodejs.org/en/).
- [For Windows](https://phoenixnap.com/kb/install-node-js-npm-on-windows) 
- [For MacOS](https://www.newline.co/@Adele/how-to-install-nodejs-and-npm-on-macos--22782681)
- [For Linux (Ubuntu)](https://linuxize.com/post/how-to-install-node-js-on-ubuntu-18.04/)

**Optional:**
If you want to use `yarn`, install it using `npm`.
```bash
npm install --global yarn
```

Now, create a new Next.js app.

```bash
npx create-next-app stripe-checkout

yarn create next-app stripe-checkout

```

I have used [tailwindcss](https://tailwindcss.com) for styling the components. So, let's install [tailwindcss](https://tailwindcss.com)  too.

```bash
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](https://code.visualstudio.com/) . 
```bash
# 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.
```css
@tailwind base;
@tailwind utilities;
@tailwind components;

```

**Short Method: **
If you want to use the short process. 
Just run the following command.
```bash
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.
```bash
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.
```js
// 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`.
```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.
```js
// 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.
```js
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`.

```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`
```html
...
<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](https://cdn.hashnode.com/res/hashnode/image/upload/v1632237309037/QsRWabFdX.png)

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.

```js
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.
```html
...
<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`.
```html
...
<input
  type='number'
  className='p-2'
  onChange={onInputChange}
  value={item.quantity}
/>
...
```

Now, let's create `onInputChange` function.
```js
...
//
const onInputChange = (e) => {
    changeQuantity(parseInt(e.target.value));
};
...
```

![Demo of working item plus and minus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2hsykfydjgl90aiisgmt.gif)

### 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.
```env
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1632242917669/GM7jG9Tni.png)

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.

```js
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.

```js
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.

```js
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](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-payment_method_types) to know more payment methods. 

- `success_url`: In  [success_url](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-success_url), you define where the user will go after the payment is successful.

- `cancel_url`: In the  [cancel_url](https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-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](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-metadata), we will add images of the product, if you want you can add other options too.

For other options,  [click here](https://stripe.com/docs/api/checkout/sessions/object)  to know.

Now, the final `create-stripe-session.js` file should look like this.
```js
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.

```js
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.
```js
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`.
```js
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}`

```html
<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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2hbmydv4f7nmhhnaziq5.gif)
 

### 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1632246312155/QlXJDAk-O.gif)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3kr3djhoxu479x12skxq.gif)
 

All done.

## Files
At last, your `pages/index.js` file should look like below:
```jsx
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.

```js
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.

### Important Links
- [Stripe Docs](https://stripe.com/docs/)
- [Github](https://github.com/projectashik/stripe-checkout-next-js-demo)
- [Preview](https://stripe-checkout-next-js-demo.vercel.app/)

### Credits
- [Fireship Stripe Course](https://fireship.io/courses/stripe-js/) 
- @[Avneesh Agarwal](@avneesh0612) Next.js Payment Article -[https://blog.avneesh.tech/payments-in-next](https://blog.avneesh.tech/payments-in-next)

**Connect with me:**
-  [Twitter](https://twitter.com/ChapagainAshik) 
-  [Github](https://github.com/projectashik)
-  [Polywork](https://www.polywork.com/projectashik)