This assumes you have already have PayPal running in NodeJS on Docker.
- PayPal standard integration checkout using NodeJS on Docker
- PayPal advanced integration checkout using NodeJS on Docker
Let's say you have both the PayPal NodeJS and a Flask Docker container and you want to integrate your Flask app to use PayPal NodeJS 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 NodeJS 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 NodeJS 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 NodeJS 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 NodeJS 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 NodeJS 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 NodeJS 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