Integrate card payments

PayPal Checkout plus customized card fields

SDKCurrentAdvancedLast updated: May 9th 2023, @ 6:56:47 am


How it Works

Advanced checkout lets you offer the PayPal payment button and custom credit and debit card fields. This guide covers integrating the PayPal button, credit, and debit card payments. You can find more ways to extend your integration in our customization doc.

Once you integrate advanced checkout, you can also offer options like Pay Later, Venmo, and alternative payment methods with some additional configuration.

This integration guide follows the code in this GitHub sample.

Integration video

Watch our video tutorial for this integration:

For this integration, pass merchant-id in the JavaScript SDK and pass the payee or PayPal-Auth-Assertion in the request for the back end.

Know before you code

Required

You need a developer account to get sandbox credentials

PayPal uses REST API credentials which you can get from the developer dashboard.

  • Client ID: authenticates your account with PayPal and identifies an app in your sandbox.
  • Client secret: authorizes an app in your sandbox. Keep this secret safe and don't share it.
DashboardRead the guide
Required

You need a developer account to get sandbox credentials

You need a combo of PayPal and third-party tools

  • JavaScript SDK: Adds PayPal-supported payment methods.
  • Orders REST API: Create, update, retrieve, authorize, and capture orders
  • npm: Registry used to install the necessary third-party libraries.

You can use Postman to explore and test PayPal APIs.

Run in Postman

1. Before you begin your integration

1Check your account setup for advanced card payments

This integration requires a sandbox business account with the Advanced Credit and Debit Card Payments capability. Your account should automatically have this capability.

To confirm that Advanced Credit and Debit Card Payments are enabled for you, check your sandbox business account as follows:

  1. Log into the PayPal Developer Dashboard, go to Apps & Credentials > Sandbox > REST API apps, and select the name of your app.
  2. Go to Features > Accept payments.
  3. Select the Advanced Credit and Debit Card Payments checkbox and select Save Changes.

Note: 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 Set up npm

One of the requirements to run this sample application is to have npm installed. For more info, visit npm's documentation.

1. Install third-party libraries

Install the following third-party libraries to set up your integration. Here is a sample command to install them all at the same time:

1npm install dotenv ejs express node-fetch

Select any of the links below to see more detailed installation instructions for each library:

Third-party librariesDescription
DotenvThis zero-dependency module separates your configuration and code by loading environment variables from a .env file into process.env.
ejsThese templates help you deliver HTML markup using plain JavaScript.
expressThis lean Node.js web application framework supports web and mobile applications.
node-fetchThis function helps you make API requests, similar to window.fetch.
2. Verify package.json

A package.json file has a list of the packages and version numbers needed for your app. You can share your package.json file with other developers so they can use the same settings as you.

The following snippet is an example of a package.json file for a PayPal integration. Compare this sample to the package.json in your project:

1{
2 "name": "@paypalcorp/advanced-integration",
3 "version": "1.0.0",
4 "description": "",
5 "main": "YOUR-SERVER-NAME.js",
6 "type": "module",
7 "scripts": {
8 "test": "echo "Error: no test specified" && exit 1",
9 "start": "node YOUR-SERVER-NAME.js"
10 },
11 "author": "",
12 "license": "Apache-2.0",
13 "dependencies": {
14 "dotenv": "^16.0.0",
15 "ejs": "^3.1.6",
16 "express": "^4.17.3",
17 "node-fetch": "^3.2.1"
18 }
19}
  • Pass the name of your server file using main by replacing the default YOUR-SERVER-NAME.js on lines 5 and 9.
  • Use scripts.test and scripts.start to customize how your app starts up.

If you're having trouble with your app, try reinstalling your local library and package files using npm install.

If you're getting the node error below, you need to include "type": "module" in your package.json file. This line isn't automatically added when package.json is created:

Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension (Use `node --trace-warnings ...` to show where the warning was created)

See line 6 of the sample package.json file for an example.

3. Set up .env

A .env file is a line-delimited text file that sets your local working environment variables. Use this .env file to securely pass the client ID and app secret for your app.

This code shows an example .env file. Replace the CLIENT_ID and APP_SECRET with values from your app:

Note: View your CLIENT_ID and APP_SECRET in the PayPal Developer Dashboard under Apps & Credentials

1CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
2APP_SECRET="YOUR_SECRET_GOES_HERE"

2. Integrate front end

This section explains how to set up your front end to integrate advanced checkout payments.

Front-end process

  1. Your app displays the PayPal checkout button and the card checkout form.
  2. Your app calls server endpoints to create the order and capture payment. The specific method depends on whether the buyer uses the PayPal button or checkout form.

Front-end code

This example uses 2 files, app.js and checkout.ejs, to show how to set up the back end to integrate with advanced payments:

  • /public/app.js handles the client-side logic and defines how the PayPal front-end components connect with the back-end. Use this file to set up the PayPal checkout using the PayPal JavaScript SDK, and handle the payer's interactions with the PayPal checkout button.
  • /views/checkout.ejs is an Embedded JavaScript (EJS) file that builds the checkout page, adds the PayPal button, and loads the app.js file.
  1. /public/app.js
  2. /views/checkout.ejs
