Bootstrap FreeKB - PayPal - Advanced integration checkout using Node.js and Flask on Docker
PayPal - Advanced integration checkout using Node.js and Flask on Docker

Updated:   |  PayPal articles

Before trying this in a Docker container, it's easier to try this out 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 Flask 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

 

Let's create a directory that will only contain the files we need.

mkdir /usr/local/paypal-advanced-integration-v2/flask

 

Copy the files and directories in /tmp/docs-examples/advanced-integration/v2 to the directory you created.

cp /tmp/docs-examples/advanced-integration/v2/server/python/* /usr/local/paypal-advanced-integration-v2/flask

 

Let's first set this up in the Sandbox environment.

  1. Go to https://developer.paypal.com/dashboard/applications/sandbox
  2. Select Create App and follow the prompts to create an app. You should get a Client ID and Secret.

/usr/local/paypal-advanced-integration-v2/flask/server.py will contain something like this. For the sake of this simple getting started article, let's update o_auth_client_id and o_auth_client_secret to have your Client ID and Secret and set Environment to Sandbox or Production.

Add the cors stuff to server.py. In this example, node.example.com is the hostname of the PayPal Node.js client which is why https://node.example.com is listed in origins.

import logging
import sys
from flask import Flask, request
from flask_cors import cross_origin
from paypalserversdk.http.auth.o_auth_2 import ClientCredentialsAuthCredentials
from paypalserversdk.logging.configuration.api_logging_configuration import LoggingConfiguration, RequestLoggingConfiguration, ResponseLoggingConfiguration
from paypalserversdk.paypal_serversdk_client import PaypalServersdkClient
from paypalserversdk.controllers.orders_controller import OrdersController
from paypalserversdk.models.amount_with_breakdown import AmountWithBreakdown
from paypalserversdk.models.checkout_payment_intent import CheckoutPaymentIntent
from paypalserversdk.models.order_request import OrderRequest
from paypalserversdk.models.purchase_unit_request import PurchaseUnitRequest
from paypalserversdk.api_helper import ApiHelper
from paypalserversdk.configuration import Environment

paypal_client: PaypalServersdkClient = PaypalServersdkClient(
    client_credentials_auth_credentials=ClientCredentialsAuthCredentials(
        o_auth_client_id='sfdgkljlasfkaslfkj2342342m34n3jkq4n234n234n24lk4l31ql4j34l1',
        o_auth_client_secret='dsgfkfjalsfkj2342334j234g234gj2h34g2jh4g23j4g23j4'
    ),
    environment=Environment.SANDBOX, # SANDBOX for api-m.sandbox.paypal.com or PRODUCTION for api-m.paypal.com
    logging_configuration=LoggingConfiguration(
        log_level=logging.INFO,
        mask_sensitive_headers=False,
        request_logging_config=RequestLoggingConfiguration(
            log_headers=True,
            log_body=True
        ),
        response_logging_config=ResponseLoggingConfiguration(
            log_headers=True,
            log_body=True
        )
    )
)

import sys
logger = logging.getLogger()
logger.setLevel(logging.INFO)
format = logging.Formatter(fmt="[%(asctime)s %(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(format)
logger.addHandler(consoleHandler)

@app.route('/api/orders', methods=['POST'])
@cross_origin(origins=['https://node.example.com'])
def create_order():
    request_body = request.get_json()
    logging.info(f"create_order function - request_body = {request_body}")
    order = orders_controller.orders_create({
      "body": OrderRequest(
        intent=CheckoutPaymentIntent.CAPTURE,
        purchase_units=[
          PurchaseUnitRequest(
             AmountWithBreakdown(
                 currency_code='USD',
                 value='0.01'
             ) 
          )
        ]
      ),
      "prefer": 'return=representation'
      }
    )
    logging.info(f"create_order function - order.body = {order.body}")
    return ApiHelper.json_serialize(order.body)
    
@app.route('/api/orders/<order_id>/capture', methods=['POST'])
@cross_origin(origins=['https://node.example.com'])
def capture_order(order_id):
    order = orders_controller.orders_capture({
        'id': order_id,
        'prefer': 'return=representation'
    })
    return ApiHelper.json_serialize(order.body)    

 

Add flask-cors to requirements.txt so that you don't get something like "Access to fetch has been blocked by CORS policy". Check out my article FreeKB - Flask - Resolve "Access to fetch has been blocked by CORS policy".

flask==3.0.3
flask-cors==5.0.0
paypal-server-sdk==0.6.0

 

Create a file named Dockerfile.

touch /usr/local/paypal-advanced-integration-v2/flask/Dockerfile

 

The Dockerfile should have something like this.

FROM tiangolo/uwsgi-nginx-flask:python3.11
RUN apt-get update -y
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/requirements.txt
RUN pip install -r /usr/requirements.txt

 

Move into the directory that contains the Dockerfile.

cd /usr/local/paypal-advanced-integration-v2/flask/

 

Use the docker build command to create the Flask PayPal Server image using the Dockerfile.

sudo docker build --file Dockerfile --tag paypal-flask-server:python3.11 .

 

The docker images command should return something like this.

]$ sudo docker images
REPOSITORY                                                        TAG             IMAGE ID       CREATED          SIZE
paypal-flask-server                                               python3.11      7e0f39b56068   12 seconds ago   1GB
tiangolo/uwsgi-nginx-flask                                        python3.11      2e5915aa7411   5 days ago       945MB

 

The docker run command can be used to create and start a Docker container from the image.

sudo docker run \
--name paypal-nodejs \
--publish 0.0.0.0:12345:80 \
--detach \
--volume /usr/local/paypal-advanced-integration-v2/flask/server.py:/app/main.py \
paypal-flask-server:python3.11

 

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
a9c9e5d2e1f1   paypal-flask-server:python3.11     "/entrypoint.sh /sta…"   11 seconds ago   Up 10 seconds       0.0.0.0:12345->80/tcp     paypal-flask

 

The docker logs command should return something like this.

~]$ sudo docker logs paypal-flask-server
2025-02-16 10:18:16,891 INFO success: quit_on_failure entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

 

The pip list command run in the container should include both Flask and paypal-server-sdk.

]$ sudo docker exec paypal-flask-server pip list
Package                          Version
-------------------------------- -----------
apimatic-core                    0.2.18
apimatic-core-interfaces         0.1.6
apimatic-requests-client-adapter 0.1.7
blinker                          1.9.0
CacheControl                     0.12.14
certifi                          2025.1.31
charset-normalizer               3.4.1
click                            8.1.8
Flask                            3.0.3
Flask-Cors                       5.0.0
idna                             3.10
itsdangerous                     2.2.0
Jinja2                           3.1.5
jsonpickle                       3.3.0
jsonpointer                      2.4
MarkupSafe                       3.0.2
msgpack                          1.1.0
paypal-server-sdk                0.6.0
pip                              25.0.1
python-dateutil                  2.9.0.post0
requests                         2.32.3
setuptools                       75.8.0
six                              1.17.0
urllib3                          2.3.0
uWSGI                            2.0.28
Werkzeug                         3.1.3
wheel                            0.45.1

 

Notice in this example the that docker run command had --publish 0.0.0.0:12345:80. This means the Docker system is listening for connections on port 12345. You will need to ensure connections are allowed on port 12345. For example, if running on Amazon Web Services you would all port 12345 in the Security Group. Go to http://<your Docker servers hostname or IP address>:12345 and the following should be displayed. Hooray, you can submit a GET request to the / route and get a response!

 

 

Now onto the Node.js client. The createOrderCallback and onApproveCallback functions in /usr/local/paypal-advanced-integration-v2/client/html/checker.js fetch /api/orders and /api/orders/${data.orderID}/capture. These endpoints are in Flask server.py. The fetch will need to be updated to include the full path to these endpoints. For example, if Flask server.py is using foo.example.com as it's hostname.

const response = await fetch(`https://foo.example.com/api/orders`)

 

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,
  );

 

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.

 

 




Did you find this article helpful?

If so, consider buying me a coffee over at Buy Me A Coffee



Comments


Add a Comment


Please enter dea690 in the box below so that we can be sure you are a human.