How To COLLECT PAYMENTS With STRIPE + NEXT.Js (Step-By-Step Tutorial)

How To COLLECT PAYMENTS With STRIPE + NEXT.Js (Step-By-Step Tutorial)

How to COLLECT PAYMENTS with STRIPE + NEXT.js (Step-by-Step Tutorial)

Today, we gonna learn how to collect payments from our e-commerce website using Stripe.

More precisely, we gonna look at how to use Stripe Checkout to easily and securely accept payments from our 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.

Watch the video on Youtube or keep reading.

Table of content

What are we building?

Processing payments is an essential part of any e-commerce website. Stripe is a toolkit and an API used by millions of businesses to accepts payments, manage customers, handle recurring subscriptions, and more.

It is very popular amongst startups because it is developer-friendly. And as you'll see, it only takes a few steps to collect payments from your React or Next.js applications using the Stripe API and toolkit.

So, in this article, we gonna use our existing Next.js website and focus on the Stripe integration from the front-end side to the back-end.

However, I'm not going to show you how to build the Next.js e-commerce website from scratch, but you can find the source code on Github. So check this out!

My Plant Shop - Next.js app powered by Stripe

If you'd like to learn more about Next.js, check out my FREE course here.

So, in this article, we gonna 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!

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.

nom 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 you 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 in the sidebar and click API keys.

Stripe API keys

As we 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 here we are using Stripe in test mode, meaning that everything we will do is for demonstration only. It can be used to test our application and our payment flow end-to-end to make sure 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_...

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.

Stripe - 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.

Finally, click \"save and add more\" to add the 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're going to use this ID 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

Load Stripe in your Next.js app

We are all set! So go back to your 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 already loaded Stripe. In that 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.

export default getStripe;

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.

So 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.

This 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 of 405 status code to the client that initiated that 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).end('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({ statusCode: 500, message: err.message });
  }
}

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

Here, we set the mode to \"payment\", we enable \"card\" as the only payment method (check out the Stripe documentation for more payment methods), we pass all the line items the user wants to buy, and finally, we set the success URL and the cancel URL.

// /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({ statusCode: 500, message: err.message });
  }
}

The success_url will be used by Stripe 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.

And that's it for this API endpoint.

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

All right! So we're done with our API endpoints.

Let's keep going with our user interface.

Redirect the user to the Stripe checkout page

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

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

This function is called when the user clicks on a button from this page to pay for its order.

So inside that 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.

And 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 () => {
  // Create Stripe checkout
  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 () => {
  ...

  // Redirect to checkout
    const stripe = await getStripe();
    await stripe.redirectToCheckout({ sessionId: id });
};

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

So 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 a 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.

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.

Stripe - Create webook

Then click \"Add endpoint\" to actually create this endpoint.

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 as default an async handler function 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).end('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).send(`Webhook Error: ${err.message}`);
      return;
    }
  } else {
    res.setHeader('Allow', 'POST');
    res.status(405).end('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 {
    ...
  }
}

Create the success page

For the last step of this article, 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 a success.js file, 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.

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

Thank you!

All right, guys, that's it for today.

I hope you enjoyed this article (or video). If you have any questions, please let me know in the comments. And don't forget to like my video on Youtube and subscribe to support the AlterClass channel.

Thank you so much!