Create your course for free with zero transaction fee →
Back to tutorials

Create an eCommerce website with Next.js and Stripe

Nov 30, 2021 · 17 min read

Create an eCommerce website with Next.js and Stripe

Overview

Today, we are going to learn how to collect payments from an e-commerce website using Stripe.

More precisely, we are going to look at how to use Stripe Checkout to quickly and securely accept payments from an e-commerce website built with the Next.js framework.

And as you'll see, it will only take a few minutes of your time to sell your products online and, more importantly, get paid! So without further ado, let's dive into it.

You can either watch the video version of this tutorial on Youtube or keep reading.

Loading...

What are we building?

In this tutorial, we are going to use an existing Next.js website and focus on the Stripe integration from the front-end side to the back-end. At the end of this tutorial, you should have an eCommerce website up and running you could use to sell products online and receive payments in your Stripe account.

My Plant Shop - Next.js app powered by Stripe

Note that I'm not going to show you how to build the Next.js e-commerce website from scratch. However, you can grab the source code of the final project on Github, so you can have a base structure and follow along with thus tutorial.

So, once you have created your Next.js project, either from scratch, or by stealing my project on Github, run your Next.js development server.

npm run dev

To resume, in this tutorial, we are going to cover:

  1. How to set up our Stripe account and our Next.js app to collect payments
  2. Create the products we want to sell in the Stripe dashboard
  3. Learn how to create a checkout session from our Next.js API and redirect the user to the Stripe Checkout page from our user interface so we can collect the payment details such as the user email address and the card's details
  4. Learn how to handle Stripe events using webhooks and our Next.js API.

🚀 Let's go!

Tasks

Set up your Stripe account + Next.js app

Before we get started, make sure to install the Stripe and the @stripe/stripe-js libraries into your Next.js project.

npm install --save stripe @stripe/stripe-js

Then you need to create an account on stripe.com. But don't worry, it's entirely free. You don't have to provide your credit cards or anything. You just need to click the sign-in button at the top, and you should be good to go.

Stripe's website

Once you are registered, you should be redirected to your Stripe dashboard. So the first thing we're going to need here is our Stripe API keys to query the Stripe API. So click on the developers link from the sidebar and click API keys.

Stripe API keys

As you can see here, we have two API keys.

The first one is the publishable key that you need to identify your account with Stripe.

The second one is the secret key. So be careful with this one. You should keep it confidential and don't publish it on your Github account, for example.

Also, note that we are using Stripe in test mode here, meaning that everything we will do is for demonstration only. It is for testing our application and our payment flow end-to-end to ensure everything is working fine before deploying to production.

By the way, when you turn on the live mode in Stripe, you will get two new API keys. So make sure to use the right keys for the right environment.

All right, with that being said, copy your keys and go back to your code editor.

Inside your Next.js project, create a new file called .env.local and create the following environment variables:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_SECRET_KEY=sk_test_...

Don't forget to restart your development server after saving your .env.local file to load your new environment variables.

Tasks

Create your products on the Stripe dashboard

So before going further with our Next.js application, go back to your Stripe dashboard, as we'll create the products we want to sell.

From here, click on products, an add product to create a new product.

Then, type in the name of your first product, upload an image for your product and set the price and the currency.

Stripe - Create a new product

Finally, click Save and add more to add a second product.

And repeat those steps for every product you'd like to sell.

When you are done creating all your products, copy each product's API ID. We'll use those IDs from within our application and pass it to Stripe with our API requests to tell Stripe which products the user wants to buy.

Stripe - Product's API ID
Tasks

Load Stripe in your Next.js app

Now that we are all setting up our Stripe account and the products we'd like to sell, we can go back to our code editor.

The first thing we're going to need is to load Stripe into our Next.js application. So inside a get-stripe.js file, load the loading wrapper loadStripe from the stripe-js library.

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

Then, create a variable to store the Stripe instance we are about to retrieve, and create a function named getStripe for doing so.

get-stripe.js
let stripePromise = null; const getStripe = () => { // ... };

Inside this function, make sure that we don't have loaded Stripe already. In this case, retrieve a Stripe instance by calling loadStripe and pass in your Stripe publishable key using the environment variable we created earlier. And then, return the Stripe instance from that function.

get-stripe.js
const getStripe = () => { if (!stripePromise) { stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); } return stripePromise; };

Finally, don't forget to export as default the getStripe function.

get-stripe.js
export default getStripe;
Tasks
Build a Full-Stack App with Next.js, Supabase & Prisma

Create a Next.js API endpoint to create a Stripe checkout session

All right! Now, before using the getStripe function from within our Next.js application, we will create the API endpoints we need to create a Stripe checkout session and retrieve the data from a checkout session using its session ID.

So start by creating a new folder named api under the pages folder. And then, within this folder, create another folder called checkout_sessions and create a file named index.js.

Inside this file, we will create the API endpoint we need to create a Stripe checkout session.

What is great about Next.js is that we don't have to create and set up our own Node.js server to create those API endpoints. Instead, we can do everything inside the same project, and Next.js would create and serve those API endpoints.

So, start by importing the Stripe module from stripe and then instantiate a new Stripe instance using your secret key from the STRIPE_SECRET_KEY environment variable.

pages/api/checkout_sessions/index.js
import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Next, create an async handler function and export it as default.

pages/api/checkout_sessions/index.js
export default async function handler(req, res) { // ... }

This handler function accepts two arguments, the HTTP request, and the HTTP response.

It is the only function that we need to create an API endpoint with Next.js. So within this function, make sure the request that we're receiving is an HTTP POST request. Otherwise, return a 405 status code to the client that initiated the request.

pages/api/checkout_sessions/index.js
export default async function handler(req, res) { if (req.method === 'POST') { // ... } else { res.setHeader('Allow', 'POST'); res.status(405).json({ message: 'Method not allowed' }); } }

Then, if we get a POST request, we will handle everything inside a try-catch block. Finally, we return a 500 status code to the client if we catch an error.

pages/api/checkout_sessions/index.js
if (req.method === 'POST') { try { // ... } catch (err) { res.status(500).json({ message: err.message }); } }

Otherwise, we create our checkout session using Stripe and pass all the session's options inside the create function.

Here, we set the mode to payment, we enable card as the only payment method, we pass all the line items the user wants to buy, and finally, we set the success URL and the cancel URL.

Check out the Stripe documentation for more payment methods.

pages/api/checkout_sessions/index.js
if (req.method === 'POST') { try { const session = await stripe.checkout.sessions.create({ mode: 'payment', payment_method_types: ['card'], line_items: req?.body?.items ?? [], success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cart`, }); res.status(200).json(session); } catch (err) { res.status(500).json({ message: err.message }); } }

Stripe will use the success_url to redirect the user once his payment has been successful. Here we use /success for this URL and pass the current checkout session ID as a query parameter.

If the user cancels his payment from this Stripe checkout session, we redirect him to his shopping cart page, /cart.

By the way, don't forget to return the newly created sessions to the client.

That's it for this API endpoint!

Tasks

Create a Next.js API endpoint to retrieve a checkout session

Now let's create a second API endpoint to retrieve a checkout session using its session ID.

So create a new file inside the checkout_sessions/ folder and call it [id].js.

Once again, load Stripe inside that file and use your Stripe secret key to create a new instance.

pages/api/checkout_sessions/[id].js
import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Export as default an async handler function and retrieve the id from the query parameter of the request.

pages/api/checkout_sessions/[id].js
export default async function handler(req, res) { const id = req.query.id; }

Use a try-catch block, and if something goes wrong, return a 500 status code to the client.

pages/api/checkout_sessions/[id].js
export default async function handler(req, res) { const id = req.query.id; try { ... } catch (err) { res.status(500).json({ statusCode: 500, message: err.message }); } }

Then, check the value of the id to make sure that it starts with cs_. Otherwise, throw an error.

But if the id is valid, retrieve the checkout sessions using Stripe by passing in the sessions ID and return it to the client.

pages/api/checkout_sessions/[id].js
export default async function handler(req, res) { const id = req.query.id; try { if (!id.startsWith('cs_')) { throw Error('Incorrect CheckoutSession ID.'); } const checkout_session = await stripe.checkout.sessions.retrieve(id); res.status(200).json(checkout_session); } catch (err) { res.status(500).json({ statusCode: 500, message: err.message }); } }

We're done with the API endpoints. Let's keep going with the user interface.

Tasks

Redirect the user to the Stripe checkout page

So now, inside our shopping cart page, we're going to implement an async function called redirectToCheckout.

pages/cart.js
const redirectToCheckout = async () => { // ... };

We'll call this function when the user clicks on a button from this page to pay for his order.

So inside the redirectToCheckout function, start by creating the Stripe checkout sessions using axios to perform a POST request to the /api/checkout_sessions API endpoint we just created.

You can install axios by running npm i axios.

Once we get the response from the server, we can retrieve the id of the newly created checkout session.

Don't forget to pass the line items to the body of the request. Below, I'm iterating over the items inside the user's shopping cart, and for each item, I'm just passing its id and quantity`.

Note that the key for the ID is actually named price and NOT id.

pages/cart.js
const redirectToCheckout = async () => { const { data: { id }, } = await axios.post('/api/checkout_sessions', { items: Object.entries(cartDetails).map(([_, { id, quantity }]) => ({ price: id, quantity, })), }); };

So when we have created this checkout session successfully, we can redirect the user to the corresponding checkout page.

pages/cart.js
const redirectToCheckout = async () => { // ... const stripe = await getStripe(); await stripe.redirectToCheckout({ sessionId: id }); };
Stripe - Checkout session page
Tasks

Create a webhook + Next.js API endpoint to handle Stripe events

Now that we're able to create a checkout session and accept payments using the Stripe checkout page, we still need to implement one more thing.

Indeed as all the payments are handled by Stripe outside of our application, we need to implement a webhook to listen to a Stripe event to know when Stripe has successfully processed the payment.

For that, we need to go back to our Stripe dashboard and create a webhook endpoint.

So from within your dashboard, click on the developers link and then webbooks. From here, click on Add endpoint, enter the URL of your application, and add /api/webhook, which is the Next.js API endpoint we are about to create just after that.

Stripe doesn't support localhost as a valid endpoint URL. It only accepts HTTPS endpoints. So, to test your webhook integration, you can use a tool like ngrok to get a public URL to your localhost server.

Finally, select the event we want to listen to and choose checkout.session.completed, which is the event that Stripe will send to the endpoint URL once a session has been completed successfully. In other words, when the user has successfully paid for its order.

Then, click Add endpoint to create this endpoint.

Stripe - Create webook

From here, copy your webhook signing secret, go back to your application, and create a new environment variable called STRIPE_WEBHOOK_SECRET inside the .env.local file, and pass the value you just copied.

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_...

Now, create a new folder under the api/ folder and call it webhook.

Inside this folder, create a new file named index.js that we will use to implement our webhook API endpoint.

Inside this file, import the Stripe module from stripe and the buffer method from the micro npm package. You can install this package with npm install micro. We are going to use this package/method to retrieve the raw body from the request.

pages/api/webhook/index.js
import Stripe from 'stripe'; import { buffer } from 'micro'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Then, export a config object with the following key/value to tell Next.js not to parse the body of the request because we need the raw data of that body to verify the webhook event signature.

Why is it important? Because we need to make sure the webhook event was actually sent by Stripe and not by a malicious third party.

pages/api/webhook/index.js
export const config = { api: { bodyParser: false, }, };

Next, as usual, export an async handler function as default and check that we received a POST request. Otherwise, return of 405 status code.

pages/api/webhook/index.js
export default async function handler(req, res) { if (req.method === 'POST') { ... } else { res.setHeader('Allow', 'POST'); res.status(405).json({ message: 'Method not allowed' }); } }

Then, create a new variable named event to store the webhook event data, and use a try-catch block to catch any errors that could occur.

pages/api/webhook/index.js
export default async function handler(req, res) { if (req.method === 'POST') { let event; try { ... } catch (err) { console.log(`❌ Error message: ${err.message}`); res.status(400).json({ message: `Webhook Error: ${err.message}` }); return; } } else { res.setHeader('Allow', 'POST'); res.status(405).json({ message: 'Method not allowed' }); } }

Next, retrieve the Stripe event by verifying its signature using the raw body of the request and your webhook secret key.

pages/api/webhook/index.js
export default async function handler(req, res) { if (req.method === 'POST') { let event; try { const rawBody = await buffer(req); const signature = req.headers['stripe-signature']; event = stripe.webhooks.constructEvent( rawBody.toString(), signature, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { // ... } } else { // ... } }

Once everything has been successfully processed, we could add our business logic.

In our example, we just log a message to the console but feel free to add any business logic you need here, such as sending an email to the customer.

And don't forget to acknowledge receipt of the event.

pages/api/webhook/index.js
export default async function handler(req, res) { if (req.method === 'POST') { let event; try { ... } catch (err) { ... } // Successfully constructed event console.log('✅ Success:', event.id); // Handle event type (add business logic here) if (event.type === 'checkout.session.completed') { console.log(`💰 Payment received!`); } else { console.warn(`🤷‍♀️ Unhandled event type: ${event.type}`); } // Return a response to acknowledge receipt of the event. res.json({ received: true }); } else { // ... } }
Tasks

Create the success page

For the last step of this tutorial, we will create the success page that Stripe will use to redirect the user back to our application when he has successfully paid for his order.

So inside pages/success.js, create a new React component called Success and export it as default.

pages/success.js
const Success = () => { // ... } export default Success;

Then, if you remember, we included the checkout session's id inside the query parameter of the success URL. So, use the useRouter hook from next/router to retrieve this id.

pages/success.js
const Success = () => { const { query: { session_id }, } = useRouter(); }

Once we've got this id, we could perform a GET request to /api/checkout_sessions/${session_id} using the useSWR hook from the swr package.

Here, you could use axios or the Fetch API if you prefer.

pages/success.js
const Success = () => { const { query: { session_id }, } = useRouter(); const { data, error } = useSWR( () => `/api/checkout_sessions/${session_id}`, fetcher ); }

Once we have that, we can create a side effect using the useEffect hook from React to shoot some fireworks onto the screen and clear the shopping cart.

And finally, return the UI of this page.

pages/success.js
const Success = () => { // ... useEffect(() => { if (data) { shootFireworks(); clearCart(); } }, [data]); return ( <div>{/* Your UI here */}</div> ); }
Confirmation page

Thank you!

All right, guys, that's it for today. You should now have a Next.js application powered by Stripe to run your e-commerce business successfully.

If you liked this tutorial, follow me on Twitter and subscribe to my YouTube channel.

And if you've got any questions, please ask them on Twitter!

Thank you so much!

Share

Guest Authors

Write for the AlterClass blog to empower the developer community and grow your brand.

Join us

Stay up to date

Subscribe to the newsletter to stay up to date with tutorials, courses and much more!