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 in React Native 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.
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 account.
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.
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]¤cy=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¤cy=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.js
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.js
component. Just delete the content of the parent component and add the PayPalButton
. Your App.js
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
, 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:
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 Continue.
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
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 Modal
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
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;
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;
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.
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
8 Replies to "How to integrate PayPal payments with React Native"
I’m stuck at react-web, Im deployed but it’s doest show paypal buton, Im also try deploy with github but, its same. any help pls :<
Can we go live on stores performing payment using webview?
Yes you can
The onMessage method of the WebView does not receive any messages
How do i send the value i.e amount from react native instead a hard coded value in the react app
add query string parameter in WebView and read it in the App.js
You haven’t changed the google.com url to your firebase url.
Big thanks for breaking down this project nicely. I was intimidated but now I have a working payment gateway.