How to integrate PayPal payments with React Native

There aren’t many ways to add a payment interface to a React Native app natively. As a result, many mobile developers opt to build their own payment interface using the PayPal SDK.

PayPal is one of the first and biggest internet payment services in the world. Its availability in 200 countries makes it a reliable payment interface for both businesses and individuals. PayPal has a huge library for integration with various programming languages, including Python, Java, JavaScript, PHP, etc.

In this tutorial, we’ll show you how to build a payment interface by interlinking web and native technologies with each other using the PayPal SDK for JavaScript.

We’ll start by building basic React Native and React apps. To follow along, you should already be familiar with JavaScript, Node.js tools such as npx and npm or yarn, modules, and basic React components. You’ll also need a PayPal developer account to create credentials for the payment integration.

We’ll use PayPal’s Integration API for JavaScript for web. We’ll build the payment interface in React as a single-page app and host it on Firebase Hosting. Finally, we’ll use React Native WebView to create the link between the React Native app and the React web app.

React Native PayPal integration: Basic setup

First, we’ll create a basic React Native app. To create the app, just enter the following command:

$ npx react-native init myPayPalApp

We’ll need to create a React app also. To create the web app, enter the following command:

npx create-react-app my-paypal-web

As these apps are initializing, we’ll use our PayPal developer account to create new app and credentials required for the integration.

Go to developer.paypal.com and log into your developer account. After logging in, you’ll be redirected to your dashboard, which will look something like this:

You’ll see a default application listed. For this tutorial, we’ll take the following steps to create a new app.

Click the Create App button. We’re using PayPal sandbox for testing, so feel free to populate as per your liking. Then click, Create App.

After the app is created, you’ll be redirected to the settings and credentials page. You can see your app’s sandbox account email, client ID, and others settings. The client ID is what we need for this tutorial.

For testing, we’ll also need sandbox customer accounts. To create customer accounts, click Accounts under Sandbox in the navigation menu to the left.

Now click Create account, then select United States and hit Create.

After the account is created, to see account credentials (i.e., email and password) for login at the test payment gateway, hover on the menu button next to the newly created account and click View/edit a****ccount.

You’ll land on something like this:

You’ll see the email ID as well as a system-generated password. You can change the password by clicking the Change password link, or you can use the system-generated password to log in during payment.

Now that we’ve completed the basic requirements, we can move on to building the payment interface.

Building the payment interface

For the payment interface, we’ll make changes to the React project we created above (my-paypal-web). We’ll add PayPal buttons to our webpage and take the result as our callback.

Copy the client ID from the new app page you just created over PayPal (shown above) and paste it into the public/index.html file’s <head> in your project.

<script src="https://www.paypal.com/sdk/js?client-id=[replace-this-with-your-client-id]&currency=USD"></script>

Your code should look something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <script src="https://www.paypal.com/sdk/js?client-id=AXEWcCDcoTu8Wt1Ud0ifqLZM2A4_MbgJhNaTByCizxG0yi8V4o6sccW5RgXtXNesMh7n38Rp0Cv2KN63&currency=USD"></script>

    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

After adding this script tag with your own client ID, it’s time to create the PayPal button for your app.

Edit the App.``j``s file. First, create a reference to the PayPal Button as a React component:

const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });

You can use this as a component in your App.``j``s component. Just delete the content of the parent component and add the PayPalButton. Your App.``j``s should look like this:

import React from "react";
import ReactDOM from "react-dom";
import "./App.css";
const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });
function App() {
  function _createOrder(data, actions) {
    return actions.order.create({
      purchase_units: [
        {
          amount: {
            value: "1",
          },
        },
      ],
    });
  }
  return (
    <div className="App">
      <PayPalButton
        createOrder={(data, actions) => _createOrder(data, actions)}
      />
    </div>
  );
}
export default App;

Go to the project directory and enter either yarn start or npm start, depending on your preference. After the React server is up and running on localhost, it’ll automatically open up localhost:3000 in your browser window. If not, visit http://localhost:3000 from your preferred browser. You’ll see an output like this:

You can style the page as you like; we won’t get into that here. You can make the changes as you prefer.

If you look at the code, you can see that we’ve already defined a prop to PayPalButton named [createOrder](https://developer.paypal.com/docs/business/javascript-sdk/javascript-sdk-reference/#createorder), which enables you to specify the content of the requests. It’s mainly the amount in this example, but you can also specify the currency, etc.

For callbacks, we’ll create functions and add them as props to the PayPalButton. First, we’ll add onApprove. This function is called when an order is approved by PayPal.

To create an async _onApprove function:

async function _onApprove(data, actions) {
    let order = await actions.order.capture();
    console.log(order);
    return order;
  }

Here, await keyword is used to fetch the details of the order. Then, we’ll console.log the order details.

Also add this function to PayPalButton props:

<PayPalButton
    createOrder={(data, actions) => _createOrder(data, actions)}
    onApprove={(data, actions) => _onApprove(data, actions)}
/>

In addition, we’ll add the onCancel and onError props to get callbacks if the user cancels a payment or if there is some error on PayPal’s end. We’ll create only one call function and use it on both props:

function _onError(err) {
  console.log(err);
}

Your PayPal Button code should look like this:

<PayPalButton
    createOrder={(data, actions) => _createOrder(data, actions)}
    onApprove={(data, actions) => _onApprove(data, actions)}
    onCancel={() => _onError("Canceled")}
    onError={(err) => _onError(err)}
/>

After these additions, your App.js file should look something like this:

import React from "react";
import ReactDOM from "react-dom";
import "./App.css";
const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });
function App() {
  function _createOrder(data, actions) {
    return actions.order.create({
      purchase_units: [
        {
          amount: {
            value: "1",
          },
        },
      ],
    });
  }
  async function _onApprove(data, actions) {
    let order = await actions.order.capture();
    console.log(order);
    return order;
  }
  function _onError(err) {
    console.log(err);
  }
  return (
    <div className="App">
      <PayPalButton
        createOrder={(data, actions) => _createOrder(data, actions)}
        onApprove={(data, actions) => _onApprove(data, actions)}
        onCancel={() => _onError("Canceled")}
        onError={(err) => _onError(err)}
      />
    </div>
  );
}
export default App;

All we’re doing is creating a payment request for $1.00 from the user and logging it to see the result.

To test your payment gateway, just click the PayPal button, enter the user credentials (i.e., the email and password from step one), and make the payment.

After the payment is made, you’ll be redirected back to your app page. Open the developer console by right clicking, inspect element, then go to the console tab. You’ll see the result from the dummy payment you just made:

Setting up Firebase Hosting

Since we’re using Firebase to host our React web app, start by signing up for a Firebase account.

Once you have an account, click Add Project and set a project name:

You can choose whether to enable or disable analytics:

Now that you’ve created your Firebase project, click C****ontinue.

To set up hosting, go to the hosting tab in the sidebar, then click the Get started button.

Open a terminal window and install firebase-tools on your system:

$ npm install -g firebase-tools

Log in using your firebase account so you can easily connect with the project from the terminal window:

$ firebase login

Use your account to authorize the login. After you've logged in successfully, go to the my-paypal-web project directory and enter the following command:

$firebase init

Use the arrow keys on the keyboard to navigate to Hosting. Press the space bar to select and return/enter to continue.

Because we’ve already created a project, we’ll select Use and existing project:

Next, select the project we created from the list and hit return/enter.

On the next step, enter the following configs:

=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? build
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No

After you’ve done that, you’ll see a success message at the bottom:

✔  Wrote build/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

To push your web app live, enter following commands:

$ yarn build

After the build is complete, enter:

$ firebase deploy

You’ll see a result with the hosting URL:

=== Deploying to 'my-pay-web'...

