Integrate Expanded Checkout for partners

DocsBetaAdvancedLast updated: February 5th 2025, @ 12:45:14 pm


How it works

Your payers get a checkout flow that includes PayPal payment buttons and customized credit card fields that handle input from the following payment fields:

  • Card number
  • CVV
  • Expiration date
  • Optional: Cardholder name

Know before you code

  • Required: Get the following sandbox account information from the Developer Dashboard:
  • This client-side and server-side integration uses the following:
    • PayPal JavaScript SDK
    • Orders REST API
    • Use Postman to explore and test PayPal APIs Run in Postman

1. Set up account to accept card payments

Before you can accept card payments on your website, set up your sandbox business account to accept card payments as follows:

  1. Log into the PayPal Developer Dashboard, go to My Apps & Credentials > Sandbox > REST API apps, and select the name of your app.
  2. Go to Sandbox App Settings > App feature options > Accept payments and select Advanced options.
  3. Select the Advanced Credit and Debit Card Payments checkbox and select Save.
  4. If you created a sandbox business account through sandbox.paypal.com, and the advanced credit and debit card payments status for the account is disabled, complete the sandbox onboarding steps.

2. Initialize JavaScript SDK

Add the JavaScript SDK to your web page and include the following:

  • Your app's client ID.
  • A div to render the PayPal buttons.
  • A div to render each of the card fields.

In the included JavaScript file, there are reference routes on the server that you’ll add in the next step.

Tip: Test your PayPal button transactions in the developer dashboard by creating sandbox accounts.

3. Adding PayPal buttons and card fields

This integration includes a full stack Node.js example. The checkout.ejs, app.js, and server.js file samples show how to render the PayPal buttons and the card fields component:

  • Use PayPal buttons to process branded payments.
  • Use card fields to process unbranded payments.

checkout.ejs

<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- To be replaced with your own stylesheet -->
  <link rel="stylesheet" type="text/css" href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css"/>
  <!-- Express fills in the clientId and clientToken variables -->
  <script
    src="https://www.paypal.com/sdk/js?components=buttons,card-fields&client-id=<%= clientId %>&merchant-id=<%= merchantId %>"></script>
</head>
<body>
    <div id="paypal-button-container" class="paypal-button-container"></div>
        <div id="checkout-form">
         <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 Multi Card Fields</button>
        </div>
    <script src="app.js"></script>
  </body>
</html>

app.js

// Render the button component
paypal
  .Buttons({
    // Sets up the transaction when a payment button is clicked
    createOrder: function (data, actions) {
      return fetch("/api/orders", {
        method: "POST",
        // use the "body" param to optionally pass additional order information
        // like product ids or amount
      })
        .then((response) => response.json())
        .then((order) => order.id);
    },
    // Finalize the transaction after payer approval
    onApprove: function (data, actions) {
      return fetch(`/api/orders/${data.orderID}/capture`, {
        method: "POST",
      })
        .then((response) => response.json())
        .then((orderData) => {
          // Successful capture! For dev/demo purposes:
          console.log( "Capture result", orderData, JSON.stringify(orderData, null, 2));
          var transaction = orderData.purchase_units[0].payments.captures[0];
          alert(`Transaction ${transaction.status}: ${transaction.id}

            See console for all available details
          `);
          // When ready to go live, remove the alert and show a success message within this page. For example:
          // var element = document.getElementById('paypal-button-container');
          // element.innerHTML = '<h3>Thank you for your payment!</h3>';
          // Or go to another URL:  actions.redirect('thank_you.html');
        });
    },
    onError: function (error) {
        // Do something with the error from the SDK
    }
  })
  .render("#paypal-button-container");

// Create the Card Fields Component and define callbacks
const cardField = paypal.CardFields({
    style: styleObject,
    createOrder: function (data, actions) {
        return fetch("/api/paypal/order/create/", {
        method: "post",
        })
        .then((res) => {
            return res.json();
        })
        .then((orderData) => {
            return orderData.id;
        });
    },
    onApprove: function (data, actions) {
        const { orderID } = data;
        return fetch(`/api/paypal/orders/${orderID}/capture/`, {
        method: "post",
        })
        .then((res) => {
            return res.json();
        })
        .then((orderData) => {
            // Redirect to success page
        });
    },
    onError: function (error) {
        // Do something with the error from the SDK
    },
    onCancel: function () {
      //Handle customer closing 3DS verification modal
    }

});

// Render each field after checking for eligibility
if (cardField.isEligible()) {
    const nameField = cardField.NameField();
    nameField.render('#card-name-field-container');

    const numberField = cardField.NumberField();
    numberField.render('#card-number-field-container');

    const cvvField = cardField.CVVField();
    cvvField.render('#card-cvv-field-container');

    const expiryField = cardField.ExpiryField();
    expiryField.render('#card-expiry-field-container');

    // Add click listener to submit button and call the submit function on the CardField component
    document.getElementById("multi-card-field-button").addEventListener("click", () => {
        cardField
        .submit()
        .then(() => {
            // Handle a successful payment
        });
    });
}

server.js

app.get("/", async (req, res) => {
  const clientId = process.env.CLIENT_ID;
  res.render("checkout", { clientId });
});

Modify the code

This section explains how to customize the PayPal buttons and card fields for your integration.

PayPal buttons

  1. Copy the complete sample code from the GitHub repo.
  2. The CSS file in the head section is a sample for demo purposes. Instead, you should use styles that align with your brand using the CSS properties supported by this integration.
  3. Optional: Customize JavaScript configurations, such as currency and intent.
  4. Optional: Change the layout, width, height, and outer styling of the PayPal buttons, such as border, box-shadow, and background.

Card fields

  1. Copy and paste both examples of card field style objects into your existing checkout.ejs and app.js files.
  2. Include the required card form elements:card number, security code, and expiration date. To learn about the available card form elements, see Card fields.
  3. Complete sample code is available from the GitHub repo.
  4. Optional: Change the layout, width, height and outer styling of the card fields, such as border, box-shadow, and background. You can modify the elements you supply as containers.

4. Call the Orders API for PayPal buttons and card fields

Create API endpoints on your server that communicate with the Orders v2 API to create an order and capture payment for an order.

Important: If you process payments that require Strong Customer Authentication, you need to provide additional context with payment indicators.

Server-side example (Node.js)

The following code samples show how to integrate the back-end code for Expanded Checkout by adding routes to an Express server to create orders and capture payments using the Orders v2 API. Find the complete sample code in the GitHub repo:

server.js

import * as paypal from "./paypal-api.js";

// create order
app.post("/api/orders", async (req, res) => {
  const order = await paypal.createOrder();
  res.json(order);
});

// capture payment
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const { orderID } = req.params;
  const captureData = await paypal.capturePayment(orderID);
  res.json(captureData);
});

paypal-api.js

// create an order
export async function createOrder() {
  const purchaseAmount = "100.00"; // TODO: pull amount from a database or session
  const accessToken = await generateAccessToken();
  const url = `${baseURL.sandbox}/v2/checkout/orders`;
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      intent: "CAPTURE",
      purchase_units: [
        {
          amount: {
            currency_code: "USD",
            value: purchaseAmount
          },
        },
      ],
    }),
  });
  const data = await response.json();
  return data;
}

// capture payment for an order
export async function capturePayment(orderId) {
  const accessToken = await generateAccessToken();
  const url = `${baseURL.sandbox}/v2/checkout/orders/${orderId}/capture`;
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
  });
  const data = await response.json();
  return data;
}

5. Capture order

Set up your server-side code to capture the order when a payer uses a credit or debit card. Using server-side code keeps you from exposing your access token on the client.

Merchant

This code sample shows how to set up the capture request if you are a merchant.

curl -v -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders/ORDER-ID/capture \
-H "Content-Type: application/json" \
-H 'Authorization: Bearer ACCESS-TOKEN' \
-H 'PayPal-Partner-Attribution-Id: BN-CODE'\
-H 'PayPal-Auth-Assertion: AUTH-ASSERTION-TOKEN'\
-H 'Accept-Language: en_US' \
  1. Copy the sample request code.
  2. Change ORDER-ID to the Order ID or Express Checkout (EC) token that you used to set up the transaction in your createOrder function.
  3. Change ACCESS_TOKEN to your access token.
  4. Change BN-CODE to your PayPal Partner Attribution ID.
  5. Change the AUTH-ASSERTION-TOKEN to your PayPal-Auth-Assertion token.