1paypal
2 .Buttons({
3 // Sets up the transaction when a payment button is selected
4 createOrder: function () {
5 return fetch('/api/orders', {
6 method: 'post',
7 // use the "body" param to optionally pass additional order information
8 // like product skus and quantities
9 body: JSON.stringify({
10 cart: [
11 {
12 sku: 'YOUR_PRODUCT_STOCK_KEEPING_UNIT',
13 quantity: 'YOUR_PRODUCT_QUANTITY',
14 },
15 ],
16 }),
17 })
18 .then((response) => response.json())
19 .then((order) => order.id)
20 },
21 // Finalize the transaction after payer approval
22 onApprove: function (data) {
23 return fetch(`/api/orders/${data.orderID}/capture`, {
24 method: 'post',
25 })
26 .then((response) => response.json())
27 .then((orderData) => {
28 // Successful capture! For dev/demo purposes:
29 console.log(
30 'Capture result',
31 orderData,
32 JSON.stringify(orderData, null, 2),
33 )
34 const transaction = orderData.purchase_units[0].payments.captures[0]
35 alert(
36 `Transaction ${transaction.status}: ${transaction.id} See console for all available details`,
37 )
38 // When ready to go live, remove the alert and show a success message within this page. For example:
39 // var element = document.getElementById('paypal-button-container');
40 // element.innerHTML = '<h3>Thank you for your payment!</h3>';
41 // Or go to another URL: actions.redirect('thank_you.html');
42 })
43 },
44 })
45 .render('#paypal-button-container')
46// If this returns false or the card fields aren't visible, see Step #1.
47if (paypal.CardFields.isEligible()) {
48 let orderId
49 // Renders card fields
50 paypal.CardFields.render({
51 // Call your server to set up the transaction
52 createOrder: () => {
53 return fetch('/api/orders', {
54 method: 'post',
55 // use the "body" param to optionally pass additional order information
56 // like product skus and quantities
57 body: JSON.stringify({
58 cart: [
59 {
60 sku: 'YOUR_PRODUCT_STOCK_KEEPING_UNIT',
61 quantity: 'YOUR_PRODUCT_QUANTITY',
62 },
63 ],
64 }),
65 })
66 .then((res) => res.json())
67 .then((orderData) => {
68 orderId = orderData.id
69 // needed later to complete capture
70 return orderData.id
71 })
72 },
73 styles: {
74 '.valid': {
75 color: 'green',
76 },
77 '.invalid': {
78 color: 'red',
79 },
80 },
81 fields: {
82 number: {
83 selector: '#card-number',
84 placeholder: '4111 1111 1111 1111',
85 },
86 cvv: {
87 selector: '#cvv',
88 placeholder: '123',
89 },
90 expirationDate: {
91 selector: '#expiration-date',
92 placeholder: 'MM/YY',
93 },
94 },
95 }).then((cardFields) => {
96 document.querySelector('#card-form').addEventListener('submit', (event) => {
97 event.preventDefault()
98 cardFields
99 .submit({
100 // Cardholder's first and last name
101 cardholderName: document.getElementById('card-holder-name').value,
102 // Billing Address
103 billingAddress: {
104 // Street address, line 1
105 streetAddress: document.getElementById(
106 'card-billing-address-street',
107 ).value,
108 // Street address, line 2 (Ex: Unit, Apartment, etc.)
109 extendedAddress: document.getElementById(
110 'card-billing-address-unit',
111 ).value,
112 // State
113 region: document.getElementById('card-billing-address-state').value,
114 // City
115 locality: document.getElementById('card-billing-address-city')
116 .value,
117 // Postal Code
118 postalCode: document.getElementById('card-billing-address-zip')
119 .value,
120 // Country Code
121 countryCodeAlpha2: document.getElementById(
122 'card-billing-address-country',
123 ).value,
124 },
125 })
126 .then(() => {
127 fetch(`/api/orders/${orderId}/capture`, {
128 method: 'post',
129 })
130 .then((res) => res.json())
131 .then((orderData) => {
132 // Two cases to handle:
133 // (1) Other non-recoverable errors -> Show a failure message
134 // (2) Successful transaction -> Show confirmation or thank you
135 // This example reads a v2/checkout/orders capture response, propagated from the server
136 // You could use a different API or structure for your 'orderData'
137 const errorDetail =
138 Array.isArray(orderData.details) && orderData.details[0]
139 if (errorDetail) {
140 var msg = 'Sorry, your transaction could not be processed.'
141 if (errorDetail.description)
142 msg += '\n\n' + errorDetail.description
143 if (orderData.debug_id)
144 msg += ' (' + orderData.debug_id + ')'
145 return alert(msg) // Show a failure message
146 }
147 // Show a success message or redirect
148 alert('Transaction completed!')
149 })
150 })
151 .catch((err) => {
152 alert('Payment could not be captured! ' + JSON.stringify(err))
153 })
154 })
155 })
156} else {
157 // Hides card fields if the merchant isn't eligible
158 document.querySelector('#card-form').style = 'display: none'
159}

Understand the front-end code

This section explains critical parts of the front-end code samples.

This breakdown focuses on key lines from the /public/app.js code sample.

PayPal Button JavaScript

This section calls the JavaScript SDK that defines the PayPal buttons

1paypal
2 .Buttons({
3 {...}
4 })
5 .render("#paypal-button-container");

