Achieve NextJS Mastery: Build a Sales Page with Stripe and Airtable

Eric AllamEric Allam

Achieve NextJS Mastery: Build a Sales Page with Stripe and Airtable

TL;DR

In this tutorial, you'll learn how to build a sales landing page:

  • Build an entire sales-page with NextJS.
  • Make payments via Stripe.
  • Save their details to an Airtable database.

Price

Your background job management for NextJS

Trigger.dev is an open-source library that enables you to create and monitor long-running jobs for your app with NextJS, Remix, Astro, and so many more!

If you can spend 10 seconds giving us a star, I would be super grateful 💖 https://github.com/triggerdotdev/trigger.dev

GiveStar

Let's set it up 🔥

Here, I'll walk you through creating the user interface for the course landing page.

Create a new TypeScript Next.js project by running the code snippet below.


_10
npx create-next-app course-page

Install the React Icons package to enable us to use different kinds of icons within the application.


_10
npm install react-icons --save

The application is divided into two pages: the Home page, representing the course landing page, and the Success page, which is displayed to the user after making a payment.

Home page 🏠

The Home page is divided into five sections - the navigation bar, header, features, purchase, and footer sections.

Scroll

Update the index.tsx file, as done below. The placeholders represent each section of the landing page.


_18
import { Inter } from "next/font/google";
_18
const inter = Inter({ subsets: ["latin"] });
_18
_18
export default function Home() {
_18
return (
_18
<main className={` ${inter.className}`}>
_18
{/* --- Navigation bar --- */}
_18
<p>Hello world</p>
_18
{/* --- Header --- */}
_18
_18
{/* --- Features Section --- */}
_18
_18
{/* --- Purchase Now Section--- */}
_18
_18
{/* --- Footer Section --- */}
_18
</main>
_18
);
_18
}

Replace the Navigation bar placeholder with the code snippet below.


_10
<nav className="sticky top-0 z-20 flex w-full items-center justify-between border-b-[1px] border-b-gray-200 bg-white p-4 md:h-[12vh] md:p-8">
_10
<h2 className="text-2xl font-bold text-purple-600">TechGrow</h2>
_10
<button className="rounded-2xl bg-purple-600 px-5 py-3 text-white hover:bg-purple-800">
_10
Get Started
_10
</button>
_10
</nav>

Copy the code snippet below into the Header section. You can get the image from its GitHub repository.


_17
<header className="flex min-h-[88vh] w-full flex-col items-center justify-between px-4 py-12 md:flex-row md:px-8">
_17
<div className="mb-8 w-full md:mb-0 md:w-[60%] md:pr-6">
_17
<h2 className="mb-4 text-5xl font-extrabold">
_17
Future-Proof Your Career with Top Digital Skills!
_17
</h2>
_17
<p className="mb-4 opacity-60">
_17
Unlock your full potential of a future-proof career through the power of
_17
top digital skills with our all-in-one growth package.
_17
</p>
_17
<button className="w-[200px] rounded-2xl bg-purple-600 px-5 py-3 text-lg font-semibold text-white hover:bg-purple-800">
_17
Get Started
_17
</button>
_17
</div>
_17
<div className="w-full md:w-[40%]">
_17
<Image src={headerImage} alt="Man smiling" className="rounded-lg" />
_17
</div>
_17
</header>

The Features Section displays some of the reasons why a customer should purchase the course.


_40
<section className="min-h-[88vh] w-full bg-purple-50 px-4 py-14 md:px-8 ">
_40
<h2 className="mb-4 text-center text-3xl font-extrabold">Why Choose Us?</h2>
_40
<p className="text-center opacity-50">
_40
Unlock your full potential of a future-proof career
_40
</p>
_40
<p className="mb-14 text-center opacity-50">
_40
that surpasses your expectation.
_40
</p>
_40
<div className="flex w-full flex-col items-center justify-between md:flex-row md:space-x-6">
_40
<div className="mb-6 w-full rounded-xl bg-white px-5 py-8 hover:border-[1px] hover:border-purple-600 hover:shadow-md md:mb-0 md:w-1/3">
_40
<div className="mb-2 max-w-max rounded-full bg-purple-50 p-4">
_40
<FaChalkboardTeacher className="text-2xl text-purple-800" />
_40
</div>
_40
<p className="mb-2 text-lg font-bold">Expert instructors</p>
_40
<p className="text-sm opacity-50">
_40
Learn from industry experts, gaining unique insights which cannot be
_40
found elsewhere.
_40
</p>
_40
</div>
_40
<div className="mb-6 w-full rounded-xl bg-white px-5 py-8 hover:border-[1px] hover:border-purple-600 hover:shadow-md md:mb-0 md:w-1/3">
_40
<div className="mb-2 max-w-max rounded-full bg-purple-50 p-4">
_40
<IoDocumentTextSharp className="text-2xl text-purple-800" />
_40
</div>
_40
<p className="mb-2 text-lg font-bold">Hands-On Projects</p>
_40
<p className="text-sm opacity-50">
_40
Learn practical, real-world digital skills through relevant projects and
_40
interactive sessions.
_40
</p>
_40
</div>
_40
<div className="mb-6 w-full rounded-xl bg-white px-5 py-8 hover:border-[1px] hover:border-purple-600 hover:shadow-md md:mb-0 md:w-1/3">
_40
<div className="mb-2 max-w-max rounded-full bg-purple-50 p-4">
_40
<BsFillClockFill className="text-2xl text-purple-800" />
_40
</div>
_40
<p className="mb-2 text-lg font-bold">Lifetime Access</p>
_40
<p className="text-sm opacity-50">
_40
Unlimited lifetime access for continuous learning and personal growth.
_40
</p>
_40
</div>
_40
</div>
_40
</section>

