DEV Community

Cover image for How to add PayPal checkout payments to your React app
Pato for PayPal Developer

Posted on • Updated on

How to add PayPal checkout payments to your React app

Have you always wondered how to monetize your web applications πŸ€‘? Time to start making those benjamins πŸ’ΈπŸ’Έ. In this how-to guide, you will learn how to integrate PayPal as your checkout payment solution for your ReactJS app using the react-paypal-js npm package.

What is PayPal? Paypal is a payment processing product that helps you process payments for your mobile and web applications. We provide a fast and easy way to handle online payments, whether it's for a digital media property or an online merchant of any kind in over 100 countries.

Guide Overview

This tutorial requires the following:

  • A PayPal Developer Account
  • A ReactJS application (I'm using create react app for this example).

You can find the source code to this guide in our PayPal React Sample App in our Github Org.

Part 1: PayPal Developer sandbox app


First, we need to create a PayPal app. To do this, navigate to the PayPal Developer Dashboard and login into your PayPal developer account. Once you are inside the PayPal Developer dashboard, make sure you are in the My Apps & Credentials page. Inside this page, click on the blue button Create App.

Create a PayPal app inside the PayPal developer Dashboard

Enter the name of the PayPal app you are creating. In my case, I named my app React-checkout. Now, select the App Type to be a Merchant, and click on the blue button Create App.

Name your new PaPal Developer App

After creating your PayPal app, you will see a screen similar to mine. In this screen, you will see your Client ID. Copy this ID and save it somewhere (We will use it later on in this tutorial).

You can always get this Client ID by navigating to the app you just created inside of the PayPal Developer Dashboard.

Copy the Client ID generated for your PayPal app

Note: Client ID is Class 4 data. Client ID is non-sensitive data, but still be prudent about sharing or posting it. We recommend putting the Client ID in an environment variable in an .env file.

Part 2: Adding PayPal to your React App


Add the PayPal NPM package

To install the react-paypal-js npm package run the following command inside of your project's terminal.

npm install @paypal/react-paypal-js

The PayPal npm package consists of 2 main parts:

  • The Context Provider, this <PayPalScriptProvider/> is responsible for the PayPal JS SDK script. This provider uses the native React Context API for managing the state and communicating with child components. It also supports reloading the script when parameters change.

  • The PayPal SDK Components, components like <PayPalButtons/> are used to render the UI for PayPal products served by the JS SDK.

If you have any issues with this npm package, please report them in its GitHub repo.

After you have installed the react-paypal-js npm package, open your root file, in my case my root file is the App.js file.

In this app, the App.js component is responsible for loading the PayPal script and rendering the <Checkout/> component. The <Checkout/> component will be created later in this tutorial.

In this file, you will import the PayPalScriptProvider from the PayPal npm package.

At the top of the file, add the following line of code:

import { PayPalScriptProvider } from "@paypal/react-paypal-js";
Enter fullscreen mode Exit fullscreen mode

In this file we are adding the initialOptions object, these options can be changed with other configuration parameters. To learn more about the other configuration options look at the PayPal SDK docs.

const initialOptions = {
  "client-id": "YOUR-CLIENT-ID-HERE",
  currency: "USD",
  intent: "capture",
};
Enter fullscreen mode Exit fullscreen mode

Now, make sure you replace the text from the client-id property with the Client ID from your PayPal app.

Working with the PayPalScriptProvider

Finally, inside the App.js, we add the <PayPalScriptProvider/>. Notice we have inside the provider the <Checkout/> component where we have the PayPal components. We now add the options prop to the PayPalScriptProvider to configure the JS SDK. It accepts an object for passing query parameters and data attributes to the JS SDK script.

<PayPalScriptProvider options={initialOptions}>
        <Checkout/>
</PayPalScriptProvider>
Enter fullscreen mode Exit fullscreen mode

Your App.js component should look like this:

import Checkout from './Checkout';
import { PayPalScriptProvider } from "@paypal/react-paypal-js";

const initialOptions = {
  "client-id": "YOUR-CLIENT-ID-HERE",
  currency: "USD",
  intent: "capture",
};

function App() {
  return (
    <PayPalScriptProvider options={initialOptions}>
        <Checkout/>
    </PayPalScriptProvider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In our React app, we are giving the user the option to select between 2 currencies (USD and Euro) to make a payment. When the user changes the currency, the value will be passed to the PayPal script thanks to the usePayPalScriptReducer().

Time to create a new Checkout.js component inside of your React application. This file is responsible for loading the PayPal components such as the PayPal buttons.

At the top of the Checkout.js file, add the following line to include the PayPalButtons and the usePayPalScriptReducer.

import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
Enter fullscreen mode Exit fullscreen mode

Loading state with the usePayPalScriptReducer

The usePayPalScriptReducer will show a spinner when the PayPal script is loading and can be used to change the values of the options of the PayPal script and at the same time reload the script with the updated parameters.

The PayPal script has several loading states and with the usePayPalScriptReducer we can track it in an easier way. This state can be used to show a loading spinner while the script loads or an error message if it fails to load.

Loading states:

  • isInitial - not started (only used when passing deferLoading={true})
  • isPending - loading (default)
  • isResolved - successfully loaded
  • isRejected - failed to load

In this sample app, we used the isPending state to render the rest of the UI including the PayPalButtons.

Inside your Checkout component add the code below. This code will extract the options, the loading state (isPending), and the dispatch to dispatch an action to our usePayPalScriptReducer();

const [{ options, isPending }, dispatch] = usePayPalScriptReducer();
Enter fullscreen mode Exit fullscreen mode

Adding The Currency Selectors

In the Checkout component, you will add the code to update the state with the new currency the user selects and this will dispatch an action to the usePayPalScriptReducer to update the PayPal script.

const [currency, setCurrency] = useState(options.currency);

    const onCurrencyChange = ({ target: { value } }) => {
        setCurrency(value);
        dispatch({
            type: "resetOptions",
            value: {
                ...options,
                currency: value,
            },
        });
    }
Enter fullscreen mode Exit fullscreen mode

Now, your UI will look like this:

{
isPending ? <p>LOADING...</p> : (
  <>
    <select value={currency} onChange={onCurrencyChange}>
      <option value="USD">πŸ’΅ USD</option>
      <option value="EUR">πŸ’Ά Euro</option>
    </select>
  </>
 )
}
Enter fullscreen mode Exit fullscreen mode

Adding the PayPal buttons

To start using the PayPal buttons, all you have to do is add the <PayPalButtons/> component to your JSX. The magic comes when you extend the functionality of the PayPal buttons by passing the following props available:

  • style: This attribute allows you to style the PayPal button E.g color, shape, layout, and more.
  • createOrder: This attribute allows you to create the request of your order with the following properties: item_total, purchase_units, and more.
  • onApprove: This attribute allows doing something with the order details after the order has been created.

Inside of your <Checkout/> component, after the onCurrencyChange function add the following functions to be called with the onCreateOrder and onApprove props of the PayPal button.

const onCreateOrder = (data,actions) => {
        return actions.order.create({
            purchase_units: [
                {
                    amount: {
                        value: "8.99",
                    },
                },
            ],
        });
    }

const onApproveOrder = (data,actions) => {
  return actions.order.capture().then((details) => {
  const name = details.payer.name.given_name;
    alert(`Transaction completed by ${name}`);
  });
}
Enter fullscreen mode Exit fullscreen mode

Now we will add the <PayPalButtons/> component to your <Checkout/> component. Your JSX code should look like this:

<div className="checkout">
  {isPending ? <p>LOADING...</p> : 
    (
      <>
        <select value={currency} onChange={onCurrencyChange}>
          option value="USD">πŸ’΅ USD</option>
          <option value="EUR">πŸ’Ά Euro</option>
        </select>
        <PayPalButtons 
          style={{ layout: "vertical" }}
          createOrder={(data, actions) => onCreateOrder(data, actions)}
          onApprove={(data, actions) => onApproveOrder(data, actions)}
        />
     </>
   )}
</div>
Enter fullscreen mode Exit fullscreen mode

The Final code of the Checkout.js file will look like this:

import React, { useState } from 'react';
import './Checkout.css';
import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";

const Checkout = () => {
    const [{ options, isPending }, dispatch] = usePayPalScriptReducer();
    const [currency, setCurrency] = useState(options.currency);

    const onCurrencyChange = ({ target: { value } }) => {
        setCurrency(value);
        dispatch({
            type: "resetOptions",
            value: {
                ...options,
                currency: value,
            },
        });
    }

    const onCreateOrder = (data,actions) => {
        return actions.order.create({
            purchase_units: [
                {
                    amount: {
                        value: "8.99",
                    },
                },
            ],
        });
    }

    const onApproveOrder = (data,actions) => {
        return actions.order.capture().then((details) => {
            const name = details.payer.name.given_name;
            alert(`Transaction completed by ${name}`);
        });
    }

    return (
        <div className="checkout">
            {isPending ? <p>LOADING...</p> : (
                <>
                    <select value={currency} onChange={onCurrencyChange}>
                            <option value="USD">πŸ’΅ USD</option>
                            <option value="EUR">πŸ’Ά Euro</option>
                    </select>
                    <PayPalButtons 
                        style={{ layout: "vertical" }}
                        createOrder={(data, actions) => onCreateOrder(data, actions)}
                        onApprove={(data, actions) => onApproveOrder(data, actions)}
                    />
                </>
            )}
        </div>
    );
}

export default Checkout;
Enter fullscreen mode Exit fullscreen mode

Part 3: Testing the PayPal Checkout


Testing the PayPal Checkout

Inside your project run in the terminal the npm start command to run your ReactJS application.

Open http://localhost:3000 to view the app in your browser.

You should see the following:

React app with PayPal checkout buttons

Finally, click on the Debit or Credit Card button to make a payment! You can use the sample card below to test the guest checkout experience with the credit cards.

Sample Card

You can create sample credit card numbers using the PayPal Credit Card Generator inside of your PayPal Developer Dashboard.
Card Type: Visa

Card Number: 4032039534213337

Expiration Date: 03/2026

CVV: 952

Online payments have grown exponentially in the past years, especially during the COVID-19 pandemic. The number of online payment transactions will continue to grow as the years go by. We know that online payments are not going anywhere, but now it’s up to you to get on this trend so your business continues to make money and PayPal is here to help you with this.

You can find the source code to guide in our PayPal React Sample App in our Github Org.

S/O to Greg Jopa for his content that help me write this how-to guide :)

PayPal Developer Community

The PayPal Developer community helps you build your career, while also improving PayPal products and the developer experience. You’ll be able to contribute code and documentation, meet new people and learn from the open source community.

Top comments (18)

Collapse
 
esponges profile image
Fernando GonzΓ‘lez Tostado

Shouldn't the client-id be imported from somewhere safe like the .env file with process.env.PAYPAL_CLIENT_ID. Or it's not a sensible id? I know that for the sake of simplicty here is hardcoded in the component, but might be worth pointing this out. Congrats on the useful post btw!

Collapse
 
devpato profile image
Pato • Edited

Client ID is Class 4 data.
Client id is non-sensitive data, but still be prudent about sharing or posting it. And yes I did it for the sake of simplicty! Thank you for your comment

Collapse
 
esponges profile image
Fernando GonzΓ‘lez Tostado • Edited

Yes, I thought that I was not, however I think that any credential β€”regardless of its sensitivenessβ€” should always be stored in .env. Just as a good practice. Thank you for the clarification.

Thread Thread
 
devpato profile image
Pato

I agree with you! I just didn’t explained that for the simplicity of the tutorial but I will add a note ;) thank you!

Collapse
 
thethirdrace profile image
TheThirdRace • Edited

My experience

As someone who actually updated their PayPal setup not long ago using this exact technique, I would recommend caution.

This article looks good, but the truth is the react-paypal-js package is not very flexible and will bring you many headaches.

Packaging

The package downloads a dynamically generated script at runtime, meaning the "versioning" of your code is not static, it's dynamic with all the problems from such method.

My website uses static site generation (SSG) to pre-compile each page as static HTML. Because of this, I can only use hashes for my Content Security Policy (CSP). Because PayPal change that dynamically generated script on the fly, my PROD environment stopped working on 2 occasions in the last 6 months... I had to adjust the hash to get back the functionality.

To avoid this, my options are very limited.

I could use a nonce, but I can kiss goodbye to my "perfect" reliability from static site generation (SSG) and I'll be forced to use server side generation (SSG).

The only other solution is to lower my Content Security Policy to pretty much accept anything from anyone...

So for security reasons, PayPal is kinda forcing me to go all SSR or remove the Content Security Policy altogether... All because PayPal decided to download at runtime a script instead of simply versioning their API like any normal and reliable service. It's a nightmare to manage, enough to look at other solutions...

Styling

You don't have much control on the styling. Everything is hidden away in an iframe, meaning those border radius ain't gonna fly.

It wouldn't be so bad if the PayPal button was actually styled correctly, but there are many glitches that just make your site look amateurish. You can't control the outline color or the button size much, it doesn't integrate well with your design, you have to design around PayPal instead. It's not a nice experience at all to be honest...

State

The onCreateOrder and onApproveOrder functions you pass are memoized within the PayPal iframe.

This means if you want dynamic pricing, like doing a "give what you want", then you're in for a lot of fun! If you want to select between 3 options and charge the according price, you're in for a lot of fun too! You kinda have to go through hoops and loops like a circus animal to make it work. It does work, but the problem is it doesn't work like anything you've ever seen in React. It's a very awkward developer experience.

Conclusion

I did upgrade using this setup and it works, as long as PayPal don't change anything on their side it's rock solid.

But if I had to redo it again, I would probably not choose this solution because of the heap of headaches it creates.

I would probably go with a payment processor like Stripe because it has both a wonderful developer experience (DX) and an awesome user experience (UX). It also offers a lot more flexibility for payment sources (GPay, Apple Pay, Credit card, Alipay, WeChat, etc.), styling and there's no shenanigan with how stuff is loaded or updated in React. Their doc is also top notch! It ain't PayPal, but then again PayPal is not the defacto payment source anymore either...

Do your research before jumping on the bandwagon

Collapse
 
devpato profile image
Pato • Edited

Hi! Thank you so much for you feedback :) If you have a few minutes of you time in the next few weeks I would love to set a quick call with you to see discuss this valuable feedback :) let me know! Thank you