Create order for PayPal Button

This code sample defines the createOrder() function. Add this code to the PayPal button in /public/app.js.

3// Sets up the transaction when a payment button is selected
4createOrder: function () {
5 return fetch("/api/orders", {
6 method: "post",
7 // use the "body" param to optionally pass additional order information
8 // like product skus and quantities
9 body: JSON.stringify({
10 cart: [
11 {
12 sku: "YOUR_PRODUCT_STOCK_KEEPING_UNIT",
13 quantity: "YOUR_PRODUCT_QUANTITY",
14 },
15 ],
16 }),
17 })
18 .then((response) => response.json())
19 .then((order) => order.id);
20},
  • Line 5 creates the order by calling the /api/orders endpoint on your server.
  • Lines 10-15 pass the SKU and quantity for the product in the cart. For more information about creating orders, see the createOrder section of the JavaScript SDK reference guide.

Capture payment when approved

This code sample defines a POST call to /api/orders/{orderID}/capture. Add this code to the PayPal button in /public/app.js.

22onApprove: function (data) {
23 return fetch(`/api/orders/${data.orderID}/capture`, {
24 method: "post",
25 })
26 .then((response) => response.json())
27 .then((orderData) => {
28 // Successful capture! For dev/demo purposes:
29 console.log(
30 "Capture result",
31 orderData,
32 JSON.stringify(orderData, null, 2)
33 );
34 const transaction = orderData.purchase_units[0].payments.captures[0];
35 alert(`Transaction ${transaction.status}: ${transaction.id}
36
37 See console for all available details
38 `);
39 // When ready to go live, remove the alert and show a success message within this page. For example:
40 // var element = document.getElementById('paypal-button-container');
41 // element.innerHTML = '<h3>Thank you for your payment!</h3>';
42 // Or go to another URL: actions.redirect('thank_you.html');
43 });
44},
  • Line 23 calls your server's capture endpoint to capture the order.
  • Lines 28-42 declare what happens when the payment is successfully captured. When you're ready to go live, you can remove the alert and either show a success message or redirect the payer to another URL.

Check for CardFields eligibility

Hosted card fields use the JavaScript SDK to help non-PCI-compliant sellers save a payer's card data and create a token for future payments. During an eligible payment, the hosted fields show up in the payment experience and prompt the payer for consent to save their card data. For more information about hosted fields, see Hosted fields.

Note: You need to complete the account setup in sandbox to be eligible for testing hosted fields. Merchant accounts have to be onboarded with the PPCP product.

This code sample checks to see if a payment is eligible for hosted card fields. If not, the payment experience won't show the hosted card fields. Add this section of code to the PayPal button in /public/app.js.

48// If this returns false or the card fields aren't visible, see Step #1.
49if (paypal.CardFields.isEligible()) {
50 let orderId;
51 {...}
52} else {
53 // Hides card fields if the merchant isn't eligible
54 document.querySelector("#card-form").style = "display: none";
55}

You can modify this code to show a custom message for eligible or ineligible payments.

Render hosted card fields and create order

This code sample renders the hosted card fields for an eligible payment. Add this section of code to the PayPal button in /public/app.js.

52// Renders card fields
53paypal.CardFields.render({
54 // Call your server to set up the transaction
55 createOrder: () => {
56 return fetch("/api/orders", {
57 method: "post",
58 // use the "body" param to optionally pass additional order information
59 // like product skus and quantities
60 body: JSON.stringify({
61 cart: [
62 {
63 sku: "YOUR_PRODUCT_STOCK_KEEPING_UNIT",
64 quantity: "YOUR_PRODUCT_QUANTITY",
65 },
66 ],
67 }),
68 })
69 .then((res) => res.json())
70 .then((orderData) => {
71 orderId = orderData.id;
72 // needed later to complete capture
73 return orderData.id;
74 });
75 },
76 styles: {
77 ".valid": {
78 color: "green",
79 },
80 ".invalid": {
81 color: "red",
82 },
83 },
84 fields: {
85 number: {
86 selector: "#card-number",
87 placeholder: "4111 1111 1111 1111",
88 },
89 cvv: {
90 selector: "#cvv",
91 placeholder: "123",
92 },
93 expirationDate: {
94 selector: "#expiration-date",
95 placeholder: "MM/YY",
96 },
97 },
98})
  • Line 56 creates the order by calling the /api/orders endpoint on your server.
  • Lines 63-64 pass the SKU and quantity for the product in the cart.
  • Lines 71-72 get the order identifier id from the orderData object returned.
  • Lines 75-82 define styles for the hosted card fields. You can change these styles as needed for your implementation.
  • Lines 83-95 define the selector values and placeholders for the input fields. You can edit this section as needed for your implementation, such as adding more fields. For more information about optional configuration, see Options in the JavaScript SDK reference.

Capture eligible payment

This code sample captures the payment when it is eligible for hosted card fields. Add this code to the PayPal button in /public/app.js, right after rendering the card fields.

