How to accept Paypal payments with React

I was devastated hearing the news about what happened on Lombok in the last couple of days. Having lived in Bali for the last few months, I have a huge bond with the Indonesian country and all of its islands. That’s why I want to help the amazing organisation Lombok Strong. And so can you! If you feel like doing something good, please support them. Every donation helps!

This React tutorial should give you guidance on how to integrate PayPal in your React application. I came across this topic when I had to introduce a payment process for my own course platform. As I went through the same decision process, I decided in favor of PayPal and Stripe.

This tutorial shows you how to integrate PayPal in your React application. There are a couple of open source React + PayPal components out there. But I found out that it isn’t too difficult to set it up on your own. Afterward, you have full control over the PayPal payments in your React applications.

If you are interested in a full blown payment solution with PayPal and Stripe in React, you can also read about the Stripe in React setup in my other article.

Create a Paypal REST API app

Fortunately you don’t need to setup an own payment server for PayPal. You can implement a React component with the PayPal business logic and reuse it everywhere in your application(s).

Before you can implement the component, you need to create a PayPal REST API application. Your “App Name” can be any name. It’s only for yourself to identify your application in case you have more than one PayPal REST API app.

After you have created the app on the PayPal dashboard, you should find your Client IDs for your sandbox and live application. You will need both in order to test your PayPal payment in development mode but also to use it in production mode in your React application.

PayPal React Component

Now let’s build the PayPal React component from scratch. It will be a class component, because we have to manage state in React’s local state and use a couple of lifecycle methods.

import React from 'react';
import ReactDOM from 'react-dom';

class PaypalButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showButton: false,
    };
  }

  componentDidMount() {
    // Todo
  }

  componentWillReceiveProps(nextProps) {
    // Todo
  }

  render() {
    // Todo
  }
}

export default PaypalButton;

Why is it necessary to manage a local state? The PayPal API library can be loaded asynchronously in our component. After we have loaded everything, the showButton state can be used to render the button. Doing it like this, enables you also to use the PayPal button for server-side rendering.

Let’s load the PayPal script asynchronously in our React component. Additionally, make sure to bind React and ReactDOM to the window object, because its needed for the PayPal React Component which comes with the library.

import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';

class PaypalButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showButton: false,
    };

    window.React = React;
    window.ReactDOM = ReactDOM;
  }

  componentDidMount() {
    // Todo
  }

  componentWillReceiveProps(nextProps) {
    // Todo
  }

  render() {
    // Todo
  }
}

export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);

If you haven’t installed by now, you need to install the react-async-script-loader via npm. The scriptLoader is a higher order component which can be used in React to lazy load scripts.

npm install react-async-script-loader

Now, in the componentDidMount() lifecycle method, you can decide if the button should be rendered already. The scriptLoader gives you access to two properties in the props of the component, isScriptLoaded and isScriptLoadSucceed, to check if the script was loaded successfully. If that is the case, you could already render the PayPal button.

import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';

class PaypalButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showButton: false,
    };

    window.React = React;
    window.ReactDOM = ReactDOM;
  }

  componentDidMount() {
    const {
      isScriptLoaded,
      isScriptLoadSucceed
    } = this.props;

    if (isScriptLoaded && isScriptLoadSucceed) {
      this.setState({ showButton: true });
    }
  }

  componentWillReceiveProps(nextProps) {
    // Todo
  }

  render() {
    // Todo
  }
}

export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);

In most cases the script isn’t loaded in the componentDidMount() lifecycle method. This lifecycle method runs only once when the component is instantiated and it’s not 100% certain to have the script loaded at this point in time. Therefore you still have the componentWillReceiveProps() lifecycle method to check for the loaded script.

import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';

class PaypalButton extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showButton: false,
    };

    window.React = React;
    window.ReactDOM = ReactDOM;
  }

  componentDidMount() {
    const {
      isScriptLoaded,
      isScriptLoadSucceed
    } = this.props;

    if (isScriptLoaded && isScriptLoadSucceed) {
      this.setState({ showButton: true });
    }
  }

  componentWillReceiveProps(nextProps) {
    const {
      isScriptLoaded,
      isScriptLoadSucceed,
    } = nextProps;

    const isLoadedButWasntLoadedBefore =
      !this.state.showButton &&
      !this.props.isScriptLoaded &&
      isScriptLoaded;

    if (isLoadedButWasntLoadedBefore) {
      if (isScriptLoadSucceed) {
        this.setState({ showButton: true });
      }
    }
  }

  render() {
    // Todo
  }
}

export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);

Now the only lifecycle method missing is the render() method. Let’s see what should be rendered in this method.

import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';

class PaypalButton extends React.Component {