Collapse
 
thethirdrace profile image
TheThirdRace

Hi,

I can definitely take some time to discuss with you.

Although my weeks are a bit crazy at the moment, so it might take a bit of time before we can set this up.

I'll send you a DM when I can schedule something.

Thread Thread
 
devpato profile image
Pato

Sounds like a plan, thank you!

Collapse
 
guiasdeprepago profile image
GuiasDePrepago

Hi, currently I'm having issues passing a state as the price on create order, do you have any solution for this?

Collapse
 
thethirdrace profile image
TheThirdRace • Edited

You can't pass state as the button is memoized.

What you can do is pass a function that checks a ref. You can adjust the value of the ref however you need.

It's completely crazy to have to use this kind of method, but that's the only work around I've found for dynamic pricing...

Collapse
 
vidipghosh profile image
Vidip Ghosh

Thank you so much for sharing. Very useful.

Collapse
 
chema profile image
JosΓ© MarΓ­a CL

thank you so much!

Collapse
 
devpato profile image
Pato

Anytime :)))

Collapse
 
crishrpz profile image
crishrpz

Amazing!! Thanks

Collapse
 
colin_walker profile image
Colin Walker

Great post, thanks for the info!

Collapse
 
eunit profile image
Eunit

The initialOptions should be like so, instead of what you suggested @devpato. I think it was a typo from your end. Take note of the clientId

const initialOptions = {
  clientId: "YOUR-CLIENT-ID-HERE",
  currency: "USD",
  intent: "capture",
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jvph profile image
JVPH

What if you're fetching the client id from the server? It seems the client id is set as undefined no matter what (useState, useEffect, etc).

Collapse
 
immanuelcherui1 profile image
IMMANUEL CHERUIYOT

is create vite@latest working with this?