97.then((cardFields) => {
98 document.querySelector("#card-form").addEventListener("submit", (event) => {
99 event.preventDefault();
100 cardFields
101 .submit({
102 // Cardholder's first and last name
103 cardholderName: document.getElementById("card-holder-name").value,
104 // Billing Address
105 billingAddress: {
106 // Street address, line 1
107 streetAddress: document.getElementById(
108 "card-billing-address-street"
109 ).value,
110 // Street address, line 2 (Ex: Unit, Apartment, etc.)
111 extendedAddress: document.getElementById(
112 "card-billing-address-unit"
113 ).value,
114 // State
115 region: document.getElementById("card-billing-address-state").value,
116 // City
117 locality: document.getElementById("card-billing-address-city")
118 .value,
119 // Postal Code
120 postalCode: document.getElementById("card-billing-address-zip")
121 .value,
122 // Country Code
123 countryCodeAlpha2: document.getElementById(
124 "card-billing-address-country"
125 ).value,
126 },
127 })
128 .then(() => {
129 fetch(`/api/orders/${orderId}/capture`, {
130 method: "post",
131 })
132 .then((res) => res.json())
133 .then((orderData) => {
134 // Two cases to handle:
135 // (1) Other non-recoverable errors -> Show a failure message
136 // (2) Successful transaction -> Show confirmation or thank you
137 // This example reads a v2/checkout/orders capture response, propagated from the server
138 // You could use a different API or structure for your 'orderData'
139 const errorDetail =
140 Array.isArray(orderData.details) && orderData.details[0];
141 if (errorDetail) {
142 var msg = "Sorry, your transaction could not be processed.";
143 if (errorDetail.description)
144 msg += "\n\n" + errorDetail.description;
145 if (orderData.debug_id) msg += " (" + orderData.debug_id + ")";
146 return alert(msg); // Show a failure message
147 }
148 // Show a success message or redirect
149 alert("Transaction completed!");
150 });
151 })
152 .catch((err) => {
153 alert("Payment could not be captured! " + JSON.stringify(err));
154 });
155 });
156 });
  • Line 98 sets up an event listener for when the payer submits an eligible card payment.
  • Lines 100-127 pass the hosted card field values, such as the cardholder's name and address, to the POST call in lines 128-131. Anything you pass into the submit is sent to the iframe that communicates with the Orders API. The iframe retrieves the relevant data and sends it along to the POST call.
  • Lines 128-131 define a POST call to /v2/checkout/orders/{id}/capture that captures the order using the orderId.
  • Lines 133-150 declare how to handle the payment capture response. When you're ready to go live, you can remove the alert and either show a success message or redirect the payer to another URL.

Sample capture response

This code sample shows a response to a POST call to /v2/checkout/orders/{id}/capture. This response is the orderData that is grabbed by lines 71-72 in the Render hosted card fields and create order section.

1{
2 "id": "5O190127TN364715T",
3 "status": "COMPLETED",
4 "payment_source": {
5 "paypal": {
6 "name": {
7 "given_name": "Firstname",
8 "surname": "Lastname"
9 },
10 "email_address": "payer@example.com",
11 "account_id": "QYR5Z8XDVJNXQ"
12 }
13 },
14 "purchase_units": [
15 {
16 "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
17 "shipping": {
18 "address": {
19 "address_line_1": "123 Main St.",
20 "admin_area_2": "Anytown",
21 "admin_area_1": "CA",
22 "postal_code": "12345",
23 "country_code": "US"
24 }
25 },
26 "payments": {
27 "captures": [
28 {
29 "id": "3C679366HH908993F",
30 "status": "COMPLETED",
31 "amount": {
32 "currency_code": "USD",
33 "value": "100.00"
34 },
35 "seller_protection": {
36 "status": "ELIGIBLE",
37 "dispute_categories": [
38 "ITEM_NOT_RECEIVED",
39 "UNAUTHORIZED_TRANSACTION"
40 ]
41 },
42 "final_capture": true,
43 "disbursement_mode": "INSTANT",
44 "seller_receivable_breakdown": {
45 "gross_amount": {
46 "currency_code": "USD",
47 "value": "100.00"
48 },
49 "paypal_fee": {
50 "currency_code": "USD",
51 "value": "3.00"
52 },
53 "net_amount": {
54 "currency_code": "USD",
55 "value": "97.00"
56 }
57 },
58 "create_time": "2018-04-01T21:20:49Z",
59 "update_time": "2018-04-01T21:20:49Z",
60 "links": [
61 {
62 "href": "https://api-m.paypal.com/v2/payments/captures/3C679366HH908993F",
63 "rel": "self",
64 "method": "GET"
65 },
66 {
67 "href": "https://api-m.paypal.com/v2/payments/captures/3C679366HH908993F/refund",
68 "rel": "refund",
69 "method": "POST"
70 }
71 ]
72 }
73 ]
74 }
75 }
76 ],
77 "payer": {
78 "name": {
79 "given_name": "Firstname",
80 "surname": "Lastname"
81 },
82 "email_address": "payer@example.com",
83 "payer_id": "QYR5Z8XDVJNXQ"
84 },
85 "links": [
86 {
87 "href": "https://api-m.paypal.com/v2/checkout/orders/5O190127TN364715T",
88 "rel": "self",
89 "method": "GET"
90 }
91 ]
92 }
  • Line 2 shows the id for this orderData object.
  • Lines 4-13 pass details about the payment source.
  • Lines 14-77 pass the purchase_units in this transaction. Each purchase unit establishes a contract between a customer and merchant. Each purchase unit represents either a full or partial order that the customer intends to purchase from the merchant.
  • Line 16 passes the reference_id that identifies the 1 purchase unit in this payment response.
  • Lines 17-24 pass details about the shipping address.
  • Line 27 declares the payments object that passes the payment details for this capture request.
  • Line 28 declares the captures object that passes details about the captured payments for this request.
  • Lines 29-57 pass the payment capture details, such as the capture identifier id, amount, disbursement_mode, and net_amount.
  • Lines 59-73 pass the HATEOAS details of the capture response.
  • Lines 78-85 pass details about the payer.
  • Lines 86-92 pass the HATEOAS details for the orders response.

