On this page
No Headings
Last updated: June 18, 2026
Deprecation notice: New integrations should use the PayPal JavaScript SDK v6.
This integration uses the PayPal JavaScript SDK v5, and it's to troubleshoot existing integrations only.
Make sure you've set up your development environment.
You can grab sample code from our:
Set up your HTML front end to integrate checkout payments.
Your app shows the PayPal checkout buttons. When a customer selects a button, your app calls server endpoints to create the order and capture payment.
This example uses an index.html file to show how to set up the front end to integrate payments.
The /src/index.html and /src/app.js files handle the client-side logic and define how the PayPal front-end components connect with the back end. Use these files to set up PayPal checkout using the JavaScript SDK v5 and handle the payer's interactions with the PayPal checkout button.
You'll need to save the index.html and app.js files in a folder named /src.
Include the <script> tag on any page that shows the PayPal buttons. This script will fetch all the necessary JavaScript to access the buttons on the window object.
Configure your script parameters:
client-id and specify which components you want to use. The SDK offers Buttons, Marks, Card Fields, and other components. This sample focuses on the buttons component.currency you want to use for pricing. For this example, we'll use USD. buyer-country and currency are only for use in sandbox testing. Do not use these in production.Pass values in disable-funding and enable-funding to control which funding sources to offer or exclude. For example, if you want to offer Venmo as a payment method, add enable-funding=venmo to your script tag. If you want to exclude credit card payments, add disable-funding=card to your script tag.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PayPal JS SDK v5 Standard Integration</title>
</head>
<body>
<div id="paypal-button-container"></div>
<p id="result-message"></p>
<!-- Initialize the JS-SDK -->
<script
src="https://www.paypal.com/sdk/js?client-id=test&buyer-country=US¤cy=USD&components=buttons&enable-funding=venmo,paylater,card"
data-sdk-integration-source="developer-studio"
></script>
<script src="app.js"></script>
</body>
</html>After setting up the SDK for your website, you need to render the buttons.
The paypal namespace has a Buttons function that initiates the callbacks needed to set up a payment.
The createOrder callback launches when the customer selects a payment button. The callback starts the order and returns an order ID. After the customer checks out using the PayPal pop-up, this order ID helps you to confirm when the payment is completed.
Completing the payment launches an onApprove callback. Use the onApprove response to update business logic, show a celebration page, or handle error responses.
To override the default style settings for your page, use a style object inside the Buttons component. You can lay out the buttons in a horizontal or vertical stack. You can also customize the buttons with different colors and shapes. Read more about how to customize your payment buttons in the style section of the JavaScript SDK v5 reference.
const paypalButtons = window.paypal.Buttons({
style: {
shape: "rect",
layout: "vertical",
color: "gold",
label: "paypal",
},
message: {
amount: 100,
},
async createOrder() {
try {
const response = await fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// use the "body" param to optionally pass additional order information
// like product ids and quantities
body: JSON.stringify({
cart: [{
id: "YOUR_PRODUCT_ID",
quantity: "YOUR_PRODUCT_QUANTITY",
}, ],
}),
});
const orderData = await response.json();
if (orderData.id) {
return orderData.id;
}
const errorDetail = orderData?.details?.[0];
const errorMessage = errorDetail ?
`${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})` :
JSON.stringify(orderData);
throw new Error(errorMessage);
} catch (error) {
console.error(error);
// resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
}
},
async onApprove(data, actions) {
try {
const response = await fetch(
`/api/orders/${data.orderID}/capture`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
const orderData = await response.json();
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show confirmation or thank you message
const errorDetail = orderData?.details?.[0];
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// recoverable state, per
// /checkout/handle-funding-failure/
return actions.restart();
} else if (errorDetail) {
// (2) Other non-recoverable errors -> Show a failure message
throw new Error(
`${errorDetail.description} (${orderData.debug_id})`
);
} else if (!orderData.purchase_units) {
throw new Error(JSON.stringify(orderData));
} else {
// (3) Successful transaction -> Show confirmation or thank you message
// Or go to another URL: actions.redirect('thank_you.html');
const transaction =
orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
orderData?.purchase_units?.[0]?.payments
?.authorizations?.[0];
resultMessage(
`Transaction ${transaction.status}: ${transaction.id}<br>
<br>See console for all available details`
);
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
}
} catch (error) {
console.error(error);
resultMessage(
`Sorry, your transaction could not be processed...<br><br>${error}`
);
}
},
});
paypalButtons.render("#paypal-button-container");
// Example function to show a result to the user. Your site's UI library can be used instead.
function resultMessage(message) {
const container = document.querySelector("#result-message");
container.innerHTML = message;
}Set up your React front end to integrate checkout payments.
Your app shows the PayPal checkout buttons. When a customer selects a button, your app calls server endpoints to create the order and capture payment.
This example uses a main.jsx file that handles the client-side logic and defines how the PayPal front-end components connect with the back end. Use this file to set up PayPal checkout using the JavaScript SDK v5 and handle the payer's interactions with the PayPal checkout button.
You'll need to save the main.jsx file in a folder named /client/react/src.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);Add PayPalScriptProvider from the @paypal/react-paypal-js library inside the <div className="App"> element. This provider loads the PayPal JS SDK and makes it available to all child components.
To render the buttons:
PayPalButtons inside the PayPalScriptProvider.createOrder fires when the buyer selects the button and starts the order. createOrder returns an order ID.onApprove fires when the buyer approves the transaction, and captures the order on the server. Use this callback to update your order state, handle errors, or redirect the buyer to a confirmation page.You can also configure the layout of the buttons with the style object inside the PayPalButtons component. See all the available style options in the PayPal JavaScript SDK v5 documentation.
This sample code enables Pay Later and credit and debit card buttons, but disables Venmo.
buyer-country and buyer-currency are for sandbox testing only. Do not use these in production.
import React, {
useState
} from "react";
import {
PayPalScriptProvider,
PayPalButtons
} from "@paypal/react-paypal-js";
// Renders errors or successful transactions on the screen.
function Message({
content
}) {
return < p > {
content
} < /p>;
}
function App() {
const initialOptions = {
"client-id": "test", // "test" loads the PayPal JS SDK in demo mode. Replace this with your sandbox client ID to test your sandbox app.
"enable-funding": "paylater,card",
"disable-funding": "venmo",
"buyer-country": "US",
currency: "USD",
"data-page-type": "product-details",
components: "buttons",
"data-sdk-integration-source": "developer-studio",
};
const [message, setMessage] = useState("");
return ( <
div className = "App" >
<
PayPalScriptProvider options = {
initialOptions
} >
<
PayPalButtons style = {
{
shape: "rect",
layout: "vertical",
color: "gold",
label: "paypal",
}
}
createOrder = {
async () => {
try {
const response = await fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// use the "body" param to optionally pass additional order information
// such as product IDs and quantities
body: JSON.stringify({
cart: [{
id: "YOUR_PRODUCT_ID",
quantity: "YOUR_PRODUCT_QUANTITY",
}, ],
}),
});
const orderData = await response.json();
if (orderData.id) {
return orderData.id;
} else {
const errorDetail = orderData?.details?.[0];
const errorMessage = errorDetail ?
`${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})` :
JSON.stringify(orderData);
throw new Error(errorMessage);
}
} catch (error) {
console.error(error);
setMessage(
`Could not initiate PayPal Checkout...${error}`
);
}
}
}
onApprove = {
async (data, actions) => {
try {
const response = await fetch(
`/api/orders/${data.orderID}/capture`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
const orderData = await response.json();
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show confirmation or thank you message
const errorDetail = orderData?.details?.[0];
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// recoverable state, per /checkout/handle-funding-failure/
return actions.restart();
} else if (errorDetail) {
// (2) Other non-recoverable errors -> Show a failure message
throw new Error(
`${errorDetail.description} (${orderData.debug_id})`
);
} else {
// (3) Successful transaction -> Show confirmation or thank you message
// Or go to another URL: actions.redirect('thank_you.html');
const transaction =
orderData.purchase_units[0].payments
.captures[0];
setMessage(
`Transaction ${transaction.status}: ${transaction.id}. See console for all available details`
);
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
}
} catch (error) {
console.error(error);
setMessage(
`Sorry, your transaction could not be processed...${error}`
);
}
}
}
/> <
/PayPalScriptProvider> <
Message content = {
message
}
/> <
/div>
);
}
export default App;The PayPal Server SDK provides integration access to PayPal REST APIs. The API endpoints are divided into distinct controllers:
Orders Controller: Orders API v2
Payments Controller: Payments API v2
Your app creates an order on the backend by calling to the ordersCreate method in the Orders Controller.
When the payer confirms the order, your app calls the ordersCapture method in the Orders Controller on the backend to move the money.
The sample integration uses the PayPal Server SDK to connect to the PayPal REST APIs. Use the server folder to set up the backend to integrate with the payments flow.
8080PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET as environment variables. The server side code is configured to fetch these values from the environment to authorize the calls to the PayPal REST APIs.Initialize the Server SDK client using OAuth 2.0 Client Credentials (PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET). The SDK automatically retrieves the OAuth token when any endpoint that requires OAuth 2.0 Client Credentials is invoked.
Endpoint: POST /v2/checkout/orders
You need a createOrder function to start a payment between a payer and a merchant.
Set up the createOrder function to make a request to the ordersCreate method in the Orders Controller and pass data from the cart object to calculate the purchase units for the order.
If you process payments that require Strong Customer Authentication, you need to provide additional context with payment indicators.
Endpoint: POST /v2/checkout/orders/{orderID}/capture
You need a captureOrder function to move money from the payer to the merchant.
Set up the captureOrder function to make a request to the ordersCapture method in the Orders Controller and pass the orderID generated from the Create Order step.
You need a handleResponse function to set up a listener that returns an HTTP status code from the API response.
handleResponse to make a POST call to the /api/orders endpoint and return an HTTP status code response.errorMessage object to show an error message when handleResponse returns an error code.import express from "express";
import "dotenv/config";
import {
ApiError,
CheckoutPaymentIntent,
Client,
Environment,
LogLevel,
OrdersController,
PaymentsController,
PaypalExperienceLandingPage,
PaypalExperienceUserAction,
ShippingPreference,
} from "@paypal/paypal-server-sdk";
import bodyParser from "body-parser";
const app = express();
app.use(bodyParser.json());
const {
PAYPAL_CLIENT_ID,
PAYPAL_CLIENT_SECRET,
PORT = 8080,
} = process.env;
const client = new Client({
clientCredentialsAuthCredentials: {
oAuthClientId: PAYPAL_CLIENT_ID,
oAuthClientSecret: PAYPAL_CLIENT_SECRET,
},
timeout: 0,
environment: Environment.Sandbox,
logging: {
logLevel: LogLevel.Info,
logRequest: {
logBody: true
},
logResponse: {
logHeaders: true
},
},
});
const ordersController = new OrdersController(client);
const paymentsController = new PaymentsController(client);
/**
* Create an order to start the transaction.
* @see /api/orders/v2/orders-create
*/
const createOrder = async (cart) => {
const collect = {
body: {
intent: "CAPTURE",
purchaseUnits: [{
amount: {
currencyCode: "CAD",
value: "100",
breakdown: {
itemTotal: {
currencyCode: "CAD",
value: "100",
},
},
},
// lookup item details in `cart` from database
items: [{
name: "T-Shirt",
unitAmount: {
currencyCode: "CAD",
value: "100",
},
quantity: "1",
description: "Super Fresh Shirt",
sku: "sku01",
}, ],
}, ],
},
prefer: "return=minimal",
};
try {
const {
body,
...httpResponse
} = await ordersController.createOrder(
collect
);
// Get more response info...
// const { statusCode, headers } = httpResponse;
return {
jsonResponse: JSON.parse(body),
httpStatusCode: httpResponse.statusCode,
};
} catch (error) {
if (error instanceof ApiError) {
// const { statusCode, headers } = error;
throw new Error(error.message);
}
}
};
// createOrder route
app.post("/api/orders", async (req, res) => {
try {
// use the cart information passed from the front-end to calculate the order amount detals
const {
cart
} = req.body;
const {
jsonResponse,
httpStatusCode
} = await createOrder(cart);
res.status(httpStatusCode).json(jsonResponse);
} catch (error) {
console.error("Failed to create order:", error);
res.status(500).json({
error: "Failed to create order."
});
}
});
/**
* Capture payment for the created order to complete the transaction.
* @see /api/orders/v2/orders-capture
*/
const captureOrder = async (orderID) => {
const collect = {
id: orderID,
prefer: "return=minimal",
};
try {
const {
body,
...httpResponse
} = await ordersController.captureOrder(
collect
);
// Get more response info...
// const { statusCode, headers } = httpResponse;
return {
jsonResponse: JSON.parse(body),
httpStatusCode: httpResponse.statusCode,
};
} catch (error) {
if (error instanceof ApiError) {
// const { statusCode, headers } = error;
throw new Error(error.message);
}
}
};
// captureOrder route
app.post("/api/orders/:orderID/capture", async (req, res) => {
try {
const {
orderID
} = req.params;
const {
jsonResponse,
httpStatusCode
} = await captureOrder(orderID);
res.status(httpStatusCode).json(jsonResponse);
} catch (error) {
console.error("Failed to create order:", error);
res.status(500).json({
error: "Failed to capture order."
});
}
});
app.listen(PORT, () => {
console.log(`Node server listening at http://localhost:${PORT}/`);
});Before going live, test your integration in the sandbox environment. Learn more about card testing, simulating successful payments using test card numbers and generating card error scenarios using rejection triggers.
Test the following use cases before going live:
Test a purchase as a payer:
Confirm the money reached the business account: