KodaSchool
Learn To Code
Stripe Payments Made Easy: Checkout and Email Automation with SendGrid Part 2
Stripe makes it easy to handle payments, and combining it with SendGrid can streamline sending email notifications to users after successful transactions.
topics
Overview
Ever thought about launching your own application over a weekend or rapidly shipping your SaaS product? Stripe makes it easy to handle payments, and combining it with SendGrid can streamline sending email notifications to users after successful transactions. In this blog,Weel use React to consumer our srtie backend server configured using the Two Third party APIs.
What you'll Learn
- Setting up a React-Vite Web app
- Integrating Material Tailwind for UI components
- Handling user bookings and displaying notification
- Using Axios for making HTTP requests
Prerequisites
- Node.js installed
- npm or yarn installed
Configuration and Setup for project Environment
Initialize The React-Project
npm init vite@latest
Name your project and Select React as the framework and Javascript as the Framework
Navigate into the Project directory and Install the necessary Dependencies;
cd frontend ; npm i react-spinners react-router-dom react-icons axios
@stripe/react-stripe-js @material-tailwind/react
npm run dev
Navigate to this page to configure Tailwindcss into our project;
1. Install Tailwindcss
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
2. Add tailwind to your postCSS configuration
3. Create a postcss.config.cjs file in your root directory
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
4. We are planning to fullly utilise Material Tailwind as our Componnet Library;
5. Tailor your tailwind.config.js to utliise this function
import withMT from '@material-tailwind/react/utils/withMT'
export default withMT({
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
play: ["Play", "sans-serif"],
},
},
},
variants: {},
plugins: [],
})
The 'withMT' function is a utility provided by Material Tailwind to help integrate Tailwind CSS with Material Tailwind components seamlessly.
6. We need to create two folders the components folder and the styles Folder
within our src folder
7. Within the styles folder lets add our tailwind directives input.css file and its
output.css file
Add an Error.jsx file to ensure that whenever non-existent routes are navigates they fall back to this page
As our last command lets add the watch:tailwiind in our package.json within the scripts object
scripts:
{....
"watch:tailwind": "tailwindcss -i ./src/styles/input.css -o ./src/styles/output.css --watch"
.....
}
Here is our final folder structure;
Within the src/component create the following files using the cli;
mkdir css
cd css;touch success.css cancel.css
touch cancel.jsx
touch success.jsx
touch queue.jsx
Queue Component (queue.jsx)
Task 1. Declare all the imports and the states to be used within our componnent
import { useState } from 'react';
import {
Button,
Select,
Option,
Input,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
Card,
CardBody,
} from '@material-tailwind/react';
import axios from 'axios';
import { PuffLoader } from 'react-spinners';
export const Queue = () => {
const [service, setService] = useState('');
const [startTime] = useState('');
const [email, setEmail] = useState('');
const [preferredServiceTime, setPreferredServiceTime] = useState('');
const [showDialog, setShowDialog] = useState(false);
const [bill, setBill] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const hourlyRate = 20;
Task 2. Send Email to User Function
~Create a Stripe Checkout Session:
The function first makes a POST request to the backend server to create a Stripe checkout session( necessary for handling payments) a charged (bill)is attached to the request
The server responds with a sessionId, which is used to generate a payment link.
~Generate the Payment Link:
Using the sessionId received from the server, the function constructs a payment link (stripePaymentLink). This link will direct the user to the Stripe-hosted payment page.
~Send the Email:
The function then makes a POST request to the backend server to send an email to the user.
The request is sent to http://localhost:3000/send-email
with details such as the user's email, the service booked, the start time, the bill amount, and the payment link.
const sendEmailToUser = async (email, service, startTime, bill) => {
try {
const sessionResponse = await axios.post('http://localhost:3000/create-checkout-session', { amount: bill });
const sessionId = sessionResponse.data.sessionId;
console.log('Session ID:', sessionId)
const stripePaymentLink = `http://localhost:5173/stripe-payment?session_id=${sessionId}`;
const response = await axios.post('http://localhost:3000/send-email', {
email,
service,
startTime,
bill,
stripePaymentLink
});
if (response.data.success) {
console.log('Email sent successfully', response.data);
setShowDialog(true);
} else {
console.error('Error sending email:', response.data.error);
}
} catch (error) {
console.error('Error sending email:', error);
}
};
Task 3 . Handling Boooking Function and the Dialog Functionality
~Booking Form:
The form includes a Select component for choosing the service, an Input component for entering the email, preferred service time in minutes.
The Button component triggers the handleBooking function when clicked. If the isLoading state is true, the button displays a loading spinner.
~Success Dialog:
The Dialog component displays a success message when the booking is successful.
const handleBooking = async () => {
// Calculate the bill based on the preferred service time and hourly rate
const serviceTimeInHours = preferredServiceTime / 60;
const calculatedBill = serviceTimeInHours * hourlyRate;
setBill(calculatedBill);
setEmail(email)
setService(service);
try {
// Send the email, service, and amount to the server
const sessionResponse = await axios.post('http://localhost:3000/create-checkout-session', {
amount: calculatedBill,
email: email,
service: service
});
console.log('sessionres for vercel', sessionResponse);
console.log('sessionres for vercel', sessionResponse.config.data.email);
localStorage.setItem('bill', calculatedBill);
await sendEmailToUser(email, service, startTime, calculatedBill);
} catch (error) {
console.error('Error creating Stripe session or sending email:', error);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-3xl font-bold mb-8">Book Your Session</h1>
<Card className="w-96">
<CardBody className="flex flex-col space-y-4">
<Select label="Select Service" value={service} onChange={(value) => setService(value)}>
<Option value="computer">Computer Rental</Option>
<Option value="internet">Internet Access</Option>
<Option value="printing">Printing</Option>
</Select>
<Input label="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
<Input
label="How much time will you Take (in minutes)"
type="
number"
value={preferredServiceTime}
onChange={(e) => setPreferredServiceTime(e.target.value)}
/>
<Button onClick={handleBooking} disabled={isLoading}>
{isLoading ? (
<>
<PuffLoader color="#EBF0EF" className="mr-2" /> Processing...
</>
) : (
'Proceed to Checkout'
)}
</Button>
</CardBody>
</Card>
<Dialog open={showDialog} handler={() => setShowDialog(false)}>
<DialogHeader>Booking Successful</DialogHeader>
<DialogBody>
Your session has been booked successfully. You will receive an email confirmation shortly with a payment link.
<br />
Your bill: ${bill.toFixed(2)}
</DialogBody>
<DialogFooter>
<Button variant="text" onClick={() => setShowDialog(false)}>
OK
</Button>
</DialogFooter>
</Dialog>
</div>
);
};
~Demo :
Lets take a glimpse of our component
When we submiit through the Button our dialog Works Hurray!
Payments page
payments.jsx
This component uses the Stripe JavaScript library to load Stripe and redirect the user to the checkout session based on a session ID provided in the URL parameters.
~Extract Session ID from URL Parameters:
The component uses the useSearchParam
s hook to extract the session_id
from the URL parameters.
~Load Stripe:
The loadStripe function from the @stripe/stripe-js library is used to load the Stripe JavaScript library with the provided publishable key.
~Redirect to Stripe Checkout:
The component defines an asynchronous function redirectToCheckout that uses the loaded Stripe instance to redirect the user to the Stripe Checkout page.
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { loadStripe } from '@stripe/stripe-js';
import { HashLoader } from 'react-spinners';
export const StripePaymentPage = () => {
const [searchParams] = useSearchParams();
const sessionId = searchParams.get('session_id');
useEffect(() => {
const redirectToCheckout = async () => {
const stripe = await loadStripe('pk_test_51NGYDuCZYEjq9G2uxPaav5ZKYWVuVhhzrPPQDKBlUfJT8EhSfsCdq3GsVxPz012amQ904zC0lLuFrpktDrFGsJ7f006j7FHyhK');
try {
const { error } = await stripe.redirectToCheckout({
sessionId,
});
if (error) {
console.error('Error redirecting to Stripe Checkout:', error);
}
} catch (error) {
console.error('Error redirecting to Stripe Checkout:', error);
}
};
if (sessionId) {
redirectToCheckout();
}
}, [sessionId]);
return <div>
<HashLoader color="#0E100F" /> <p className='font-bold '>Redirecting to Stripe Checkout...</p></div>;
};
Upon receiving the Emaill click the link and Get redirected to the Stripe hosted page
The Page navigates to the Stripe page in a new Tab Fill in your payment details
Use stripe test cards for Simulation of Live environment
Hurray We have successfully managed to Initiate a stripe checkout a stripe checkout
Transaction Status
A Transaction can either be Successful or can be Cancelled .
Lets ensure we handle the payment status as it should.
In our success.jsx Add this code Snippet
import { FaCheck } from "react-icons/fa";
import { Link } from 'react-router-dom';
import './css/success.css'
export const Success = () => {
return (
<div className='main'>
<div className='success-page'>
<div className="success-icon">
<FaCheck />
</div>
<h1>Payment Successful</h1>
<h3>Your payment has been processed Successfully</h3>
<p>Click on the button below to continue shopping</p>
<Link to= '/' >
<button>Continue Using our Services</button>
</Link>
</div>
</div>
)
}
In our Cancel.jsx Add this code snippet
import './css/cancel.css'
import { CgDanger } from "react-icons/cg";
import { Link } from 'react-router-dom';
export const Cancel = () => {
return (
<div className='main'>
<div className='fail-page'>
<div className="fail-icon">
<CgDanger />
</div>
<h1>Payment Unsuccessful</h1>
<h3>Error Processing your payment</h3>
<p>Click on the button to go back</p>
<Link to= '/queue' >
<button>Back</button>
</Link>
</div>
</div>
)
}
Let add some Styling to both files by success.css and Cancel.css
success.css file
.main {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f1f1f1;
}
.success-page{
background: white;
border-radius: 40px;
padding: 100px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
.success-icon{
font-size: 100px;
color: #00ff00;
display: flex;
justify-content: center;
align-items: flex-start;
margin-bottom: 30px;
}
.success-page h1{
font-size: 30px;
}
.success-page h3{
font-size: 20px;
}
.success-page button{
background: black;
color: white;
border: none;
outline: none;
border-radius: 40px;
padding: 25px;
cursor: pointer;
font-size: 20px;
transition: all 0.3s ease-in-out;
}
.success-page button:hover{
background: #f1f1f1;
color: black;
border: 1px solid black;
}
Cancel.css
.main {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f1f1f1;
}
.fail-page{
background: white;
border-radius: 40px;
padding: 100px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
}
.fail-icon{
font-size: 100px;
color: #ff0000;
display: flex;
justify-content: center;
align-items: flex-start;
margin-bottom: 30px;
}
.fail-page h1{
font-size: 30px;
}
.fail-page h3{
font-size: 20px;
}
.fail-page button{
background: black;
color: white;
border: none;
outline: none;
border-radius: 30px;
padding: 15px;
padding-left: 60px;
padding-right: 60px;
cursor: pointer;
font-size: 20px;
transition: all 0.3s ease-in-out;
}
.fail-page button:hover{
background: #f1f1f1;
color: black;
border: 1px solid black;
}
Lets see what weve built in a nutshell
SuccessPage
CancelPage
Lets add the main.jsx file and the App.jsx file for our last touches
Finally Run our server using npm run dev command
Main.jsx file
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './styles/output.css'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
Here is our App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Error from './error';
import { Queue } from './components/queue';
import { StripePaymentPage } from './components/payments';
import {Success} from '../src/components/success'
import {Cancel} from '../src/components/cancel'
// eslint-disable-next-line react/prop-types
const Container = ({ children }) => (
<div className="container mx-auto px-4 py-8">{children}</div>
);
function App() {
return (
<Router>
<Container>
<Routes>
<Route path="/" element={<Queue />} />
<Route path="/success" element={<Success />} />
<Route path="/cancel" element={<Cancel />} />
<Route path="/stripe-payment" element={<StripePaymentPage />} />
<Route path="*" element={<Error />} />
</Routes>
</Container>
</Router>
);
}
export default App;
Form more insights visit This repo
I reference u checking on to this article for the Server-side :
Stripe Payments Made Easy: Checkout and Email Automation with SendGrid
Happy Coding!
share
Software Engineer | AWS Enthusiast | Tech Writer
Building and scaling cloud infrastructures.
Expert in AWS architecture, cost optimization, and security.
Passionate about simplifying tech through writing and hands-on solutions.
Driven to solve real-world problems with innovative and Quality Software.