In this section, we need to define the action that redirects the user to the checkout page. In the course slug, we have to Buy the course that currently does nothing as below.
But before that... we need to define the serverless function that creates the session URL for checkout payments.
We use the Stripe built-in checkout page to process the payment for the website. Stripe's built-in checkout page is fully managed by the Stripe team and with a few parameters, we can integrate Stripe payment into our website.
After completing the payment on the Stripe Checkout page, we can redirect the user to the course page. The simplified roadmap image for implementing Stripe checkout is shown as below:
If the payment is fails or not complete, we can simply redirect the user to the course page along with the query parameter is below.
http://localhost:8000/product/mysql-course/?state=fail
If the payment is successful, we first execute the serverless function to store the subscription data of the recipient in the Sanity server and then redirect to the course page.
The detailed roadmap for implementing Stripe checkout is shown as below:
Here, we write the logic for checkout serverless function as highlighted below:
To redirect the user, we can use Stripe API to create the checkout session. For your reference, you can visit the below page.
To redirect the user to the Stripe checkout, we can create the file at the below location:
/src/api/checkout.js
The above file creates the route in gatsby at http://localhost:8000/api/checkout
.
In the checkout.js
, we can write the code as below:
import stripeAPI from "stripe"
export default async function handler(req, res) {
try {
const stripe = new stripeAPI(String(process.env.GATSBY_STRIPE_secret_ID), {
apiVersion: "2020-08-27",
})
// Stripe docs: https://stripe.com/docs/api/checkout/sessions/create
const session = await stripe.checkout.sessions.create({
success_url: req.body.successUrl,
cancel_url: req.body.cancelUrl,
customer_email: req.body.email,
payment_method_types: ["card"],
line_items: [
{
price: req.body.priceId,
quantity: 1,
},
],
mode: req.body.mode,
metadata: req.body.metadata,
})
res.json({ url: session.url })
} catch (error) {
const status = error.response?.status || error.statusCode || 500
const message = error.response?.data?.message || error.message
// Respond with error code and message
res.status(status).json({
message: error.expose ? message : `Faulty ${req.baseUrl}: ${message}`,
})
}
}
For the above serverless function, we need to pass the below parameter in the function request body.
successUrl, cancelUrl, email, priceId, mode, metadata
To test the above serverless function, we can send the HTTP request to the API route in the Postman as below:
For the checkout serverless function, we grab the request data as below:
The above Postman HTTP request generates the checkout URL. If we visit the checkout URL on the browser, it opens the stripe checkout page from where you can proceed to checkout.
For the course page, we need to create three (3) models state based on the different conditions as below:
Form, Success, Fail
The three (3) models appear in the browser as below.
Here, we define the logic for the form modal. In the form modal, the name and email input are available. After the user fills in the input, it redirects to the Stripe checkout page. For that, we can create a file at the below location:
/src/components/singleProduct/state/form.js
In the form.js
, we can write the code as below:
import React, { useState } from "react"
import axios from "axios"
const Form = ({ location, productPrice }) => {
const [state, setState] = useState({})
const [disable, setDisable] = useState(false)
const handleChange = e => {
setState({ ...state, [e.target.name]: e.target.value })
}
async function checkout(e) {
try {
e.preventDefault()
setDisable(true)
let priceId = productPrice.priceID
const {
data: { url },
} = await axios.post("/api/checkout", {
email: state?.email,
name: state?.name,
mode: "subscription",
priceId: priceId,
metadata: {
priceId: priceId,
priceRef: `${productPrice._id}`,
},
cancelUrl: `${location?.href}?state=fail`,
successUrl: `${location.origin}/api/createSubscription?name=${state?.name}&email=${state?.email}&priceId=${priceId}&priceRef=${productPrice._id}&redirectOrigin=${location.href}`,
})
window.location = url
setDisable(false)
} catch (error) {
setDisable(false)
}
}
return (
<form onSubmit={checkout}>
<h1 className="mb-5 text-lg font-medium text-gray-900 title-font">
Continue to Payment
</h1>
<input
className="block w-full p-2 mb-4 placeholder-gray-900 bg-gray-200 border rounded appearance-none focus:border-teal-500"
name="name"
type="text"
onChange={handleChange}
placeholder="Name"
/>
<input
className="block w-full p-2 mb-4 placeholder-gray-900 bg-gray-200 border rounded appearance-none focus:border-teal-500"
name="email"
type="email"
onChange={handleChange}
placeholder="Email"
/>
<div className="container pt-5 mx-auto">
<div className="flex justify-end space-x-2">
<button
type="button"
type="submit"
className={`font-semibold text-sm inline-flex items-center justify-center px-3 py-1.5 border border-transparent rounded leading-5 shadow-sm transition duration-150 ease-in-out bg-indigo-500 focus:outline-none focus-visible:ring-2 hover:bg-indigo-600 text-white disabled:bg-gray-50 disabled:text-gray-500 disabled:border-gray-200 disabled:shadow-none disabled:pointer-events-none`}
disabled={disable ? true : false}
>
Proceed to checkout
</button>
</div>
</div>
</form>
)
}
export default Form
You can download form.js
file below:
Let's breakdown the above form.js
code to understand it better.
In the form.js
, we can send the HTTP request (along with the payload data) to the checkout serverless function when the form is submitted as below.
To incorporate the Form Modal in the course page, we can update the code as below:
To render the Form Modal in React component, we can write the code as below:
At last, we can update the singleProduct.js
code as below:
import React, { useState, useEffect } from "react"
import { Link } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Success from "./state/success"
import Fail from "./state/fail"
import Form from "./state/form"
import Modal from "@components/modal"
import queryString from "query-string"
import PortableText from "@components/portabletext/portableText"
const SingleProduct = ({
data: { title, _rawExerpt, _rawBody, image, productPrice },
location,
}) => {
const [showModal, setShowModal] = useState(false)
const [modalState, setModalState] = useState("")
useEffect(() => {
const queriedTheme = queryString.parse(location.search)
if (["form", "fail", "success"].includes(queriedTheme.state)) {
setShowModal(true)
setModalState(queriedTheme.state)
}
}, [])
function handleState() {
setShowModal(true)
setModalState("form")
}
return (
<main className="grid place-items-center">
{showModal && (
<Modal onClose={() => setShowModal(false)}>
{modalState == "form" && (
<Form location={location} productPrice={productPrice} />
)}
</Modal>
)}
<section className="body-font">
<span className="block mb-3 text-sm font-extrabold leading-snug text-center text-gray-400 md:text-left">
COURSE
</span>
<div className="flex flex-col items-center pb-24 md:flex-row md:items-start">
<div className="flex flex-col items-center mb-16 text-center lg:flex-grow md:w-1/2 lg:pr-14 md:pr-10 md:items-start md:text-left md:mb-0">
<h1 className="mb-4 text-4xl font-extrabold leading-snug sm:text-4xl">
{title}
</h1>
<p className="mb-8 leading-relaxed">
{_rawExerpt && <PortableText blocks={_rawExerpt} />}
</p>
<div className="flex items-center justify-center">
<button
className="flex-shrink-0 px-6 py-2 mr-10 text-lg text-white bg-indigo-500 border-0 rounded focus:outline-none hover:bg-indigo-600"
onClick={() => {
handleState()
}}
>
Buy the course
</button>
<p className="flex-shrink-0 text-2xl font-medium leading-snug text-gray-400">
10 USD
</p>
</div>
</div>
<div className="w-5/6 lg:max-w-xs lg:w-full md:w-1/2">
<GatsbyImage
className="object-cover object-center rounded"
image={getImage(image.asset)}
alt={"heading"}
/>
</div>
</div>
</section>
<small className="prose max-w-fit">
{_rawBody && <PortableText blocks={_rawBody} />}
</small>
</main>
)
}
export default SingleProduct
Now!!! Let's fill the Form Modal and see how it goes.
Great. We have successfully landed on the Stripe checkout page. In the next section, the user completes the payment and lands on the course page.