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.

· 6 min read
Barack Ouma

Barack Ouma

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

- Stripe and SendGrid Account

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

Image

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;

Image

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

Image

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;

Image

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

Image

When we submiit through the Button our dialog Works Hurray!

Image

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 useSearchParams 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

Image

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

Image

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

Image

CancelPage

Image

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

Barack Ouma

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.