Copy the code snippet below into the Purchase Now Section placeholder.


_41
<div className="flex min-h-[70vh] w-full flex-col items-center justify-between bg-purple-700 px-4 py-14 md:flex-row md:px-12">
_41
<div className="mb-8 w-full md:mb-0 md:w-[50%] md:pr-6">
_41
<h2 className="mb-4 text-5xl font-extrabold text-purple-50">
_41
Start learning and grow your skills today!{" "}
_41
</h2>
_41
<p className="mb-4 text-purple-300">
_41
Unlock your full potential of a future-proof career through the power of
_41
top digital skills with our all-in-one growth package.
_41
</p>
_41
<div className="mb-6">
_41
<div className="mb-2 flex items-center space-x-3">
_41
<AiFillCheckCircle className="text-2xl text-green-300" />
_41
<p className="text-sm text-purple-50 opacity-80">24/7 availability</p>
_41
</div>
_41
<div className="mb-2 flex items-center space-x-3">
_41
<AiFillCheckCircle className="text-2xl text-green-300" />
_41
<p className="text-sm text-purple-50 opacity-80 ">
_41
Expert-led tutorials
_41
</p>
_41
</div>
_41
<div className="mb-2 flex items-center space-x-3">
_41
<AiFillCheckCircle className="text-2xl text-green-300" />
_41
<p className="text-sm text-purple-50 opacity-80 ">
_41
High-quality contents
_41
</p>
_41
</div>
_41
<div className="mb-2 flex items-center space-x-3">
_41
<AiFillCheckCircle className="text-2xl text-green-300" />
_41
<p className="text-sm text-purple-50 opacity-80 ">
_41
Hands-on practical and interactive sessions
_41
</p>
_41
</div>
_41
</div>
_41
<button className="w-[200px] rounded-2xl bg-purple-50 px-5 py-3 text-lg font-semibold text-purple-600 hover:bg-purple-100">
_41
Purchase Now
_41
</button>
_41
</div>
_41
<div className="flex w-full items-center justify-center md:w-[50%]">
_41
<Image src={buy} alt="Man smiling" className="rounded-lg" />
_41
</div>
_41
</div>

Finally, update the Footer section as done below.


_10
<footer className="flex min-h-[10vh] w-full items-center justify-center bg-white">
_10
<p className="text-sm text-purple-800">
_10
Copyright, &copy; {new Date().getFullYear()} All Rights Reserved Tech Grow
_10
</p>
_10
</footer>

Success 🚀

After a successful payment, users are redirected to the Success page.

Create a success.tsx file and copy the code below into the file.


_16
import React from "react";
_16
import Link from "next/link";
_16
_16
export default function Success() {
_16
return (
_16
<div className="flex min-h-[100vh] w-full flex-col items-center justify-center">
_16
<h2 className="mb-4 text-3xl font-bold">Payment Sucessful!</h2>
_16
<Link
_16
href="/"
_16
className="rounded-2xl bg-purple-50 px-5 py-3 text-lg font-semibold text-purple-600 hover:bg-purple-100"
_16
>
_16
Go Home
_16
</Link>
_16
</div>
_16
);
_16
}

Congratulations!🎉 You've successfully created the user interface for the application.


Start collecting payments 💰

Stripe is a popular online payment processing platform that enables you to create products and integrate both one-time and recurring payment methods into your application.

Here, I'll walk you through how to create a product on Stripe and how to add the Stripe checkout page to your Next.js application.

