Taking Stripe Payments With Next.js 10, TypeScript 4, React 17 and Vercel
Published: Nov 7, 2020
Last updated: Nov 7, 2020
Over my previous posts, I have been exploring some of the neat things about Next.js 10 and using Vercel as a host.
Today we are going to show how you can build out your next store or start financing your own dreams.
We will use a Stripe + Nextjs starter template, upgrade React to version 17, TypeScript to version 4 and deploy the final working application to Vercel via the CLI.
I tell you what, I sure am ready to leave me 9-5 job.
Prerequisites and requirements
Getting started
Thanks to create-next-app
, we already have an example that we can get started with and explore!
Run the following to create a Next.js example app in the folder with-stripe-typescript-app
:
# Create Stripe TypeScript example npx create-next-app --example with-stripe-typescript with-stripe-typescript-app
Change into the newly-created directory.
Optionally, We are going to make some changes to this template for the sake of bringing TypeScript and React things up to version 4 and 17 respectively!
# Move TypeScript from v3 -> v4, React from v16 -> v17 npm install react@latest react-dom@latest # Update types npm install --save-dev typescript@latest @types/react@latest
In my case, this brought TypeScript up to v4.0.5 and React + ReactDOM to v17.0.1. Note that in future, breaking changes could break the app.
You could check for the other packages as well (Stripe, etc.) but I will leave them for the sake of working with what is currently there.
Preparing the app
Before we go too deep into exploration, we need to add in your Stripe API Keys
As for the webhook secret, this comes from the Stripe CLI
After installing the Stripe CLI, run stripe login
and log into your account (this will open up the browser to confirm).
Next, run stripe listen --forward-to localhost:<your-port-likely-3000>/api/webhooks
. This will give you back a webhook secret you can use.
From the root of the project, run cp .env.local.example
to .env.local.example
.
Inside, you'll fine some environment variables that we need to update:
# Stripe keys # https://dashboard.stripe.com/apikeys NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<your-stripe-pk> STRIPE_SECRET_KEY=<your-stripe-sk> STRIPE_PAYMENT_DESCRIPTION='Software development services' # https://stripe.com/docs/webhooks/signatures STRIPE_WEBHOOK_SECRET=<your-webhook-secret-from-stripe-cli>
Exploring the app
To start the app, run npm run dev
in the terminal.
Head to http://localhost:3000 and you'll be faced with the following screen:
Localhost running
This page allows us to run three examples:
- Donation using Stripe Checkout.
- Donation using Stripe Elements.
- Using the Shopping Cart.
Let's explore the code for each page.
Donate with Stripe Checkout
Checking out the first option with Donation with Checkout.
The code for how this page works can be found at pages/donate-with-checkout.tsx
.
import { NextPage } from "next"; import Layout from "../components/Layout"; import CheckoutForm from "../components/CheckoutForm"; const DonatePage: NextPage = () => { return ( <Layout title="Donate with Checkout | Next.js + TypeScript Example"> <div className="page-container"> <h1>Donate with Checkout</h1> <p>Donate to our project 💖</p> <CheckoutForm /> </div> </Layout> ); }; export default DonatePage;
All the future examples can be found at
pages/path/to/route
. I won't be touching deeper on the page entry points moving further.
Within that form, we can see the checkout form in components/CheckoutForm
.
While I won't go too deep into each and every piece of information here, if we look at the code and see the handleSubmit
closure function, we can see where most the important information comes from:
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => { e.preventDefault(); setLoading(true); // Create a Checkout Session. const response = await fetchPostJSON("/api/checkout_sessions", { amount: input.customDonation, }); if (response.statusCode === 500) { console.error(response.message); return; } // Redirect to Checkout. const stripe = await getStripe(); const { error } = await stripe!.redirectToCheckout({ // Make the id field from the Checkout Session creation API response // available to this file, so you can provide it as parameter here // instead of the {{CHECKOUT_SESSION_ID}} placeholder. sessionId: response.id, }); // If `redirectToCheckout` fails due to a browser or network // error, display the localized error message to your customer // using `error.message`. console.warn(error.message); setLoading(false); };
This code re-directs us to the Stripe Checkout where we can pay with some test cards.
The API call code it uses to authorize the checkout session comes from pages/api/checkout_sessions/*.ts
where *
refers to any of the three files that help out here.
If we run through the UI and click on "donate" we can see that in action.
Stripe Checkout
Using a Stripe test card4242 4242 4242 4242
for the card number, any 3 digits for the CVC and any future date for validating the purchase.
If we check our terminal, we will see some neat confirmation logs for both the Nextjs app logs and webhook logs:
# Nextjs App logs ✅ Success: evt_1Hke6eJV8JMnC8XlhOPbQAy2 💵 Charge id: ch_1Hke6dJV8JMnC8XltpI9mM15 ✅ Success: evt_1Hke6eJV8JMnC8Xlm2EtO9Cq 🤷♀️ Unhandled event type: payment_method.attached ✅ Success: evt_1Hke6eJV8JMnC8XlpWp04upc 🤷♀️ Unhandled event type: customer.created ✅ Success: evt_1Hke6fJV8JMnC8Xl29TUZxrT 💰 PaymentIntent status: succeeded ✅ Success: evt_1Hke6fJV8JMnC8XlcTKuetWs 🤷♀️ Unhandled event type: checkout.session.completed # Stripe Webhook Logs 2020-11-07 10:04:59 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:08:46 --> payment_intent.created [evt_id] 2020-11-07 10:08:46 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:12:33 --> charge.succeeded [evt_id] 2020-11-07 10:12:33 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:12:34 --> payment_method.attached [evt_1Hke6eJV8JMnC8Xlm2EtO9Cq] 2020-11-07 10:12:34 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:12:34 --> customer.created [evt_1Hke6eJV8JMnC8XlpWp04upc] 2020-11-07 10:12:34 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:12:34 --> payment_intent.succeeded [evt_1Hke6fJV8JMnC8Xl29TUZxrT] 2020-11-07 10:12:34 <-- [200] POST http://localhost:3000/api/webhooks [evt_id] 2020-11-07 10:12:34 --> checkout.session.completed [evt_1Hke6fJV8JMnC8XlcTKuetWs] 2020-11-07 10:12:34 <-- [200] POST http://localhost:3000/api/webhooks [evt_id]
The UI itself will redirect to /result
where the response JSON body will be shown.
Response on result page
Donate with Stripe Elements
If we return to the home page and select the Donate with Elements
option, we will come to a similar donation page but with the difference that we are now using Stripe Elements from the @stripe/react-stripe-js
package.
This enables you to make purchase attempts directly from within your website and has flexible styling options to keep it within your styling.
Similar to before, it is the handleSubmit
closure function from the components/ElementsForm.tsx
form that will give us the most information on what is happening:
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => { e.preventDefault(); // Abort if form isn't valid if (!e.currentTarget.reportValidity()) return; setPayment({ status: "processing" }); // Create a PaymentIntent with the specified amount. const response = await fetchPostJSON("/api/payment_intents", { amount: input.customDonation, }); setPayment(response); if (response.statusCode === 500) { setPayment({ status: "error" }); setErrorMessage(response.message); return; } // Get a reference to a mounted CardElement. Elements knows how // to find your CardElement because there can only ever be one of // each type of element. const cardElement = elements!.getElement(CardElement); // Use your card Element with other Stripe.js APIs const { error, paymentIntent } = await stripe!.confirmCardPayment( response.client_secret, { payment_method: { card: cardElement!, billing_details: { name: input.cardholderName }, }, } ); if (error) { setPayment({ status: "error" }); setErrorMessage(error.message ?? "An unknown error occured"); } else if (paymentIntent) { setPayment(paymentIntent); } };
The API call code it uses to authorize the checkout session comes from pages/api/payment_intents/*.ts
where *
refers to any of the three files that help out here.
If we fill out the details similar to before with the same test card and click Donate $x
, you will see that the entire payment process happens on the page as opposed to running through the redirects.
Elements form
Elements paid
Exploring the Shopping Cart
As for the final option of Use Shopping Cart, you'll see the example gives us a lovely set of options to add items to a cart, then checkout.
This actually works by using a package called use-shopping-cart
In real-world usage, you can add the products through the Stripe Dashboard.
This example follows a similar route to the basic checkout example. If you add some items to the cart and checkout, you will be redirected to Stripe Checkout to complete to order and follow a similar redirect path.
Shopping cart page
The difference in this cart, however, is that your items will show on the checkout page!
Checkout with shopping cart
See the payments in the dashboard
If you head to the Payments section of your Stripe Dashboard, you will be able to confirm the test payments we made.
Payments in the dashboard
Deploying to Vercel
If you have not already, install Vercel using npm i -g vercel
.
Now, simply run vercel
from the root of the application and follow the prompts.
The above requires you to have a Vercel account setup.
Once this is done, you will have a link to the live website! The battle, however, is not over yet. If you try to run the checkout from here, your Developer Tools console will inform you that the API keys are not set.
We need to now add our keys to Vercel.
Creating the webhook in Stripe
In order to capture webhooks in our production app, we need to create a webhook URL in the Stripe Dashboard. It will look something like https://your-url.your-account.vercel.app/api/webhooks
.
Creating a production webhook
Once created in the dashboard, grab the Signing Secret and get ready to add this to Vercel.
Adding Environment Variables
There will be a link to the settings for your application given back on terminal (something like https://vercel.com/[your-account]/[app-name]/settings
Head to this link and select Environment Variables from the sidebar. From here, we want to add back in our variables using what we had from .env.local
in our project, only replace the STRIPE_WEBHOOK_SECRET
with the actual link we created in the section before.
Once these values have been filled out, run vercel --prod
from the terminal once again from the root directory of the repo to re-deploy the app so that the new environment variables take place.
Head to your URL now, run through one of the flows we described during this post and you are done. Congratulations!
Production Example Payment
Production payment on the Stripe Dashboard
Conclusion
In summary, we have looked today at setting up React 17 and TypeScript 4.x in a Nextjs 10 application and event taken it to production using Vercel to host the live website with the ability to accept payments.
Most of these example API routes and implementations from the app can be re-used in the real-world for your own projects.
Stripe + Vercel = Greatness. Happy hacking!
Resources and further reading
Image credit: Sam Dan Truong
Originally posted on my blog
Dennis O'Keeffe
Melbourne, Australia
1,200+ PEOPLE ALREADY JOINED ❤️️
Get fresh posts + news direct to your inbox.
No spam. We only send you relevant content.
Taking Stripe Payments With Next.js 10, TypeScript 4, React 17 and Vercel
Introduction