
Before trying this in a Docker container, it's easier to try this out using Node.js on a Linux server. Check out my article PayPal advanced integration checkout using Node.js.
If you have not yet followed my article PayPal standard integration checkout using Node.js on Docker, you'll probably want to go with the standard integration before trying this advanced integration.
This article describes the design where an HTML page running on an HTTP web server uses the PayPal Node.js client to present the PayPal checkout form. Upon entering data into the PayPal checkout form a request is made to a PayPal Node.js server which returns a response to the PayPal Node.js client and the HTTP web server does something with the response, such as saying "order successfully placed.

Let's say your Docker server is a Linux system. Move into your /tmp directory and clone the https://github.com/paypal-examples/docs-examples.git repository.
~]$ cd /tmp
~]$ git clone https://github.com/paypal-examples/docs-examples.git
Create the /usr/local/paypal-advanced-integration-v2 directory.
mkdir /usr/local/paypal-advanced-integration-v2
Copy the files and directories in /tmp/docs-examples/advanced-integration/v2 to /usr/local/paypal-advanced-integration-v2.
cp -R /tmp/docs-examples/advanced-integration/v2/* /usr/local/paypal-advanced-integration-v2
/usr/local/paypal-advanced-integration-v2/server/server.js will set the base constant to "https://api-m.sandbox.paypal.com" or "https://api-m.paypal.com". Let's comment out the base constant.
#const base = "https://api-m.sandbox.paypal.com";
/usr/local/paypal-advanced-integration-v2/server/server.js should have 3 references to the base constant.
const response = await fetch(`${base}/v1/oauth2/token`, {
const url = `${base}/v2/checkout/orders`;
const url = `${base}/v2/checkout/orders/${orderID}/capture`;
Let's update the lower case base constant to upper case.
const response = await fetch(`${BASE}/v1/oauth2/token`, {
const url = `${BASE}/v2/checkout/orders`;
const url = `${BASE}/v2/checkout/orders/${orderID}/capture`;
/usr/local/paypal-advanced-integration-v2/server/server.js should have something like this, a $100.00 USD currency charge.
value: "100.00",
Let's change this to CURRENCY_VALUE.
value: CURRENCY_VALUE,
Let's add the following constant to /usr/local/paypal-advanced-integration-v2/server/server.js.
const { BASE, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT, CURRENCY_VALUE } = process.env;
Let's first set this up in the Sandbox environment.
- Go to https://developer.paypal.com/dashboard/applications/sandbox
- Select Create App and follow the prompts to create an app. You should get a Client ID and Secret.
Rename /usr/local/paypal-advanced-integration-v2/.env.example to .env and update .env to have your apps Client ID and Secret. as well as PORT, CURRENCY_VALUE, and BASE. This make it much cleaner if we need to toggle between sandbox and live as we only need to update the .env file.
PORT = 8888
CURRENCY_VALUE = "1.00"
# SANDBOX
#BASE="https://api-m.sandbox.paypal.com"
#PAYPAL_CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
#PAYPAL_CLIENT_SECRET="YOUR_SECRET_GOES_HERE"
# LIVE
BASE="https://api-m.paypal.com"
PAYPAL_CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
PAYPAL_CLIENT_SECRET="YOUR_SECRET_GOES_HERE"
By default, your sandbox app should be setup with support for the advanced integration.
However, your live app may not be setup with support for the advanced integration. I just had to click on the Learn More link and follow the prompts to setup my live app with support for the advanced integration.
By default, /usr/local/paypal-advanced-integration-v2/server/server.js will have USD 100.00 (e.g. $100.00). You probably want to change this to some other value.
const createOrder = async (cart) => {
console.log(
"shopping cart information passed from the frontend createOrder() callback:",
cart,
);
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders`;
const payload = {
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "100.00",
},
},
],
};
Add "cors" to the dependencies in /usr/local/paypal-advanced-integration-v2/server/package.json.
{
"name": "paypal-advanced-integration",
"description": "Sample Node.js web app to integrate PayPal Advanced Checkout for online payments",
"version": "1.0.0",
"main": "server/server.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server/server.js",
"format": "npx prettier --write **/*.{js,md}",
"format:check": "npx prettier --check **/*.{js,md}",
"lint": "npx eslint server/*.js --env=node && npx eslint client/*.js --env=browser"
},
"license": "Apache-2.0",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
While not necessary, I would add console.log lines to /usr/local/paypal-advanced-integration-v2/client/checkout.js, something like this, as this makes it much easier to understand what is happening and to troubleshoot problems.
async function createOrderCallback() {
console.log(`This is the beginning of the createOrderCallback function`);
Then when pull up the app in your web browser, you should see the console.log events in the developer (f12) console.
Most importantly, I would add the following console.log lines to /usr/local/paypal-advanced-integration-v2/client/checkout.js so that you will know if the if cardField.isEligible() statement evaluated to true or false.
if (cardField.isEligible()) {
console.log(`The if cardField.isEligible() statement evaluated to true`);
. . .
} else {
console.log(`The if cardField.isEligible() statement did NOT evaluate to true`);
console.log(`cardField = ${JSON.stringify(cardField)}`);
. . .
}
Create a file named Dockerfile in the /usr/local/paypal-advanced-integration-v2 directory.
touch /usr/local/paypal-advanced-integration-v2/Dockerfile
The Dockerfile should have something like this.
- Use RUN npm install when creating the sandbox container
- Use RUN npm install --omit=dev when creating the production container
FROM node:22-alpine
WORKDIR /src
COPY package.json /src/
RUN npm install
Move into the directory that contains the Dockerfile.
cd /usr/local/paypal-advanced-integration-v2
Use the docker build command to create the Node.js image using the Dockerfile.
sudo docker build --file Dockerfile --tag paypal-nodejs:22-alpine .
The docker images command should return something like this.
~]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
paypal-nodejs 18-alpine 824127ec51d1 4 seconds ago 154MB
node 18-alpine 24d8fcd7167f 3 weeks ago 132MB
The docker run command can be used to create and start a Docker container from the Node.js image.
sudo docker run \
--name paypal-nodejs \
--publish 0.0.0.0:8888:8888 \
--detach \
--volume /usr/local/paypal-advanced-integration-v2/.env:/src/.env \
--volume /usr/local/paypal-advanced-integration-v2/client/checkout.js:/src/client/checkout.js \
--volume /usr/local/paypal-advanced-integration-v2/server/server.js:/src/server/server.js \
--volume /usr/local/paypal-advanced-integration-v2/server/views/checkout.ejs:/src/server/views/checkout.ejs \
paypal-nodejs:18-alpine \
npm start
The docker container ls command should show the container is up and running.
~]$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c19d5eab9a75 paypal-nodejs:18-alpine "docker-entrypoint.s…" 2 weeks ago Up 2 weeks 0.0.0.0:8888->8888/tcp paypal-nodejs
The docker logs command should return something like this.
~]$ sudo docker logs paypal-nodejs
> paypal-advanced-integration@1.0.0 start
> nodemon server/server.js
[nodemon] 3.1.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server/server.js`
Node server listening at http://localhost:8888/
The npm list command run in the container should return the dependencies in package.json.
]$ sudo docker exec paypal-nodejs npm list
paypal-advanced-integration@1.0.0 /src
+-- cors@2.8.5
+-- dotenv@16.4.5
+-- ejs@3.1.10
+-- express@4.19.2
+-- node-fetch@3.3.2
`-- nodemon@3.1.4
Go to http://<your Docker servers hostname or IP address>:8888/. If something like this is displayed, this means render("#paypal-button-container") in /src/client/checkout.js in the Docker container was rendered. /src/server/views/checkout.ejs in the Docker container has <div id="paypal-button-container" class="paypal-button-container"></div> to render paypal-button-container.
On the other hand, if something like this should be displayed, then this should mean that cardField.isEligible() in /src/client/checkout.js in the Docker container evaluated to true and the advanced integration view has been rendered.
If you don't want the 2 yellow PayPal buttons to be displayed, check out my article Remove yellow PayPal buttons from PayPal advanced integration using Node.js.
Viewing the page source should have something like this, which is your /src/server/views/checkout.ejs file in the Docker container.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css"
href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css" />
<title>PayPal JS SDK Advanced Integration - Checkout Flow</title>
</head>
<body>
<div id="paypal-button-container" class="paypal-button-container"></div>
<div id="card-form" class="card_container">
<div id="card-name-field-container"></div>
<div id="card-number-field-container"></div>
<div id="card-expiry-field-container"></div>
<div id="card-cvv-field-container"></div>
<button id="multi-card-field-button" type="button">Pay now with Card</button>
</div>
<p id="result-message"></p>
<script src="https://www.paypal.com/sdk/js?components=buttons,card-fields&client-id=abcdefg123456789abcdefg123456789abcdefg123456789"></script>
<script src="checkout.js"></script>
</body>
</html>
Let's say you want to customize the placeholder text, perhaps something like this, check out my article Customize PayPal advanced integration checkout input form placeholder using Node.js.
Submit a payment and something like "Transaction COMPLETED" should be displayed.
In https://developer.paypal.com/dashboard/applications/sandbox, on the Event Logs tab, there should be 201 OK events for the completed transation.
And the docker logs command should have something like this.
~]$ sudo docker logs paypal-nodejs2
> paypal-advanced-integration@1.0.0 start
> nodemon server/server.js
[nodemon] 3.1.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server/server.js`
Node server listening at http://localhost:8888/
shopping cart information passed from the frontend createOrder() callback: [ { id: 'YOUR_PRODUCT_ID', quantity: 'YOUR_PRODUCT_QUANTITY' } ]
/usr/local/paypal-advanced-integration-v2/server/server.js has the console.log that appends the event to the log.
const createOrder = async (cart) => {
console.log(
"shopping cart information passed from the frontend createOrder() callback:",
cart,
);
Did you find this article helpful?
If so, consider buying me a coffee over at