How to handle Stripe payments using React and Firebase?

As an example on how to handle payments, we will imagine that we just opened a new gym in the city.

If a customer buys a membership, he will pay 50 $ per month and he will have full access to the gym.

What do we need?

  • 2 Firebase projects
  • 1 Stripe account
  • 1 React project

Firebase configuration

Before we can open our new gym, we need to test payments. That's the reason why we need 2 Firebase projects; one will be for doing tests (PRE) and the other will be our validated system (PRO).

We will create a configuration file Firebase.js and set variables depending if we are working on PRE or PRO.

jsx
import firebase from 'firebase';

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Modify this line to set the enviroment
// environment -> 'PRE', 'PRO'
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export let environment = 'PRE';

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configuration of PRE environment
// You get your own configuration from Firebase
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var configPRE = {
    apiKey: "ladjkjKSJOEroijfkSLKDJoeroiijIFOj",
    authDomain: "coolgym-pre.firebaseapp.com",
    databaseURL: "https://coolgym-pre.firebaseio.com",
    projectId: "coolgym-pre",
    storageBucket: "coolgym-pre.appspot.com",
    messagingSenderId: "809342059318",
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configuration of PRO environment
// You get your own configuration from Firebase
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var configPRO = {
    apiKey: "jLKSDFjoweoJFLKFAJLDFoOJKKEOR",
    authDomain: "coolgym-pro.firebaseapp.com",
    databaseURL: "https://coolgym-prO.firebaseio.com",
    projectId: "coolgym-pro",
    storageBucket: "coolgym-pro.appspot.com",
    messagingSenderId: "83748289347"
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initializing Firebase depending on environment
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
firebase.initializeApp(environment === 'PRE' ? configPRE : configPRO);

export default firebase;

At this point we are able to introduce the new customer's data in our database. In Firebase you can do it manually. So, as an example, we will introduce the data of our first customer (feel free to do it in PRE and in PRO as well):

JSON
{
    "customers": {
        "1": {
            "name": "Erik",
            "age": 29,
            "city": "Barcelona"
        }
    }
}

Stripe configuration

Stipe will provide us a payment terminal to charge the customers. After we open a Stripe account, we will get API keys for our test environment (PRE) and for the real one (PRO).

We will create a configuration Stripe.js file with the API keys:

jsx
import { environment } from './Firebase';

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configuration of PRE environment
// You get your API key from Stripe
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let configPRE = { 
    apiKey: 'pk_test_jOFDsf7SERjisofjioJEIWR97F89sfjoio'
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Configuration of PRE environment
// You get your API key from Stripe
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let configPRO = {
    apiKey: 'pk_live_DJldjflkJFOWJEOJORJodlfkSDJKLFJksl'
}

export const apiKey = environment === 'PRE' ? configPRE.apiKey : configPRO.apiKey;

React setting

Now we can kindly offer the customer the payment terminal and our React component will act as it. We will show the user how much he needs to pay and he will only need to introduce his card details.

First, we will install this useful package:

terminal
npm i --save react-stripe-elements

Then, we create a PaymentTerminal.js component in React:

jsx
import React                           from 'react';
import { Elements, StripeProvider }    from 'react-stripe-elements';
import { apiKey }                      from './Stripe';

const PaymentTerminal = () => {
    
    return  (
        <div className = 'Modal'>
            <StripeProvider apiKey = {apiKey}>
                <Elements>
                    <CheckoutForm/>
                </Elements>
            </StripeProvider>
        </div>
    );
    
}

export default PaymentTerminal;

Finally, we can create a CheckoutForm.js component:

  1. Reads the customer's info from Firebase.
  2. Displays form to fill in with card details.
  3. Sends token to Firebase server.
  4. Writes in the database that the customer is now a member.
jsx
import React, { useEeffect, useState } from 'react';
import {CardElement, injectStripe}     from 'react-stripe-elements';
import firebase, { environment }       from '../Functions/Firebase';

const CheckoutForm = ({stripe}) => {
    
    const [customer, setCustomer]  = useState(null);
    const [payment, setPayment]    = useState(false);
    
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Reading customer's info from Firebase
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    useEffect( () => {
        
        firebase.database().ref('customers/1').on('value', snapshot => {
            
            if(snapshot.val()){
                
                setCustomer(snapshot.val());
                
            }
            
        });
        
    }, []);
    
    const submit = async (ev) => {
    
        let fetchURL = 'firebase_function_coolGymMembership_URL';
        
        let {token}  = await stripe.createToken({name: customer.name});
        
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // Sends token to Firebase server
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        let response = await fetch(fetchURL, {
            
            method: 'POST',
            headers: {'Content-Type':'application/json'},
            body: JSON.stringify({
                environment: environment,
                stripeToken: token.id, 
            })
            
        });
        
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // Writes in the database that the customer is a new member
        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        if(response.ok) {
            
            firebase.database().ref('customers/1/member').transaction(value => true);
            
            setPayment('success');
            
        }
    }
    
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Displays form to fill in with card details
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    return (
      <div className = 'Checkout'>
        { payment !== 'success'
        ? <div className = 'Modal-Wrap'>
                <h2>Welcome to coolGym!</h2>
                <p>You'll get charged 50 $ per month and you'll be a member.</p>
                <CardElement hidePostalCode = {true}/>
                <div className = 'Total'>
                    <button onClick = {hide}>Cancelar</button>
                    <button onClick = {submit}>Pay</button>   
                </div>
          </div>
        : <div className = 'Modal-Wrap Succeed'>
                <h2>Thanks!</h2>
                <p>Payment succesfully done!</p>
          </div> 
        }
      </div>
    );
    
}

export default injectStripe(CheckoutForm);

Firebase server function setting

Payments are processed from the server side. CheckoutForm.js sends the customer's data to Firebase and the server needs to process the transaction.

Thus, we need to create a new function in Firebase in index.js file:

javascript
const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
// firebase functions:config:set stripe.coolgym.token.pre = token_pre
// firebase functions:config:set stripe.coolgym.token.pro = token_pro
// firebase functions:config:set stripe.coolgym.priceId   = stripe_priceId
// firebase functions:config:get to see the variables

exports.coolGymMembership = functions.https.onRequest( (request, response) => {
    
    return cors(request, response, async () => {
        
        let environment = request.body.environment;
        let token       = request.body.stripeToken;
        
        let stripe = require('stripe')(functions.config().stripe.coolgym.token[environment.toLowerCase()]);
        
        let customer = await stripe.customers.create({
            source: token
        });
        
        let subscription = await stripe.subscriptions.create({
            customer: customer.id,
            items: [{price: functions.config().stripe.priceId}]
        });
        
        subscription
        ? response.status(200).json({subscriptionId: subscription.id})
        : response.status(500).send('Error!');
        
    });
    
});

Final result

If the user was correctly charged, he will have a new property in the database:

JSON
{
    "customers": {
        "1": {
            "name": "Erik",
            "age": 29,
            "city": "Barcelona",
            "member": true
        }
    }
}

Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.