Platform

This code sample shows how to set up the capture request on a platform.

curl -v -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders/ORDER-ID/capture \
-H "Content-Type: application/json" \
-H 'Authorization: Bearer ACCESS-TOKEN' \
-H 'PayPal-Partner-Attribution-Id: BN-CODE'\
-H 'Accept-Language: en_US' \
  1. Copy the sample request code.
  2. Change ORDER-ID to the Order ID or EC token that you used to set up the transaction in your createOrder function.
  3. Change ACCESS_TOKEN to your access token.
  4. Change BN-CODE to your PayPal Partner Attribution ID.

Full example

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Checkout Page</title>
    </head>
    <body>
        <div id="checkout-form">
         <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 Fields</button>
        </div>
    </body>
      <script src="https://www.paypal.com/sdk/js?client-id=<your-client-id>&components=card-fields"></script>
      <script>
       // Custom Styles Object (optional)
        const styleObject = {
            input: {
                "font-size": "16 px",
                "font-family": "monospace",
                "font-weight": "lighter",
                color: "blue",
            },
            ".invalid": {
            color: "purple",
            },
            ":hover": {
                color: "orange",
            },
            ".purple": {
                color: "purple",
            },
        };
        // Create the Card Fields Component and define callbacks
        const cardField = paypal.CardFields({
            style: styleObject,
            createOrder: function (data, actions) {
                return fetch("your-server/api/paypal/order/create/", {
                method: "post",
                })
                .then((res) => {
                    return res.json();
                })
                .then((orderData) => {
                    return orderData.id;
                });
            },
            onApprove: function (data, actions) {
                const { orderID } = data;
                return fetch(`your-server/api/paypal/orders/${orderID}/capture/`, {
                method: "post",
                })
                .then((res) => {
                    return res.json();
                })
                .then((orderData) => {
                    // Redirect to success page
                });
            },
            inputEvents: {
                onChange: function (data) {
                    // Handle a change event in any of the fields
                },
                onFocus: function(data) {
                    // Handle a focus event in any of the fields
                },
                onBlur: function(data) {
                    // Handle a blur event in any of the fields
                },
                onInputSubmitRequest: function(data) {
                    // Handle an attempt to submit the entire card form
                    // when a buyer hits the enter key (or mobile equivalent)
                    // while focusing any of the fields
                }
            },
        });
        // Define Container for each field and the submit button
        const cardNameContainer = document.getElementById("card-name-field-container"); // Optional field
        const cardNumberContainer = document.getElementById("card-number-field-container");
        const cardCvvContainer = document.getElementById("card-cvv-field-container");
        const cardExpiryContainer = document.getElementById("card-expiry-field-container");

        const multiCardFieldButton = document.getElementById("multi-card-field-button");

        // Render each field after checking for eligibility
        if (cardField.isEligible()) {

            const nameField = cardField.NameField();
            nameField.render(cardNameContainer);

            const numberField = cardField.NumberField();
            numberField.render(cardNumberContainer);

            const cvvField = cardField.CVVField();
            cvvField.render(cardCvvContainer);

            const expiryField = cardField.ExpiryField();
            expiryField.render(cardExpiryContainer);

            // Add click listener to submit button and call the submit function on the CardField component
            multiCardFieldButton.addEventListener("click", () => {
                cardField
                .submit()
                .then(() => {
                    // Handle a successful payment
                })
                .catch((err) => {
                    // Handle an unsuccessful payment
                });
            });
        }
      </script>
</html>

Test purchases

The integration checks eligibility requirements and the payment fields only display if the payer is eligible for the payment method.

Use test card numbers to test your card field integration and advanced credit and debit card payments in the sandbox. Create a positive test card number using the credit card generator in the PayPal Developer Dashboard, or choose a card from this list of positive test card numbers.

Using an invalid card number results in an unsuccessful transaction.

When prompted for information, such as a phone number for the sandbox business request, enter any number that fits the required format. Sandbox request data doesn’t need to use real values.