See the Capture payment for order API endpoint for more details about the capture response.


This breakdown focuses on key lines from the /views/checkout.ejs code sample.

Import style sheet

This section calls the JavaScript SDK that defines the PayPal buttons

5<link
6 rel="stylesheet"
7 type="text/css"
8 href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css"
9/>

Import JavaScript SDK

12<script src="https://www.paypal.com/sdk/js?client-id=<CLIENT_ID>&merchant-id=<MERCHANT_ID>&currency=USD"></script>

Line 12: Replace the clientId with the client ID from your app's sandbox API credentials in the Developer Dashboard. See the Know before you code section for more details on these credentials.

Add PayPal button

16<div id="paypal-button-container" class="paypal-button-container"></div>

Line 16: Include the PayPal Checkout button before you request the credit card fields or on the same page that you request this information. For more information, see Section 8, Required Use of PayPal Checkout, PayPal Credit, in the PayPal Online Card Payments Services Agreement.

Create your card form

This section of code shows how to create the form fields and Submit button you need to take card payments.

17<div class="card_container">
18 <form id="card-form">
19 <label for="card-number">Card Number</label>
20 <div id="card-number" class="card_field"></div>
21 <div style="display: flex; flex-direction: row;">
22 <div>
23 <label for="expiration-date">Expiration Date</label>
24 <div id="expiration-date" class="card_field"></div>
25 </div>
26 <div style="margin-left: 10px;">
27 <label for="cvv">CVV</label>
28 <div id="cvv" class="card_field"></div>
29 </div>
30 </div>
31 <label for="card-holder-name">Name on Card</label>
32 <input
33 type="text"
34 id="card-holder-name"
35 name="card-holder-name"
36 autocomplete="off"
37 placeholder="card holder name"
38 />
39 <div>
40 <label for="card-billing-address-street">Billing Address</label>
41 <input
42 type="text"
43 id="card-billing-address-street"
44 name="card-billing-address-street"
45 autocomplete="off"
46 placeholder="street address"
47 />
48 </div>
49 <div>
50 <label for="card-billing-address-unit">&nbsp;</label>
51 <input
52 type="text"
53 id="card-billing-address-unit"
54 name="card-billing-address-unit"
55 autocomplete="off"
56 placeholder="unit"
57 />
58 </div>
59 <div>
60 <input
61 type="text"
62 id="card-billing-address-city"
63 name="card-billing-address-city"
64 autocomplete="off"
65 placeholder="city"
66 />
67 </div>
68 <div>
69 <input
70 type="text"
71 id="card-billing-address-state"
72 name="card-billing-address-state"
73 autocomplete="off"
74 placeholder="state"
75 />
76 </div>
77 <div>
78 <input
79 type="text"
80 id="card-billing-address-zip"
81 name="card-billing-address-zip"
82 autocomplete="off"
83 placeholder="zip / postal code"
84 />
85 </div>
86 <div>
87 <input
88 type="text"
89 id="card-billing-address-country"
90 name="card-billing-address-country"
91 autocomplete="off"
92 placeholder="country code"
93 />
94 </div>
95 <br /><br />
96 <button value="submit" id="submit" class="btn">Pay</button>
97 </form>
98 </div>
  • Lines 18-97 declare a card-form object with fields for card-number, expiration-date, cvv, card-holder-name, and billing address information.
  • The fields in this example match up with fields in the Capture eligible payment code sample.

Import app.js

This code sample renders the hosted card fields for an eligible payment. Add this section of code to the PayPal button in /public/app.js.

99<script src="app.js"></script>

Line 99 imports the app.js file that you created.


Run front end integration

  • Run npm start to run your server again.
  • Open your browser and navigate to localhost:8888 for testing.
  • When your server is running, proceed to the next section to test your integration.

3. Integrate back end

This section explains how to set up your back end to integrate advanced checkout payments.

Back-end process

  1. Your app shows a customer the available payment methods on the server-side.
  2. Your app creates an order on the back-end by making a call to the Create Orders API endpoint.
  3. Your app makes a call to the Capture Payment for Order API endpoint on the back-end to move the money when the buyer confirms the order.