First, you need to create a Stripe account. You can use a test mode account for this tutorial.

first

Select Products from the top menu and click the Add Product button to create a new product. Provide the product name, price, description, and payment option. Select one-time as the payment option.

Select Products

Create a .env.local file and copy the product ID into the file.


_10
PRODUCT_ID=<YOUR_PRODUCT_ID>

Next, click Developers from the top menu, select API keys, and create a new secret key.

ProdId

Save the secret key into the .env.local file. It authenticates and enables you to access Stripe from the application.


_10
STRIPE_API_KEY=<YOUR_STRIPE_SECRET_KEY>

Adding Stripe checkout page to Next.js

To do this, install the Stripe Node.js library.


_10
npm install stripe

Create an API endpoint - api/payment within the Next.js application and copy the code below into the file.


_24
//👉🏻 Within the api/payment.ts file
_24
import type { NextApiRequest, NextApiResponse } from "next";
_24
import Stripe from "stripe";
_24
_24
const stripe = new Stripe(process.env.STRIPE_API_KEY!, {} as any);
_24
_24
export default async function handler(
_24
req: NextApiRequest,
_24
res: NextApiResponse
_24
) {
_24
const session = await stripe.checkout.sessions.create({
_24
line_items: [
_24
{
_24
price: process.env.PRODUCT_ID,
_24
quantity: 1,
_24
},
_24
],
_24
mode: "payment",
_24
success_url: `http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}`,
_24
cancel_url: "http://localhost:3000",
_24
});
_24
_24
res.status(200).json({ session: session.url });
_24
}

The code snippet above creates a checkout session for the product and returns the session URL. The session URL is the link where payments for a product are collected, and you need to redirect users to this URL.

Create a function within the index.tsx file that retrieves the session URL from the API endpoint and redirects the user to the page. Execute the function when a user clicks any of the buttons on the web page.


_10
const handlePayment = async () => {
_10
try {
_10
const data = await fetch("/api/payment");
_10
const response = await data.json();
_10
window.location.assign(response.session);
_10
} catch (err) {
_10
console.error(err);
_10
}
_10
};

Stripe

Congratulations!🎉 You've successfully added the Stripe checkout page to your application. In the upcoming sections, you'll learn how to handle payments and save users' details to an Airtable database using Trigger.dev.


Process payments with Trigger.dev

Trigger.dev is an open-source library that enables you to create and monitor long-running jobs for your app with NextJS, Remix, Astro, and so many more! With Trigger.dev, you can automate, schedule, and delay tasks within your codebase and in services like GitHub repositories, Slack channels, etc.

Connect stripe to Trigger.dev ✨

Here, you'll learn how to handle Stripe payments within your application using Trigger.dev webhooks.

Trigger.dev webhooks are user-friendly, managing both the registration and un-registration processes for you. Additionally, if there's an error, it attempts to resend the event until successful.

All you have to do is specify the service and events you want to listen to; Trigger.dev takes care of the configurations.

Adding Trigger.dev to a Next.js app

Before we proceed, you need to create a Trigger.dev account.

Create an organization and project name for your jobs.

Create org

Follow the steps provided. Once you've completed them, feel free to move on to the next section of this article.

Org2

Otherwise, click Environments & API Keys on the sidebar menu of your project dashboard.

EnvApiKeys

Copy your DEV server API key and run the code snippet below to install Trigger.dev. Follow the instructions carefully.


_10
npx @trigger.dev/cli@latest init

Start your Next.js project.


_10
npm run dev

In another terminal, run the following code snippet to establish a tunnel between Trigger.dev and your Next.js project.


_10
npx @trigger.dev/cli@latest dev

Finally, rename the jobs/examples.ts file to jobs/functions.ts. This is where all the jobs are processed.

Congratulations!🎉 You've successfully added Trigger.dev to your Next.js app.

Listen to stripe successful payments

Install the Stripe package provided by Trigger.dev.


_10
npm install @trigger.dev/stripe@latest

Update the jobs/functions.ts file as shown below.


_25
import { client } from "@/trigger";
_25
import { Stripe } from "@trigger.dev/stripe";
_25
_25
const stripe = new Stripe({
_25
id: "stripe",
_25
apiKey: process.env.STRIPE_API_KEY!,
_25
});
_25
_25
client.defineJob({
_25
//👇🏻 job properties
_25
id: "save-customer",
_25
name: "Save Customer Details",
_25
version: "0.0.1",
_25
//👇🏻 event trigger
_25
trigger: stripe.onCheckoutSessionCompleted(),
_25
_25
run: async (payload, io, ctx) => {
_25
const { customer_details } = payload;
_25
await io.logger.info("Getting event from Stripe!🎉");
_25
//👇🏻 logs customer's details
_25
await io.logger.info(JSON.stringify(customer_details));
_25
_25
await io.logger.info("✨ Congratulations, A customer just paid! ✨");
_25
},
_25
});