i  deploying hosting
i  hosting[my-pay-web]: beginning deploy...
i  hosting[my-pay-web]: found 18 files in build
✔  hosting[my-pay-web]: file upload complete
i  hosting[my-pay-web]: finalizing version...
✔  hosting[my-pay-web]: version finalized
i  hosting[my-pay-web]: releasing new version...
✔  hosting[my-pay-web]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/my-pay-web/overview
Hosting URL: https://my-pay-web.web.app

You can just copy the hosting URL and paste it in your preferred browser. Here’s what mine looks like:

https://my-pay-web.web.app

Building the basic WebView in React Native

If you have prior experience working with React Native, that’s a plus. If not, the official docs offer a guide to setting up your environment and installing the app. If you haven’t done that already, just go to the project directory and use the following command to install the WebView module for React Native:

yarn add react-native-webview

Next, connect you device using USB and enter the following command:

npx react-native run-android

After you’ve successfully installed the app on your device or emulator, open up the App.js file. Delete the default extra code and import the WebView module:

import { WebView } from 'react-native-webview';

To initialize the payment gateway from React Native, we’ll create a button to show the web view in a modal and get a response from WebView. We’ll also create a useState() hook to show and hide the WebView.

const [showGateway, setShowGateway] = useState(false);

Button:

<View style={styles.btnCon}>
  <TouchableOpacity
    style={styles.btn}
    onPress={() => setShowGateway(true)}>
      <Text style={styles.btnTxt}>Pay Using PayPal</Text>
  </TouchableOpacity>
</View>

Button styles:

  btnCon: {
    height: 45,
    width: '70%',
    elevation: 1,
    backgroundColor: '#00457C',
    borderRadius: 3,
  },
  btn: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  btnTxt: {
    color: '#fff',
    fontSize: 18,
  },

Button output:

Now, import the M``odal component from react-native and create the modal with a basic web view showing google.com.

Modal and WebView:

{showGateway ? (
        <Modal
          visible={showGateway}
          onDismiss={() => setShowGateway(false)}
          onRequestClose={() => setShowGateway(false)}
          animationType={"fade"}
          transparent>
          <View style={styles.webViewCon}>
            <View style={styles.wbHead}>
              <TouchableOpacity
                style={{padding: 13}}
                onPress={() => setShowGateway(false)}>
                <Feather name={'x'} size={24} />
              </TouchableOpacity>
              <Text
                style={{
                  flex: 1,
                  textAlign: 'center',
                  fontSize: 16,
                  fontWeight: 'bold',
                  color: '#00457C',
                }}>
                PayPal GateWay
              </Text>
              <View style={{padding: 13}}>
                <ActivityIndicator size={24} color={'#00457C'} />
              </View>
            </View>
            <WebView
              source={{uri: 'https://www.google.com'}}
              style={{flex: 1}}
            />
          </View>
        </Modal>
      ) : null}

We’re using <Feather /> from [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons) and ActivityIndicator from react-native.

Modal and WebView output:

Now, we’ll show/hide the ActivityIndicator after the page has loaded to get a callback from WebView. If the page has loaded, we’ll add following hooks and props:

const [prog, setProg] = useState(false);
const [progClr, setProgClr] = useState('#000');

Props for WebView:

onLoadStart={() => {
  setProg(true);
  setProgClr('#000');
}}
onLoadProgress={() => {
  setProg(true);
  setProgClr('#00457C');
}}
onLoadEnd={() => {
  setProg(false);
}}
onLoad={() => {
  setProg(false);
}}

Just for extra UX, this code changes the color of ActivityIndicator according to the progress of the page.

Your App.js file should now look something like this:

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Modal,
  ActivityIndicator,
} from 'react-native';
import {WebView} from 'react-native-webview';
import Feather from 'react-native-vector-icons/Feather';


