Stripe With React And Flask_restful
Stripe has a few way to get started, you can use stripe-hosted page, embedded form, or custom payment flow. I will be using the embedded form. While custom payment flow has the most flexibility and stripe-hosted page has none(it's a no code approach), embedded form falls in between enough flexibility to do some customizations while at the same time letting stripe do some of the heavy lifting for us. You can read more about each approach here.
To get started we need to install stripe in our react and flask projects.
Run this command at the root of your react project:
npm install --save @stripe/react-stripe-js @stripe/stripe-js
Run this command at the root of your flask project:
pip3 install stripe
Now, good thing stripe doesn't require any special configuration so we can get started in creating the components and routes we need.
FLASK APPLICATION SETUP WITH STRIPE
Stripe needs your flask project to have a route that will create a session when the user is ready to proceed with payment. I personally like to have a route file for each route that I create so I've created a file called stripe_checkout_session.py and added the code below.
#! /usr/bin/env python3.6
from itertools import product
from statistics import quantiles
from config import api
from flask_restful import Resource
import os
from flask import Flask, jsonify, redirect, request
import stripe
class StripeCheckoutSession(Resource):
def post(self):
DOMAIN = os.getenv('DOMAIN')
# This is your test secret API key.
stripe.api_key = os.getenv('STRIPE_API_KEY')
ESTABLISH ING THE DOMAIN AND API_KEY
Since I am using flask_restful, I've configured the api variable in a config file. This is not from stripe, this configuration is to use flask_restful which means the routes will use flask_restful's Resource class. You don't have to do it this way but since stripe already has code examples using app.route
, my intention is to show you how you can do this using flask_restful and its Resource class.
In the post function we have the domain and strip.api_key. The domain should point to the URL your react app is hosted on. If you're using vite's default URL locally, you want it to look something like this:
'http://localhost:5173/'
Once you deploy your application you'd need to come back to this domain and change it to the domain of your react website. For now we will proceed with a local setup.
stripe.api_key
is needed to gain access to stripe's API. When you create an account on stripe, they issue you a key. This key is like an ID that you "show" stripe to gain access to their services. So by setting stripe.api_key to the key they give you, every time you hit stripe's API, that key will be used to verify who you are and what you have access to.
CREATING THE CHECKOUT-SESSION WITH STRIPE
To create a session with stripe
, it'll look something like this:
try:
session = stripe.checkout.Session.create(
ui_mode = 'embedded',
line_items=line_items_list,
mode='payment',
return_url=DOMAIN + '/return?session_id={CHECKOUT_SESSION_ID}',
)
except Exception as e:
return str(e)
Stripe's checkout session uses the create method to establish a session. This method takes some important arguments. Let's dive into what these are.
THE UI_MODE PARAMETER
The ui_mode parameter has two options: embedded
or hosted
. Since we are using the embedded form option we are going to use the embedded
option so the checkout session displays as an embedded form.
THE LINE_ITEMS PARAMETER
The line_items
parameter is where you pass an array of objects with some of the products information. Stripe will handle and display your items in the embedded form that they provide. Each product's object needs to follow a data structure like the following.
{
"price_data": {
"currency": "usd",
"product_data": {"name": product_name},
"unit_amount": product_price * 100,
},
"adjustable_quantity": {"enabled": True, "minimum": 1, "maximum": 10},
"quantity": quantity,
}
The important properties you need to use would be the price_data
and quantity
properties. When you hit the checkout_session route, each product's object should have the product name, price, and quantity.
Important side note: the unit_amount
that is in the price_data
object is calculated in cents and it's represented as a whole number. Here is the link to read more about it.
Depending on the structure and procedures of your project's setup, these product objects can be formatted in the frontend or the backend. I will give you an example on how you can do it in the backend, since the frontend is straightforward.
#getting the array of products from the front-end
product_list = request.get_json()
#define a list where the reformatted product's object will be stored
line_items_list = []
#iterate through the array of products received from the frontend
for product in product_list:
product_obj = product.get('product')
quantity = product.get('quantity')
product_name = product_obj.get('name')
product_price = product_obj.get('price')
#create a dict for each product using stripes data structure
product_={
"price_data": {
"currency": "usd",
"product_data": {"name": product_name},
"unit_amount": product_price * 100,
},
"adjustable_quantity": {"enabled": True, "minimum": 1, "maximum": 10},
"quantity": quantity,
}
#add the reformatted dict to the line_items_list
line_items_list.append(product_)
There is no difference on whether you format each product object in the frontend or backend, as long as it's formatted the right way before you hit stripe's API.
THE MODE PARAMETER
The mode parameter is to let stripe know what type of transaction you want stripe to provide. Here is the link to the different options mode
has. We are going using the payment
mode to let stripe know we just want to use the one-time payment process.
THE RETURN_URL PARAMETER
Last but not least, the return_url parameter. This parameter will hold the URL you want the user to be redirected to when the transaction is complete.
Your code should look something like this by now.
#! /usr/bin/env python3.6
from itertools import product
from statistics import quantiles
from config import api
from flask_restful import Resource
"""
server.py
Stripe Sample.
Python 3.6 or newer required.
"""
import os
from flask import Flask, jsonify, redirect, request
import stripe
class StripeCheckoutSession(Resource):
def post(self):
DOMAIN = os.getenv('DOMAIN')
# This is your test secret API key.
stripe.api_key = os.getenv('STRIPE_API_KEY')
product_list = request.get_json()
line_items_list = []
for product in product_list:
product_obj = product.get('product')
quantity = product.get('quantity')
product_name = product_obj.get('name')
product_price = product_obj.get('price')
product_={
"price_data": {
"currency": "usd",
"product_data": {"name": product_name},
"unit_amount": product_price * 100,
},
"adjustable_quantity": {"enabled": True, "minimum": 1, "maximum": 10},
"quantity": quantity,
}
line_items_list.append(product_)
try:
session = stripe.checkout.Session.create(
ui_mode = 'embedded',
line_items=line_items_list,
mode='payment',
return_url=DOMAIN + '/return?session_id={CHECKOUT_SESSION_ID}',
)
except Exception as e:
return str(e)
return jsonify(clientSecret=session.client_secret)
api.add_resource(StripeCheckoutSession, '/user/create-checkout-session')
Before continuing make sure you are receiving the secrete key when you hit the create-checkout-session
endpoint. Resolve any error before continuing.
CHECKING STATUS ROUTE
Stripe also needs you to create a check-status
route to check the status of the checkout-session
. This route will be needed for the Return component in your react app.
Create another route file and add the following code.
from config import api
from flask_restful import Resource
from flask import jsonify, request
import stripe
class SessionStatus(Resource):
def get(self):
session = stripe.checkout.Session.retrieve(request.args.get('session_id'))
return jsonify(status=session.status, customer_email=session.customer_details.email)
#Adjust the route to match the structure of your routes in your api.
api.add_resource(SessionStatus, '/user/session-status')
So there's not much going on here, this route basically just lets your react app know what the status of the checkout-session is. It will return a status of open
or complete
and this returned value will be used to determine what your react app will render for the user.
REACT APPLICATION SETUP WITH STRIPE
Now let create the component that will use the routes we just created.
In your react app, we are going to create two components: CheckoutForm and Return.
CHECKOUTFORM COMPONENT
Let's start with the CheckoutForm
component.
import React, { useCallback, useState, useEffect } from "react";
import { loadStripe } from '@stripe/stripe-js';
import {
EmbeddedCheckoutProvider,
EmbeddedCheckout
} from '@stripe/react-stripe-js';
import { postData } from "../../api";
const stripePromise = loadStripe(import.meta.env.VITE_STRIPEKEY);
const CheckoutForm = () => {
const fetchClientSecret = useCallback(async () => {
//get array of Products
//send array of products to backend
const getClientSecret = await postData('/api/user/create-checkout-session', parsedCartList)
//return the clientSecrete
return getClientSecret.clientSecret
}, []);
const options = { fetchClientSecret };
return (
<div id="checkout">
<EmbeddedCheckoutProvider
stripe={stripePromise}
options={options}
>
<EmbeddedCheckout />
</EmbeddedCheckoutProvider>
</div>
)
}
export default CheckoutForm
The loadStripe
function holds the key that stripe issues in their test-mode example here. Make sure you call the loadStripe
function outside the component. You can assign it any name you want. In this case, we'll just use the same name stripe uses.
We normally fetch data using useEffect
but in this situation we need to use the useCallback
hook because we are going to pass that function as props to the EmbeddedCheckoutProvider
component.
In the useCallback
hook we are doing a couple of things. Depending on how your project is set up, the goal is to retrieve the list of products your user has selected and then send a post request to obtain the secrete key.
The EmbeddedCheckoutProvider
component holds the stripe promise received from the loadStripe
function and the secrete key that will be needed to establish a safe connection during payment. The EmbeddedCheckout
component renders the checkout page with the client secret.
RETURN COMPONENT
Let's create the Return component.
import React, { useState, useEffect } from 'react'
import {getData } from '../../api';
const Return = () => {
const [status, setStatus] = useState(null);
const [customerEmail, setCustomerEmail] = useState('');
useEffect(() => {
const checkoutSessionStatus = async () => {
// returns the query string from the current URL of the web page
const queryString = window.location.search;
//URLSearchParams is a built-in javascript object
//that allows you to work with query string parameters
//provides methods for appending, deleting, getting, and
//setting key-value pairs in a query string.
const urlParams = new URLSearchParams(queryString);
//In this case we are going to the the session_id from the urlParams
const sessionId = urlParams.get('session_id');
//send the session_id and fetch for the checkout-session status
const checkoutStatus = await getData(`/api/user/session-status?session_id=${sessionId}`)
// if checkoutstatus was successful
if (checkoutStatus) {
// Set the status and email to the states
setStatus(checkoutStatus.status)
setCustomerEmail(checkoutStatus.customer_email)
//remove cart items from users cart in the database
//remove cart items from localStorage, useContext, or etc.
//reset cart items in cart to 0
}
}
checkoutSessionStatus()
}, []);
//if status is open, redirect user to the checkout page
if (status === 'open') {
return (
<Navigate to="/user/checkout" />
)
}
//if status is complete, display a message for the user to know that their
//payment was either successful or they need to try again.
if (status === 'complete') {
return (
<section id="success">
<p>
We appreciate your business! A confirmation email will be sent to {customerEmail}.
If you have any questions, please email <a href="mailto:orders@example.com">orders@example.com</a>.
</p>
</section>
)
}
return null;
}
export default Return
The Return component is pretty straightforward, I left comment explaining what each line is doing.
CREATE ROUTES TO RENDER COMPONENTS IN REACT APP
Since stripe has example creating routes with react-router-dom-v5, I will be using react-router-dom-v6.
So all you have to do is create the routes that look something like this:
//adjust the endpoints if needed
{ path: '/user/checkout', element: <CheckoutForm /> },
{ path: '/user/return', element: <Return /> },
Then in your checkout button, navigate your user to the CheckoutForm
component. You can use the useNavigate
hook from react-router-dom to direct your user to the CheckoutForm
component.
const handleCheckout = () => {
//format your array of products and store them either in state,
//local storage, and/or database
//then navigate user to the CheckoutForm component
navigate('/user/checkout')
}
That's it! If you want to use stripe in a production project, you just need to use your issued key from stripe when you create an account and don't forget to replace the domain with your live hosting domain. I hope this was a helpful read, happy coding!
Subscribe to my newsletter
Read articles from Ruth directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ruth
Ruth
I am a Full Stack Software Engineer with experience in leveraging a variety of technologies to find the easiest and most efficient and effective way to meet the needs of each application. My experience ranges from creating simple landing pages to developing complex interactive web applications. I believe my skills in communication, team work, and googling will contribute positively to your team.