Back-end code

  1. Generate a PayPal-Auth-Assertion header
  2. Pass the PayPal-Auth-Assertion header with standard Content-Type, Authorization, and PayPal-Request-ID headers. Copy and modify the following code to generate the PayPal-Auth-Assertion header.

    1// Node.js
    2
    3function encodeObjectToBase64(object) {
    4 const objectString = JSON.stringify(object);
    5 return Buffer
    6 .from(objectString)
    7 .toString("base64");
    8}
    9
    10const clientId = "CLIENT-ID";
    11const sellerPayerId = "SELLER-PAYER-ID"; // preferred
    12// const sellerEmail = "SELLER-ACCOUNT-EMAIL"; // use instead if payer-id unknown
    13
    14const header = {
    15 alg: "none"
    16};
    17const encodedHeader = encodeObjectToBase64(header);
    18
    19const payload = {
    20 iss: clientId,
    21 payer_id: sellerPayerId
    22 // email: sellerEmail
    23};
    24const encodedPayload = encodeObjectToBase64(payload);
    25
    26const jwt = `${encodedHeader}.${encodedPayload}.`; // json web token
    27console.log(`Paypal-Auth-Assertion=${jwt}`);

    Note: The example above contains period characters ( . ) which are required according to JSON web standard structure.

    Modify the code:

    • Replace CLIENT-ID with the client ID of the platform or marketplace from the PayPal developer dashboard.
    • Replace SELLER-PAYER-ID with the payer ID or the email of the receiving seller's PayPal account.
  3. Set up your back end
  4. The following example uses 2 files, paypal-api.js and server.js to show how to set up the back end to integrate with advanced payments:

    • paypal-api.js provides helper methods and functions for making the API calls and handling errors, such as creating and capturing a PayPal payment object, generating access and client tokens, and handling errors.
    • server.js imports the functions from paypal-api.js into your application environment and creates instructions that your app uses to call those functions.
    1. paypal-api.js
    2. Server.js
    1import fetch from "node-fetch";
    2// set some important variables
    3const { CLIENT_ID, APP_SECRET } = process.env;
    4const base = "https://api-m.sandbox.paypal.com";
    5// call the create order method
    6export async function createOrder() {
    7 const purchaseAmount = "100.00"; // TODO: pull prices from a database
    8 const accessToken = await generateAccessToken();
    9 const url = `${base}/v2/checkout/orders`;
    10 const response = await fetch(url, {
    11 method: "post",
    12 headers: {
    13 "Content-Type": "application/json",
    14 "Authorization": `Bearer ${accessToken}`,
    15 "PayPal-Auth-Assertion":"AUTH-ASSERTION",
    16 "PayPal-Partner-Attribution-Id":"BN-CODE",
    17 },
    18 body: JSON.stringify({
    19 intent: "CAPTURE",
    20 purchase_units: [
    21 {
    22 amount: {
    23 currency_code: "USD",
    24 value: purchaseAmount,
    25 },
    26 },
    27 ],
    28 }),
    29 });
    30 return handleResponse(response);
    31}
    32// capture payment for an order
    33export async function capturePayment(orderId) {
    34 const accessToken = await generateAccessToken();
    35 const url = `${base}/v2/checkout/orders/${orderId}/capture`;
    36 const response = await fetch(url, {
    37 method: "post",
    38 headers: {
    39 "Content-Type": "application/json",
    40 "Authorization": `Bearer ${accessToken}`,
    41 "PayPal-Auth-Assertion":"AUTH-ASSERTION",
    42 "PayPal-Partner-Attribution-Id":"BN-CODE",
    43 },
    44 });
    45 return handleResponse(response);
    46}
    47// generate access token
    48export async function generateAccessToken() {
    49 const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64");
    50 const response = await fetch(`${base}/v1/oauth2/token`, {
    51 method: "post",
    52 body: "grant_type=client_credentials",
    53 headers: {
    54 Authorization: `Basic ${auth}`,
    55 },
    56 });
    57 const jsonData = await handleResponse(response);
    58 return jsonData.access_token;
    59}
    60// generate client token
    61export async function generateClientToken() {
    62 const accessToken = await generateAccessToken();
    63 const response = await fetch(`${base}/v1/identity/generate-token`, {
    64 method: "post",
    65 headers: {
    66 "Authorization": `Bearer ${accessToken}`,
    67 "Accept-Language": "en_US",
    68 "Content-Type": "application/json",
    69 "PayPal-Auth-Assertion":"AUTH-ASSERTION",
    70 "PayPal-Partner-Attribution-Id":"BN-CODE",
    71 },
    72 });
    73 console.log('response', response.status)
    74 const jsonData = await handleResponse(response);
    75 return jsonData.client_token;
    76}
    77async function handleResponse(response) {
    78 if (response.status === 200 || response.status === 201) {
    79 return response.json();
    80 }
    81 const errorMessage = await response.text();
    82 throw new Error(errorMessage);
    83}

Understand the back-end code

This section explains critical parts of the back-end code samples.

This breakdown focuses on key lines from the paypal-api.js code sample.

imports

This line of code imports the fetch function from the Node-Fetch library.

1import fetch from "node-fetch"

Grab variables and sandbox URL

This section of code grabs the environment variables and sets the base sandbox URL.

3// set some important variables
4const { CLIENT_ID, APP_SECRET } = process.env;
5const base = "https://api-m.sandbox.paypal.com";
  1. Line 4 uses the Node-Fetch library to import and declare the client ID and app secret from your .env file.
  2. Line 5 declares the base URL for PayPal’s sandbox API.

Create order

Create an order to start a payment between a payer and a seller by making a POST request to /v2/checkout/orders.

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

