
This assumes you have already have PayPal running in Node.js on Docker.
- PayPal standard integration checkout using Node.js on Docker
- PayPal advanced integration checkout using Node.js on Docker
Let's say you have both the PayPal Node.js and a Flask Docker container and you want to integrate your Flask app to use PayPal Node.js for accepting payments.
]$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2e76449588fc paypal-nodejs:latest "docker-entrypoint.s…" 24 minutes ago Up 7 minutes 0.0.0.0:8888->8888/tcp paypal-nodejs
1f151892d439 tiangolo/uwsgi-nginx-flask:latest "/entrypoint.sh /sta…" 2 days ago Up 2 days 0.0.0.0:80->80/tcp, 443/tcp flask
For example, perhaps you want to have something like this on some of your Flask apps HTML pages.
The HTML page that is used to return the PayPal submit payment buttons runs in the Node.js Docker container. For example, if you are implementing v2 of the advanced integration, then https://github.com/paypal-examples/docs-examples/blob/main/advanced-integration/v2/server/views/checkout.ejs has the HTML. In your PayPal Node.js Docker container, the checkout.ejs file should be located at /src/server/views/checkout.ejs.
~]$ sudo docker exec paypal-nodejs ls -l /src/server/views
total 4
-rw-r--r-- 1 node node 1036 Mar 11 05:28 checkout.ejs
Let's take this HTML and put it in our Flask app. For example, let's say your Flask app has the following structure.
├── main.py
├── my-project (directory)
│ ├── __init__.py
│ ├── views.py
│ ├── templates (directory)
│ │ ├── base.html
│ │ ├── demo.html
Let's take the HTML from /src/server/views/checkout.ejs and put it in demo.html in our Flask app. The HTML in checkout.ejs has <script src="checkout.js"></script> and <script src="https://www.paypal.com/sdk/js?components=buttons,card-fields&client-id=<%= clientId %>"></script>. We will need to modify these to include the full URL of our PayPal Node.js Docker container and our client-id.
<script src="https://www.paypal.com/sdk/js?components=buttons,card-fields&client-id=abcdefg123456789"></script>
<script src="https://server1.example.com:8888/checkout.js"></script>
/src/client/checkout.js in your PayPal Node.js Docker container should have the following.
const response = await fetch("/api/orders", {
const response = await fetch(`/api/orders/${data.orderID}/capture`, {
This will cause fetch requests to be submitted to <the base URL of your Flask app>/api/orders. For example, if running your Flask app in VSCode, then the base URL of your Flask app is probably http://127.0.0.1:5000. Or, if running your Flask app in a Docker container, then the base URL of your Flask app is probably being returned by DNS, something like http://my-flask-app. If running your Flask app in VSCode, http://127.0.0.1:5000/api/orders will probably cause 405 (method not allowed) to be returned, which you should be able to see in your web browsers developer console (f12).
Let's update /src/client/checkout.js to have the base URL of our PayPal Node.js Docker container.
const base = "http://server1.example.net:8888"
const response = await fetch(`${base}/api/orders`, {
const response = await fetch(`${base}/api/orders/${data.orderID}/capture`, {
I also like to add console.log statements so that my web browser developer console has additional events, which are super helpful to see what's going on under the hood.
async function createOrderCallback() {
console.log('this is the beginning of the createOrderCallback function in checkout.js');
Let's update Node.js package.json to contains cors. This may be needed so that we don't get something like "access to fetch at 'http://docker.example.com:8888/my-server/create-paypal-order' has been blocked by CORS policy.
{
"name": "@paypalcorp/standard-integration",
"version": "1.0.0",
"main": "paypal-api.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "Apache-2.0",
"description": "",
"dependencies": {
"cors": "^2.8.5", <- ADD CORS
"dotenv": "^16.0.0",
"express": "^4.17.3",
"node-fetch": "^3.2.1"
}
}
Let's also update /src/server/server.js to
- import cors
- use cors
- comment out app.set view engine and views
import express from "express";
import fetch from "node-fetch";
import "dotenv/config";
import cors from "cors"; <- IMPORT CORS
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env;
const base = "https://api-m.sandbox.paypal.com";
const app = express();
//app.set("view engine", "ejs");
//app.set("views", "./server/views");
app.use(express.static("client"));
app.use(express.json());
app.use(cors()); <- USE CORS
Also update /src/server/server.js with header "Access-Control-Allow-Origin": "*".
export async function createOrder() {
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders`;
const response = await fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
"Access-Control-Allow-Origin": "*" <- ADD THIS
},
Last but not least, let's update /src/client/checkout.js to redirect to a different Flask HTML page based on whether or not result-message does or does not have "Transaction COMPLETED".
const flaskbase = "http://127.0.0.1:5000"
function resultMessage(message) {
console.log(`${timestamp()} this is the beginning of the resultMessage function in ${base}/checkout.js`);
const container = document.querySelector("#result-message");
container.innerHTML = message;
console.log(`${timestamp()} message = ${message}`);
if (message.includes("Transaction COMPLETED")) {
console.log(`${timestamp()} message includes Transaction COMPLETED - redirecting to ${flask_base}/order?status=success`)
document.location.href = `${flask_base}/order?status=COMPLETED`;
} else {
console.log(`${timestamp()} message does not include Transaction COMPLETED - redirecting to ${flask_base}/order?status=failed`)
document.location.href = `${flask_base}/order?status=failed`;
}
}
Pull up your HTML page using your Flask app and you should get something like this.
Did you find this article helpful?
If so, consider buying me a coffee over at