const App = () => {
  const [showGateway, setShowGateway] = useState(false);
  const [prog, setProg] = useState(false);
  const [progClr, setProgClr] = useState('#000');
  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={styles.container}>
        <View style={styles.btnCon}>
          <TouchableOpacity
            style={styles.btn}
            onPress={() => setShowGateway(true)}>
            <Text style={styles.btnTxt}>Pay Using PayPal</Text>
          </TouchableOpacity>
        </View>
      </View>
      {showGateway ? (
        <Modal
          visible={showGateway}
          onDismiss={() => setShowGateway(false)}
          onRequestClose={() => setShowGateway(false)}
          animationType={"fade"}
          transparent>
          <View style={styles.webViewCon}>
            <View style={styles.wbHead}>
              <TouchableOpacity
                style={{padding: 13}}
                onPress={() => setShowGateway(false)}>
                <Feather name={'x'} size={24} />
              </TouchableOpacity>
              <Text
                style={{
                  flex: 1,
                  textAlign: 'center',
                  fontSize: 16,
                  fontWeight: 'bold',
                  color: '#00457C',
                }}>
                PayPal GateWay
              </Text>
              <View style={{padding: 13, opacity: prog ? 1 : 0}}>
                <ActivityIndicator size={24} color={progClr} />
              </View>
            </View>
            <WebView
              source={{uri: 'https://www.google.com'}}
              style={{flex: 1}}
              onLoadStart={() => {
                setProg(true);
                setProgClr('#000');
              }}
              onLoadProgress={() => {
                setProg(true);
                setProgClr('#00457C');
              }}
              onLoadEnd={() => {
                setProg(false);
              }}
              onLoad={() => {
                setProg(false);
              }}
            />
          </View>
        </Modal>
      ) : null}
    </SafeAreaView>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#fff',
  },
  btnCon: {
    height: 45,
    width: '70%',
    elevation: 1,
    backgroundColor: '#00457C',
    borderRadius: 3,
  },
  btn: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  btnTxt: {
    color: '#fff',
    fontSize: 18,
  },
  webViewCon: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  wbHead: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f9f9f9',
    zIndex: 25,
    elevation: 2,
  },
});
export default App;

Connecting the PayPal interface to a React Native app using WebView

To receive data from the WebView page to our React Native app, we’ll use onMessage prop. We also need to add some code to our web app to send the required data. The window.ReactNativeWebView.postMessage method is used to send data from WebView to our React Native app.

After making to required change your _onApprove and _onError functions, your code should look like this:

async function _onApprove(data, actions) {
    let order = await actions.order.capture();
    console.log(order);
    window.ReactNativeWebView &&
      window.ReactNativeWebView.postMessage(JSON.stringify(order));
    return order;
}
function _onError(err) {
    console.log(err);
    let errObj = {
      err: err,
      status: "FAILED",
    };
    window.ReactNativeWebView &&
      window.ReactNativeWebView.postMessage(JSON.stringify(errObj));
}

We’re using JSON.stringify because postMessage can only accept string arguments.

Remember to build the web app and deploy it to Firebase:

$ yarn build
$ firebase deploy

To get the data on the React Native side, we’ll use onMessage. Create the following function and add it to the onMessage prop:

function onMessage(e) {
    let data = e.nativeEvent.data;
    setShowGateway(false);
    console.log(data);
  }

Add the onMessage prop and set the source uri to the URI of you web app:

<WebView
  source={{uri: 'https://www.google.com'}}
  onMessage={onMessage}
  ...
/>

Now it’s time to test the payment gateway. At this point, we’re logging the result from postMessage in the React Native app.

Click Pay Using PayPal to reveal the payment page. Enter your credentials and make payment:



After the payment is made successfully (or unsuccessfully), you'll see the result printed in the console:

You can alert the users about the status of the payment they just made by adding an alert in the onMessage function:

function onMessage(e) {
    ...
    let payment = JSON.parse(data);
    if (payment.status === 'COMPLETED') {
      alert('PAYMENT MADE SUCCESSFULLY!');
    } else {
      alert('PAYMENT FAILED. PLEASE TRY AGAIN.');
    }
}

Here’s the output:

The full code is as follows.

App.js (React my-paypal-web):