7// call the create order method
8export async function createOrder() {
9 const purchaseAmount = "100.00"; // TODO: pull prices from a database
10 const accessToken = await generateAccessToken();
11 const url = `${base}/v2/checkout/orders`;
12 const response = await fetch(url, {
13 method: "post",
14 headers: {
15 "Content-Type": "application/json",
16 "Authorization": `Bearer ${accessToken}`,
17 "PayPal-Auth-Assertion": "AUTH-ASSERTION",
18 "PayPal-Partner-Attribution-Id": "BN-CODE",
19 },
20 body: JSON.stringify({
21 intent: "CAPTURE",
22 purchase_units: [
23 {
24 amount: {
25 currency_code: "USD",
26 value: purchaseAmount,
27 },
28 },
29 ],
30 }),
31 });
32 return handleResponse(response);
33}
  • Line 9 declares a static price to use for testing. You can change this to a dynamic value before going live.
  • Line 10 establishes a listener to capture the accessToken from the generateAccessToken() function later in the API call.
  • Lines 11-29 create an order by sending a POST request to the Orders v2 API, using the accessToken.

Note: Visit the Create Order endpoint of the PayPal Orders v2 API to see sample responses and other details.

Partners and sellers who are PCI compliant can also create an order and complete the payment by passing raw credit card details through the Orders v2 API.

7Creates a new order and completes the payment using given card in a single shot
8//Request
9curl -v -k -X POST 'https://api-m.sandbox.paypal.com/v2/checkout/orders' \
10 -H 'PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a' \
11 -H 'Authorization: Bearer ACCESS-TOKEN' \
12 -H 'Content-Type: application/json' \
13 -d '{
14 "intent": "AUTHORIZE",
15 "purchase_units": [
16 {
17 "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
18 "amount": {
19 "currency_code": "USD",
20 "value": "100.00"
21 }
22 }
23 ],
24 "payment_source": {
25 "card": {
26 "number": "4111111111111111",
27 "expiry": "2020-02",
28 "name": "John Doe",
29 "billing_address": {
30 "address_line_1": "2211 N First Street",
31 "address_line_2": "17.3.160",
32 "admin_area_1": "CA",
33 "admin_area_2": "San Jose",
34 "postal_code": "95131",
35 "country_code": "US"
36 }
37 }
38 }
39}'

Capture payment

Capture an order to successfully move money from the payer's payment method to the seller by making a POST call to /v2/checkout/orders/${orderId}/capture.

34// capture payment for an order
35export async function capturePayment(orderId) {
36 const accessToken = await generateAccessToken();
37 const url = `${base}/v2/checkout/orders/${orderId}/capture`;
38 const response = await fetch(url, {
39 method: "post",
40 headers: {
41 "Content-Type": "application/json",
42 "Authorization": `Bearer ${accessToken}`,
43 "PayPal-Auth-Assertion": "AUTH-ASSERTION",
44 "PayPal-Partner-Attribution-Id": "BN-CODE",
45 },
46 });
47 return handleResponse(response);
48}
  • Line 36 establishes a listener to capture the accessToken from the generateAccessToken() function later in the API call.
  • Line 37 declares the URL of the API endpoint for this call.
  • Lines 38-43 define a response that makes a POST call to the /v2/checkout/orders/${orderId}/capture API endpoint to capture the order, using the accessToken.

Note: Visit the Capture Payment endpoint of the PayPal Orders v2 API to see sample responses and other details

Payment processors return processor response codes when they receive a transaction request. For advanced card payments, the code displays in the authorization object under the response_code field.

This sample shows the processor response codes that are returned in the response of authorization and capture calls:

1"processor_response": {
2 "avs_code": "Y",
3 "cvv_code": "S",
4 "payment_advice_code": "",
5 "response_code": "0000"
6 }

If an external payment processor declines a transaction, PayPal returns a HTTP 201 Created status code and a status of DECLINED in the capture status.

See the Orders API response_code object to get the processor response code for the non-PayPal payment processor errors.

Generate access token

You need an access token to authenticate all REST API requests. The following code sample creates an access token for you by making a POST call to the /v1/oauth2/ token endpoint.

49// generate access token
50export async function generateAccessToken() {
51 const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64");
52 const response = await fetch(`${base}/v1/oauth2/token`, {
53 method: "post",
54 body: "grant_type=client_credentials",
55 headers: {
56 Authorization: `Basic ${auth}`,
57 },
58 });
59 const jsonData = await handleResponse(response);
60 return jsonData.access_token;
61}
  • Line 51 combines the CLIENT_ID and APP_SECRET as a key:value pair.
  • Lines 52-58 define a response that makes a POST call to the /v1/oauth2/token API endpoint to generate an access token.
  • Lines 59-60 establish a listener to capture the response jsonData from the request and return the access_token.
  1. Sample access token request
  2. Sample access token response
1curl -v -X POST "https://api-m.sandbox.paypal.com/v1/oauth2/token"
2 -u "CLIENT_ID:CLIENT_SECRET"
3 -H "Content-Type: application/x-www-form-urlencoded"
4 -d "grant_type=client_credentials"

Note: Read more about authenticating with PayPal.

Generate client token

A client token uniquely identifies your payer. You need a client token to use card fields. The following code sample creates a client token for you by making a POST call to the /v1/identity/generate-token endpoint.

