Stripe With React And Flask_restful

RuthRuth
9 min read

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!

0
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.