Implementing one time payments using React and Stripe

A few days ago, I was trying to implement one time payments using Stripe, but I was getting an error:

Customer has not linked source with id in Stripe.

The process to solve the problem was:

  1. User introduces card details (client-side)
  2. Stripe generates a token (client-side)
  3. Creating a customer using the token as a source (server-side)
  4. Charging the customer (server-side)

Disclaimer: I'm using React in the client-side and Firebase in the server-side.

Client-side

Stripe provides 2 packages to work with React. Install them:

terminal
npm i --save @stripe/stripe-js @stripe/react-stripe-js

Now it's time to create a payment modal:

jsx
import React, { useState, useEffect }                    from 'react';
import { loadStripe }                                    from '@stripe/stripe-js';
import { CardElement, Elements, useStripe, useElements } from '@stripe/react-stripe-js';

const PaymentModal = () => {
    
    const stripePromise = loadStripe('STRIPE_PUBLIC_API_KEY');
    
    return  (
        <div className = 'Modal'>
            <Elements stripe = {stripePromise}>
                <CheckoutForm/>
            </Elements>
        </div>
    );
    
}

export default PaymentModal;

At this point, we can define the checkout form. The checkout form will have a <CardElement/>. After the user introduces card details and clicks on the button, a token will be generated by Stripe and sent to the server via a fetch() function.

jsx
const CheckoutForm = () => {
    
    const [payment, setPayment] = useState(false);
    const stripe                = useStripe();
    const elements              = useElements();
    
    const handleSubmit = async (e) => {
        
        e.preventDefault();
        
        const { error, token } = await stripe.createToken(elements.getElement(CardElement));
        
        if(!error){
            
            setPayment('processing');
            
            let response = await fetch(fetchUrl, {
                
                method: 'POST',
                headers: {'Content-Type':'application/json'},
                body: JSON.stringify({
                    name: name,
                    mail: mail,
                    token: token.id
                })
                
            });
            
            if(response.ok) {
                
                console.log('Payment successful!');
                
            }
            
        }
        
    }
    
    return (
        <div className = 'Checkout'>
            <form className = 'Modal-Wrap' onSubmit = {handleSubmit}>
                <CardElement options = {{hidePostalCode: true}}/>
                    <button type = 'submit' disabled = {!stripe}>Pay</button>   
            </form>
        </div>
    );
}

Server-side

The server will receive the token, will create a new customer using the token as a source and finally charge the customer. If everything went well, the server will respond with Ok!. Otherwise, the server will respond with Error!.

javascript
const functions  = require('firebase-functions');
const cors       = require('cors')({origin: true});
const nodemailer = require('nodemailer');
const stripe     = require('stripe')('STRIPE_PRIVATE_API_KEY');

exports.oneTimePayment = functions.https.onRequest( (request, response) => {
    
    return cors(request, response, async () => {
        
        let name  = request.body.name;
        let email = request.body.mail;
        let token = request.body.token;
        
        let customer = await stripe.customers.create({
            name: name,
            email: email,
            source: token
        });
        
        let charge = await stripe.charges.create({
            customer: customer.id,
            amount: 1000,
            currency: 'eur',
            description: 'Charge example',
        });
        
        charge
        ? response.status(200).send('Ok!')
        : response.status(500).send('Error!');
        
    });
    
});

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