63// generate client token
64export async function generateClientToken() {
65 const accessToken = await generateAccessToken();
66 const response = await fetch(`${base}/v1/identity/generate-token`, {
67 method: "post",
68 headers: {
69 "Authorization": `Bearer ${accessToken}`,
70 "Accept-Language": "en_US",
71 "Content-Type": "application/json",
72 "PayPal-Auth-Assertion":"AUTH-ASSERTION",
73 "PayPal-Partner-Attribution-Id":"BN-CODE",
74 },
75 });
76 console.log('response', response.status)
77 const jsonData = await handleResponse(response);
78 return jsonData.client_token;
79}
  • Line 65 sets up a listener to capture the accessToken from the generateAccessToken() function.
  • Lines 66-73 make a POST call to the /v1/identity/generate-token API endpoint to generate a client token.
  • Lines 75-76 establish a listener to capture the response jsonData from the request and return the client_token.
  1. Sample client token request
  2. Sample client token response
1curl -X POST https://api-m.sandbox.paypal.com/v1/identity/generate-token \
2-H 'Content-Type: application/json' \
3-H 'Authorization: Bearer ACCESS-TOKEN' \
4-H 'PayPal-Partner-Attribution-Id: BN-CODE' \
5-H 'Accept-Language: en_US' \

Tip: Because each payer session is unique, set up your server to generate a new client token each time the card fields render on your page.

Handle responses

The handleResponse function sets up a listener for API responses.

79async function handleResponse(response) {
80 if (response.status === 200 || response.status === 201) {
81 return response.json();
82 }
83 const errorMessage = await response.text();
84 throw new Error(errorMessage);
85}
  • Line 80 declares which HTTP status codes return a response. Other status codes return an error message.

This is a breakdown of the server.js code sample.

Declare imports

This section of code imports the configuration from your .env file, the express library, and the content of your paypal-api.js file.

1import "dotenv/config";
2import express from "express";
3import * as paypal from "./paypal-api.js";

Set up port and server

This section of code sets up the port to run your server.

4const {PORT = 8888} = process.env;

This code starts the express Node.js web application framework. Line 6 declares that the rendering engine uses Embedded JavaScript templates (ejs).

5const app = express();
6app.set("view engine", "ejs");
7app.use(express.static("public"));

This code runs your app on localhost, using the port 8888 that you set up in line 4 of the API call.

42app.listen(PORT, () => {
43 console.log(`Server listening at http://localhost:${PORT}/`);
44});

Test server

Run npm and visit http://localhost:8888/.

1npm start

This command starts the server on localhost. It will load on port 8888 by default, but you can specify a port as shown in line 4 of the sample server.js file.

Once started, you'll see the message Server listening at http://localhost:8888/. If you open that page in a web browser, it'll be blank.

Create checkout page

Set up your app's checkout page to load this section of code when it's rendered by your server.

10// render checkout page with client id & unique client token
11app.get("/", async (req, res) => {
12 const clientId = process.env.CLIENT_ID;
13 try {
14 const clientToken = await paypal.generateClientToken();
15 res.render("checkout", { clientId, clientToken });
16 } catch (err) {
17 res.status(500).send(err.message);
18 }
19});
  • Line 14 generates a client token by calling the generateClientToken() function from your paypal-api.js file.
  • Line 15 renders the checkout page.

Run createOrder()

This section of code creates an endpoint that calls the createOrder() function from your paypal-api.js file. Call the endpoint when the payment is submitted, for example, when the payer selects the PayPal button, or submits a card payment. See the /public/app.js section for more information.

21// create order
22app.post("/api/orders", async (req, res) => {
23 try {
24 const order = await paypal.createOrder();
25 res.json(order);
26 } catch (err) {
27 res.status(500).send(err.message);
28 }
29});

Lines 24-25 make a call to the paypal.createOrder() function to create an order.

Run capturePayment()

This section of code creates an endpoint that calls the capturePayment() function from your paypal-api.js file. Call the endpoint to capture the order and move money from the payer's payment method to the seller.

31// capture payment
32app.post("/api/orders/:orderID/capture", async (req, res) => {
33 const { orderID } = req.params;
34 try {
35 const captureData = await paypal.capturePayment(orderID);
36 res.json(captureData);
37 } catch (err) {
38 res.status(500).send(err.message);
39 }
40});
  • Line 33 pulls the orderID from the request parameters.
  • Lines 35-36 call the capturePayment() function from paypal-api.js and pass the orderID.

5. Test integration

Before going live, test your integration in the sandbox environment.

Learn more about the following resources on the Card Testing page:

Note: Use the credit card generator to generate additional test credit cards for sandbox testing.

Next steps & customizations

Add security to your checkout experience, or create customizations for your audience. Before you go live, you should complete the integration checklist.

Add more payment methods
Optional
Pay Later

Payers buy now and pay in installments.

Optional
Pay with Venmo

Add the Venmo button to your checkout integration.

Optional
Alternative payment methods

Support local payment methods across the globe.

Required
Integration checklist

Go through the integration checklist before you go live.

Security and customizations
Optional
Capture payment

Captures payment for an order.

Optional
Subscriptions

Create subscriptions to bill customers at regular intervals.

Optional
Refund a captured payment

Refund all or part of a captured payment.

Optional
Real-time account updater

Reduce declines by getting card updates from the issuer.