  ...

  render() {
    const {
      total,
      currency,
      env,
      commit,
      client,
      onSuccess,
      onError,
      onCancel,
    } = this.props;

    const {
      showButton,
    } = this.state;

    const payment = // Todo

    const onAuthorize = // Todo

    return (
      <div>
        {showButton && <paypal.Button.react
          env={env}
          client={client}
          commit={commit}
          payment={payment}
          onAuthorize={onAuthorize}
          onCancel={onCancel}
          onError={onError}
        />}
      </div>
    );
  }
}

export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);

There are many props which are passed to your PayPal component. Let’s check what they stand for:

  • currency: Your desired currency (e.g. USD) to charge a customer.
  • total: The total amount of your desired currency (e.g. $1 would be a total amount of 100).
  • commit: If set to true then the PayPal checkout flow will display a ‘Pay Now’ button.
  • client: An object which has your Client IDs for Sandbox and Production.
  • env: Environment mode (development, production) to choose a Client ID from the client object.
  • onSuccess: A handler to do something in case the payment was successful.
  • onError: A handler to do something in case the payment was erroneous.
  • onCancel: A handler to do something in case the payment was cancelled.

You will see in a later step how these props are passed to the PayPal component from a parent component which uses the PayPal component. For now, let’s focus on finishing the implementation. As you can see, the showButton boolean from React’s local component state is used for a conditional rendering of the PayPal button.

The only thing left is to implement the payment business logic in the payment() and onAuthorize() functions which follows PayPals REST API definition. First, you have to create a payment with the amount and currency grouped with your Client ID based on the environment. Second, you can execute the transaction.

import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';

class PaypalButton extends React.Component {

  ...

  render() {
    const {
      total,
      currency,
      env,
      commit,
      client,
      onSuccess,
      onError,
      onCancel,
    } = this.props;

    const {
      showButton,
    } = this.state;

    const payment = () =>
      paypal.rest.payment.create(env, client, {
        transactions: [
          {
            amount: {
              total,
              currency,
            }
          },
        ],
      });

    const onAuthorize = (data, actions) =>
      actions.payment.execute()
        .then(() => {
          const payment = {
            paid: true,
            cancelled: false,
            payerID: data.payerID,
            paymentID: data.paymentID,
            paymentToken: data.paymentToken,
            returnUrl: data.returnUrl,
          };

          onSuccess(payment);
        });

    return (
      <div>
        {showButton && <paypal.Button.react
          env={env}
          client={client}
          commit={commit}
          payment={payment}
          onAuthorize={onAuthorize}
          onCancel={onCancel}
          onError={onError}
        />}
      </div>
    );
  }
}

export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(PaypalButton);

That’s it for the PayPal button implementation. Now, how would you finally make use of it in another component?

import React from 'react';
import PaypalButton from './PaypalButton';

const CLIENT = {
  sandbox: 'xxxXXX',
  production: 'xxxXXX',
};

const ENV = process.env.NODE_ENV === 'production'
  ? 'production'
  : 'sandbox';

class App extends React.Component {
  render() {
    const onSuccess = (payment) =>
      console.log('Successful payment!', payment);

    const onError = (error) =>
      console.log('Erroneous payment OR failed to load script!', error);

    const onCancel = (data) =>
      console.log('Cancelled payment!', data);

    return (
      <div>
        <PaypalButton
          client={CLIENT}
          env={ENV}
          commit={true}
          currency={'USD'}
          total={100}
          onSuccess={onSuccess}
          onError={onError}
          onCancel={onCancel}
        />
      </div>
    );
  }
}

export default App;

It’s a security risk to keep the Client IDs in your source code. It’s better to include them in a .env file. That way, you prevent to make them publicly available. Don’t forget to add the .env file to your .gitignore file if you are using GitHub.

import React from 'react';
import PaypalButton from './PaypalButton';

const CLIENT = {
  sandbox: process.env.PAYPAL_CLIENT_ID_SANDBOX,
  production: process.env.PAYPAL_CLIENT_ID_PRODUCTION,
};

class App extends React.Component {
  ...
}

export default App;

That’s basically the whole implementation of the PayPal component in React with the official PayPal libraries. Now you can use it in various applications. Hopefully, the tutorial helped you to setup PayPal in React on your own.

Build a Hacker News App along the way. No setup configuration. No tooling. No Redux. Plain React in 190+ pages of learning material. Learn React like 33.000+ readers.

Get the Book
comments powered by Disqus

Never miss an article about web development, JavaScript and self-growth.

Take Part

Join 15.000+ Developers

Learn Web Development with JavaScript

Tips and Tricks

Access Tutorials, eBooks and Courses

Personal Development as a Software Engineer