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
-
Part 1: PayPal Developer sandbox App
- Creating an app using the PayPal sandbox
-
Part 2: Adding PayPal to your React App
- Add the PayPal NPM package
- Working with the
PayPalScriptProvider
- Loading state with the
usePayPalScriptReducer
- Adding the PayPal buttons
-
Part 3: Testing the PayPal Checkout
- Testing the PayPal Checkout
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.
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.
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.
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";
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",
};
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>
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;
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";
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();
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,
},
});
}
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>
</>
)
}
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}`);
});
}
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>
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;
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:
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.
- Website: developer.paypal.com
- Twitter: @paypaldev
- Github: @paypal developer
Top comments (18)
Shouldn't the
client-id
be imported from somewhere safe like the.env
file withprocess.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!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
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.I agree with you! I just didnβt explained that for the simplicity of the tutorial but I will add a note ;) thank you!
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
andonApproveOrder
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
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
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.
Sounds like a plan, thank you!
Hi, currently I'm having issues passing a state as the price on create order, do you have any solution for this?
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 theref
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...
Thank you so much for sharing. Very useful.
thank you so much!
Anytime :)))
Amazing!! Thanks
Great post, thanks for the info!
The
initialOptions
should be like so, instead of what you suggested @devpato. I think it was a typo from your end. Take note of theclientId
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).
is create vite@latest working with this?