The code snippet automatically creates a Stripe webhook that listens for checkout completion events, triggered when a user makes a payment.

Stripe Webhook

After the user makes a payment, their details are logged to the job console on Trigger.dev.

Stripe5

Save the customer information 💾

After retrieving the customer's details from the Stripe webhook, the next step is to save these details to a database. In this section, you will learn how to integrate Airtable into a Next.js app and interact with it using Trigger.dev.

Airtable is an easy-to-use cloud-based software that helps you organize information into customizable tables. It's like a mix between a spreadsheet and a database, allowing you to manage data, tasks, or projects collaboratively in a visually appealing way.

To get started, create an Airtable account and set up a workspace and a base. An Airtable workspace serves as a folder containing multiple databases, known as bases. Each base can contain several tables.

Bases

Within the base, create a table containing a Name and Email columns. This is where the customer's name and email retrieved from Stripe will be stored.

Airtable

Click the Help button on the navigation bar, and select API Documentation.

Help

Scroll through the page, find and copy the base and table ID, and save them into the .env.local file.


_10
AIRTABLE_BASE_ID=<YOUR_AIRTABLE_BASE_ID>
_10
AIRTABLE_TABLE_ID=<YOUR_AIRTABLE_TABLE_ID>

Next, create a personal access token by clicking on your avatar and selecting Developer Hub. Give the token a read-and-write scope.

Next

Save the newly generated token into the .env.local file.


_10
AIRTABLE_TOKEN=<YOUR_PERSONAL_ACCESS_TOKEN>

Then, install the Airtable package provided by Trigger.dev.


_10
npm install @trigger.dev/airtable

Update the jobs/functions.js file to save the user's name and email to Airtable after completing the payment checkout.


_48
import { Airtable } from "@trigger.dev/airtable";
_48
import { client } from "@/trigger";
_48
import { Stripe } from "@trigger.dev/stripe";
_48
_48
// -- 👇🏻 Airtable instance --
_48
const airtable = new Airtable({
_48
id: "airtable",
_48
token: process.env.AIRTABLE_TOKEN,
_48
});
_48
// -- 👇🏻 Stripe instance --
_48
const stripe = new Stripe({
_48
id: "stripe",
_48
apiKey: process.env.STRIPE_API_KEY!,
_48
});
_48
_48
client.defineJob({
_48
id: "save-customer",
_48
name: "Save Customer Details",
_48
version: "0.0.1",
_48
// -- 👇🏻 integrates Airtable --
_48
integrations: { airtable },
_48
trigger: stripe.onCheckoutSessionCompleted(),
_48
_48
run: async (payload, io, ctx) => {
_48
const { customer_details } = payload;
_48
await io.logger.info("Getting event from Stripe!🎉");
_48
await io.logger.info(JSON.stringify(customer_details));
_48
_48
await io.logger.info("Adding data to Airtable🎉");
_48
_48
// --👇🏻 access the exact table via its ID --
_48
const table = io.airtable
_48
.base(process.env.AIRTABLE_BASE_ID!)
_48
.table(process.env.AIRTABLE_TABLE_ID!);
_48
_48
// -- 👇🏻 adds a new record to the table --
_48
await table.createRecords("create records", [
_48
{
_48
fields: {
_48
Name: customer_details?.name!,
_48
Email: customer_details?.email!,
_48
},
_48
},
_48
]);
_48
_48
await io.logger.info("✨ Congratulations, New customer added! ✨");
_48
},
_48
});

The code snippet above integrates Airtable to Trigger.dev, access the table, and it with the customer's name and email.

Congratulations! You have completed the project for this tutorial.


Conclusion

So far, you've learned how to

  • add a Stripe checkout page to your Next.js app,
  • handle payments with Trigger.dev, and
  • save data to Airtable via Trigger.dev.

Trigger.dev offers three communication methods: webhook, schedule, and event. Schedule is ideal for recurring tasks, events activate a job upon sending a payload, and webhooks trigger real-time jobs when specific events occur.

As an open-source developer, you're invited to join our community to contribute and engage with maintainers. Don't hesitate to visit our GitHub repository to contribute and create issues related to Trigger.dev.

The source for this tutorial is available here: https://github.com/triggerdotdev/blog/tree/main/sales-page

Thank your for reading!