How to integrate PayPal in React
Integrating PayPal in React is straightforward with the official @paypal/react-paypal-js package, which renders PayPal’s smart payment buttons and handles the payment flow without requiring you to build custom UI.
As the creator of CoreUI, I’ve implemented PayPal payments in several e-commerce applications alongside Stripe to maximize payment method coverage.
The correct approach creates the PayPal order on your server to keep credentials secure, then confirms the capture server-side as well.
Never process order creation or capture entirely on the frontend — always validate and confirm on your server.
Install the PayPal React SDK.
npm install @paypal/react-paypal-js
Create the PayPal order on your server (Node.js example):
// server: POST /api/paypal/create-order
import fetch from 'node-fetch'
const PAYPAL_API = process.env.NODE_ENV === 'production'
? 'https://api.paypal.com'
: 'https://api.sandbox.paypal.com'
async function getAccessToken() {
const credentials = Buffer.from(
`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
).toString('base64')
const res = await fetch(`${PAYPAL_API}/v1/oauth2/token`, {
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials'
})
const { access_token } = await res.json()
return access_token
}
app.post('/api/paypal/create-order', async (req, res) => {
const { amount } = req.body
const token = await getAccessToken()
const response = await fetch(`${PAYPAL_API}/v2/checkout/orders`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
intent: 'CAPTURE',
purchase_units: [{
amount: { currency_code: 'USD', value: amount.toFixed(2) }
}]
})
})
const order = await response.json()
res.json({ id: order.id })
})
The server fetches an access token using your Client ID and Secret. The order is created server-side so credentials never reach the browser. The response returns only the order id to the client.
The PayPal Button Component
Render PayPal’s hosted button UI.
// PayPalCheckout.jsx
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'
export function PayPalCheckout({ amount, onSuccess }) {
return (
<PayPalScriptProvider options={{
'client-id': import.meta.env.VITE_PAYPAL_CLIENT_ID,
currency: 'USD'
}}>
<PayPalButtons
style={{ layout: 'vertical', color: 'gold' }}
createOrder={async () => {
const res = await fetch('/api/paypal/create-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount })
})
const { id } = await res.json()
return id
}}
onApprove={async (data) => {
const res = await fetch(`/api/paypal/capture-order/${data.orderID}`, {
method: 'POST'
})
const details = await res.json()
if (details.status === 'COMPLETED') {
onSuccess(details)
}
}}
onError={(err) => console.error('PayPal error:', err)}
/>
</PayPalScriptProvider>
)
}
createOrder calls your server to create the PayPal order and returns the order ID. onApprove is called after the user approves payment in PayPal’s popup — it calls your server to capture the payment. PayPal’s UI (the smart buttons, login, and review screens) is entirely hosted by PayPal.
Capturing the Payment Server-Side
// server: POST /api/paypal/capture-order/:orderId
app.post('/api/paypal/capture-order/:orderId', async (req, res) => {
const token = await getAccessToken()
const { orderId } = req.params
const response = await fetch(
`${PAYPAL_API}/v2/checkout/orders/${orderId}/capture`,
{
method: 'POST',
headers: { Authorization: `Bearer ${token}` }
}
)
const details = await res.json()
// Save order to your database here
res.json(details)
})
Always capture on the server. Capturing client-side would mean trusting the client’s claim that payment was approved, which is a security vulnerability.
Best Practice Note
This is the same PayPal integration pattern referenced in CoreUI e-commerce templates. Use PayPal’s sandbox environment for testing — set PAYPAL_API to https://api.sandbox.paypal.com and use sandbox credentials. For side-by-side Stripe and PayPal options, render both payment methods and let users choose. See how to integrate Stripe in React for the Stripe alternative.



