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.
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:
- How to set up our Stripe account and our Next.js app to collect payments
- Create the products we want to sell in the Stripe dashboard
- 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
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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
.
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.
import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
Next, create an async handler
function and export it as default.
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.
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.
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.
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!
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.
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.
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.
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.
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.
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
.
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.
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.
const redirectToCheckout = async () => { // ... const stripe = await getStripe(); await stripe.redirectToCheckout({ sessionId: id }); };
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.
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.
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.
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.
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.
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.
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.
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 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.
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
.
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.
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.
const Success = () => { // ... useEffect(() => { if (data) { shootFireworks(); clearCart(); } }, [data]); return ( <div>{/* Your UI here */}</div> ); }
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!
Guest Authors
Write for the AlterClass blog to empower the developer community and grow your brand.
Join usStay up to date
Subscribe to the newsletter to stay up to date with tutorials, courses and much more!