import React from "react";
import ReactDOM from "react-dom";
import "./App.css";
const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });
function App() {
  function _createOrder(data, actions) {
    return actions.order.create({
      purchase_units: [
        {
          amount: {
            value: "1",
          },
        },
      ],
    });
  }
  async function _onApprove(data, actions) {
    let order = await actions.order.capture();
    console.log(order);
    window.ReactNativeWebView &&
      window.ReactNativeWebView.postMessage(JSON.stringify(order));
    return order;
  }
  function _onError(err) {
    console.log(err);
    let errObj = {
      err: err,
      status: "FAILED",
    };
    window.ReactNativeWebView &&
      window.ReactNativeWebView.postMessage(JSON.stringify(errObj));
  }
  return (
    <div className="App">
      <PayPalButton
        createOrder={(data, actions) => _createOrder(data, actions)}
        onApprove={(data, actions) => _onApprove(data, actions)}
        onCancel={() => _onError("CANCELED")}
        onError={(err) => _onError("ERROE")}
      />
    </div>
  );
}
export default App;

App.js (React Native myPayPalApp):

import React, {useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Modal,
  ActivityIndicator,
} from 'react-native';
import {WebView} from 'react-native-webview';
import Feather from 'react-native-vector-icons/Feather';
const App = () => {
  const [showGateway, setShowGateway] = useState(false);
  const [prog, setProg] = useState(false);
  const [progClr, setProgClr] = useState('#000');
  function onMessage(e) {
    let data = e.nativeEvent.data;
    setShowGateway(false);
    console.log(data);
    let payment = JSON.parse(data);
    if (payment.status === 'COMPLETED') {
      alert('PAYMENT MADE SUCCESSFULLY!');
    } else {
      alert('PAYMENT FAILED. PLEASE TRY AGAIN.');
    }
  }
  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={styles.container}>
        <View style={styles.btnCon}>
          <TouchableOpacity
            style={styles.btn}
            onPress={() => setShowGateway(true)}>
            <Text style={styles.btnTxt}>Pay Using PayPal</Text>
          </TouchableOpacity>
        </View>
      </View>
      {showGateway ? (
        <Modal
          visible={showGateway}
          onDismiss={() => setShowGateway(false)}
          onRequestClose={() => setShowGateway(false)}
          animationType={'fade'}
          transparent>
          <View style={styles.webViewCon}>
            <View style={styles.wbHead}>
              <TouchableOpacity
                style={{padding: 13}}
                onPress={() => setShowGateway(false)}>
                <Feather name={'x'} size={24} />
              </TouchableOpacity>
              <Text
                style={{
                  flex: 1,
                  textAlign: 'center',
                  fontSize: 16,
                  fontWeight: 'bold',
                  color: '#00457C',
                }}>
                PayPal GateWay
              </Text>
              <View style={{padding: 13, opacity: prog ? 1 : 0}}>
                <ActivityIndicator size={24} color={progClr} />
              </View>
            </View>
            <WebView
              source={{uri: 'https://my-pay-web.web.app/'}}
              style={{flex: 1}}
              onLoadStart={() => {
                setProg(true);
                setProgClr('#000');
              }}
              onLoadProgress={() => {
                setProg(true);
                setProgClr('#00457C');
              }}
              onLoadEnd={() => {
                setProg(false);
              }}
              onLoad={() => {
                setProg(false);
              }}
              onMessage={onMessage}
            />
          </View>
        </Modal>
      ) : null}
    </SafeAreaView>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#fff',
  },
  btnCon: {
    height: 45,
    width: '70%',
    elevation: 1,
    backgroundColor: '#00457C',
    borderRadius: 3,
  },
  btn: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  btnTxt: {
    color: '#fff',
    fontSize: 18,
  },
  webViewCon: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  wbHead: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f9f9f9',
    zIndex: 25,
    elevation: 2,
  },
});
export default App;

Conclusion

If you’ve made it this far, congratulations — you’ve successfully set up your test payment gateway for React Native using PayPal. Although the code above should suffice for the basic payment system, you can change it according to your own needs. You can also refer to the official PayPal guide for further reference.


You can also checkout this post over Logrocket here.