Integrate card payments
Last updated: Oct 10th, 5:09pm
How it works
Integrate advanced Checkout to present credit and debit card fields customized with your site's branding style. Advanced Checkout helps customers pay with PayPal, debit and credit cards, Pay Later options, Venmo, and other payment methods.
You can find more ways to extend your integration in Customize your buyer's experience.
Sequence diagram
The following sequence diagram shows an advanced Checkout integration with card fields and 3D Secure:
- The script tag fetches the JavaScript SDK when your checkout page renders.
- Your page uses the JavaScript SDK to render card fields.
- The buyer enters their card details into the card fields.
- Optionally, you can collect the billing address with your own input fields and include the values in the submit method.
- The buyer selects the Submit button which calls the
CardFields.submit
method. - The
createOrder
callback requests your server to create a PayPal order. - Your server requests the PayPal Orders API with the appropriate 3DS verification.
- The
createOrder
callback returns anorderId
to your page. - Depending on the 3DS verification method passed by your
createOrder
call, the buyer sees a 3DS User Authentication from the card-issuing bank. - The
onApprove
callback returns aliabilityShift
response that your page can use to determine how to process the order. - If there are no issues during the
createOrder
process, theonApprove
callback sends a capture request to your server. - Your server requests the PayPal Orders API to capture the order.
- Your page can use the capture response to verify the payment was completed, catch issues with the payment method, and approve or decline the transaction.
User experience
The PayPal card fields component can be rendered on your page and customized to match your user experience. You can load the PayPal buttons component alongside card fields to help your buyer select their preferred payment method. The PayPal buttons component shows up on your page based on the configurations set in the JavaScript SDK.
When your buyer chooses to pay with a credit or debit card:
- Card fields will render on your page.
- The buyer fills each of the fields with their card information, such as cardholder name, card number, expiration date, postal code, and CVV.
- Optionally, the buyer can enter their billing address into your own input elements to pass along with the order.
- After the buyer has filled in the card fields, the buyer selects the Pay or Submit button to process the card transaction.
- The order goes to PayPal servers, where we process the payment.
Know before you code
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.
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.
Prerequisites
Check your account setup for advanced card payments
This integration requires a sandbox business account with Advanced Credit and Debit Card Payments, which should be enabled automatically in your sandbox account.
To confirm that Advanced Credit and Debit Card Payments are enabled for you, check your sandbox business account:
- Log into the PayPal Developer Dashboard, go to Apps & Credentials > Sandbox > REST API apps, and select the name of your app.
- Go to Features > Accept payments.
- Select the Advanced Credit and Debit Card Payments checkbox and select Save Changes.
Set up npm
You'll need to have npm installed to run this sample application For more info, visit npm's documentation.
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 libraries | Description |
---|---|
Dotenv | This zero-dependency module separates your configuration and code by loading environment variables from an .env file into process.env . |
ejs | These templates help you deliver HTML markup using plain JavaScript. |
express | This lean Node.js web application framework supports web and mobile applications. |
node-fetch | This function helps you make API requests, similar to window.fetch . |
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":"paypal-js-advanced-integration-ib",3"description":"Sample Node.js web app to integrate PayPal Advanced Checkout for online payments",4"version":"1.0.1",5"main":"server/YOUR-SERVER-NAME.js",6"type":"module",7"scripts":{8"start":"node server/YOUR-SERVER-NAME.js",9},10"dependencies":{11"dotenv":"^16.3.1",12"express":"^4.18.2",13"node-fetch":"^3.3.2"14}15}
- Pass the name of your server file using
main
by replacing the defaultYOUR-SERVER-NAME.js
on lines 5 and 8. - Use
scripts.test
andscripts.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 following node error, include
"type": "module"
in yourpackage.json
file: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.
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:
1APP_SECRET="YOUR_SECRET_GOES_HERE"
Integrate front end
This section explains how to set up your front end to integrate advanced checkout payments.
Front-end process
- Your app shows PayPal payment buttons and card fields.
- Your app calls server endpoints to create the order and capture payment. The request details depend 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. Save theapp.js
file in a folder named/public
./server/views/checkout.ejs
is an Embedded JavaScript (EJS) file that builds the checkout page, adds the PayPal button, and loads theapp.js
file. Save thecheckout.ejs
file in a folder named/server/views
.
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.
The JavaScript file in the sample code includes reference routes on the server that you’ll add later.
- Include the
<script>
tag on any page that shows the PayPal buttons. This script will fetch all the necessary JavaScript to access the buttons on thewindow
object. - Pass a
client-id
and specify whichcomponents
you want to use. The SDK offers buttons, marks, card fields, and other components. This sample focuses on thebuttons
component. - In addition to passing the
client-id
and specifying which components you want to use, you can also pass the currency you want to use for pricing. In this example, USD is used.
1<!--checkout.ejs file-->2<!DOCTYPEhtml>3<htmllang="en">4<head>5<metacharset="UTF-8"/>6<metaname="viewport"content="width=device-width, initial-scale=1.0"/>7<linkrel="stylesheet"type="text/css"href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css"/>8<title>PayPal JS SDK Advanced Integration - Checkout Flow</title>9<scriptsrc="https://www.paypal.com/sdk/js?components=buttons,card-fields&client-id=<CLIENT_ID>&merchant-id=<MERCHANT_ID>¤cy=USD&intent=capture"data-partner-attribution-id="<BN_CODE>"data-client-token="<CLIENT_TOKEN>"></script>10</head>11<body>12<divid="paypal-button-container"class="paypal-button-container"></div>13<!-- Containers for card fields hosted by PayPal -->14<divid="card-form"class="card_container">15<divid="card-name-field-container"></div>16<divid="card-number-field-container"></div>17<divid="card-expiry-field-container"></div>18<divid="card-cvv-field-container"></div>19<div>20<labelfor="card-billing-address-line-1">Billing address line 1</label>21<inputtype="text"id="card-billing-address-line-1"name="card-billing-address-line-1"autocomplete="off"placeholder="Billing address line 1"/>22</div>23<div>24<labelfor="card-billing-address-line-2">Billing address line 2</label>25<inputtype="text"id="card-billing-address-line-2"name="card-billing-address-line-2"autocomplete="off"placeholder="Billing address line 2"/>26</div>27<div>28<labelfor="card-billing-address-admin-area-line-1">Admin area line 1</label>29<inputtype="text"id="card-billing-address-admin-area-line-1"name="card-billing-address-admin-area-line-1"autocomplete="off"placeholder="Admin area line 1"/>30</div>31<div>32<labelfor="card-billing-address-admin-area-line-2">Admin area line 2</label>33<inputtype="text"id="card-billing-address-admin-area-line-2"name="card-billing-address-admin-area-line-2"autocomplete="off"placeholder="Admin area line 2"/>34</div>35<div>36<labelfor="card-billing-address-country-code">Country code</label>37<inputtype="text"id="card-billing-address-country-code"name="card-billing-address-country-code"autocomplete="off"placeholder="Country code"/>38</div>39<div>40<labelfor="card-billing-address-postal-code">Postal/zip code</label>41<inputtype="text"id="card-billing-address-postal-code"name="card-billing-address-postal-code"autocomplete="off"placeholder="Postal/zip code"/>42</div>43<br/><br/>44<buttonid="card-field-submit-button"type="button">45 Pay now with Card46</button>47</div>48<pid="result-message"></p>49<scriptsrc="app.js"></script>50</body>51</html>
After setting up the SDK for your website, you need to render the PayPal buttons and the card field components.
Card fields
The PayPal namespace has a CardFields
component to accept and save cards without handling card information. PayPal handles all security and compliance issues associated with processing cards. The CardFields
function checks to see if a payment is eligible for card fields. If not, the card fields won't appear during the payment flow.
The /public/app.js
file needs a function that submits a createOrder()
request:
- Declare a
createOrder
callback that launches when the customer selects the payment button. The callback starts the order and returns an order ID. After the customer checks out using the PayPal pop-up, this order ID helps you to confirm when the payment is completed. - Set up your app to call
cardField.isEligible()
for each card field to determine if a payment is eligible for card fields. - Render each card field by declaring it as an object in
cardField
and then applying a.render()
function. - Create the order by calling the
createOrder
function. - Define styles for the card fields. You can change these styles for your implementation.
- Define the
selector
andplaceholder
values for the input fields. You can edit this section for your implementation, such as adding more fields. For more information about optional configurations, see Options in the JavaScript SDK reference. - Set up your app to capture the payment when it is eligible for card fields by adding code to the PayPal button in
/server/view/checkout.ejs
, right after rendering the card fields. - You need to declare an event listener
/public/app.js
for when the payer submits an eligible card payment. - Pass the card field values, such as the cardholder's name and address, to the
POST
call. Anything you pass into thesubmit
function is sent to the iframe that communicates with the Orders API. The iframe retrieves the data and sends it along to thePOST
call. See the Orders v2 API reference for details about billing address fields and other parameters. For example, use the 2-character country code to test the billing address.
Completing the payment launches an onApprove
callback, which sends a POST
call to /v2/checkout/orders/{id}/capture
to capture the order using the orderId in the data object in /server/server.js
. Use the onApprove
response to update business logic, show a celebration page, or handle error responses.
Set up your app to handle the payment capture response, such as when the payment is captured, when the payer's instrument is declined, or when there is an error:
- Include a function that shows a message to the user by passing data to the
result-message
HTML element container ofcheckout.ejs
. - Your app needs to call the JavaScript SDK that defines the PayPal card fields and links them to the
createOrder()
function. - If your website handles shipping physical items, see details about our shipping callbacks.
PayPal buttons
The paypal
namespace has a Buttons
function that initiates the callbacks needed to set up a payment. The app.js
file needs a function that submits a createOrder()
request.
- Declare a
createOrder
callback that launches when the customer selects the payment button. The callback starts the order and returns an order ID. After the customer checks out using the PayPal pop-up, this order ID helps you to confirm when the payment is completed. - Completing the payment launches an
onApprove
callback. Use theonApprove
response to update business logic, show a celebration page, or handle error responses. - Set up your app to handle the payment capture response, such as when the payment is captured, when the payer's instrument is declined, or when there is an error.
- Include a function that shows a message to the user by passing data to the
result-message
HTML element container ofcheckout.ejs
. - Your app must call the JavaScript SDK that defines the PayPal buttons and links them to the
createOrder()
function.
1// app.js file2// Render the button component3paypal4.Buttons({5// Sets up the transaction when a payment button is selected6createOrder: createOrderCallback,7onApprove: onApproveCallback,8onError:function(error){9// Do something with the error from the SDK10},11style:{12shape:"rect",13layout:"vertical",14color:"gold",15label:"paypal",16},17message:{18amount:100,19},20})21.render("#paypal-button-container");22// Render each field after checking for eligibility23const cardField =window.paypal.CardFields({24createOrder: createOrderCallback,25onApprove: onApproveCallback,26style:{27input:{28"font-size":"16px",29"font-family":"courier, monospace",30"font-weight":"lighter",31color:"#ccc",32},33".invalid":{34color:"purple"35},36// Optional to handle buyer checkout errors37onError:(err)=>{38// redirect to your error catch-all page39window.location.assign("/your-error-page-here");40},41},42});43if(cardField.isEligible()){44const nameField = cardField.NameField({45style:{46input:{47color:"blue"48},49".invalid":{50color:"purple"51}52},53});54 nameField.render("#card-name-field-container");55const numberField = cardField.NumberField({56style:{57input:{58color:"blue"59}60},61});62 numberField.render("#card-number-field-container");63const cvvField = cardField.CVVField({64style:{65input:{66color:"blue"67}68},69});70 cvvField.render("#card-cvv-field-container");71const expiryField = cardField.ExpiryField({72style:{73input:{74color:"blue"75}76},77});78 expiryField.render("#card-expiry-field-container");79// Add click listener to submit button and call the submit function on the CardField component80document81.getElementById("card-field-submit-button")82.addEventListener("click",()=>{83 cardField84.submit({85// From your billing address fields86billingAddress:{87addressLine1:document.getElementById(88"card-billing-address-line-1"89).value,90addressLine2:document.getElementById(91"card-billing-address-line-2"92).value,93adminArea1:document.getElementById(94"card-billing-address-admin-area-line-1"95).value,96adminArea2:document.getElementById(97"card-billing-address-admin-area-line-2"98).value,99countryCode:document.getElementById(100"card-billing-address-country-code"101).value,102postalCode:document.getElementById(103"card-billing-address-postal-code"104).value,105},106})107.then(()=>{108// submit successful109});110});111}112asyncfunctioncreateOrderCallback(){113resultMessage("");114try{115const response =awaitfetch("/api/orders",{116method:"POST",117headers:{118"Content-Type":"application/json",119},120// Use the "body" param to optionally pass additional order information121// like product ids and quantities122body:JSON.stringify({123cart:[{124id:"YOUR_PRODUCT_ID",125quantity:"YOUR_PRODUCT_QUANTITY",126},],127}),128});129const orderData =await response.json();130if(orderData.id){131return orderData.id;132}else{133const errorDetail = orderData?.details?.[0];134const errorMessage = errorDetail ?135`${errorDetail.issue}${errorDetail.description} (${orderData.debug_id})`:136JSON.stringify(orderData);137thrownewError(errorMessage);138}139}catch(error){140console.error(error);141resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);142}143}144asyncfunctiononApproveCallback(data, actions){145try{146const response =awaitfetch(`/api/orders/${data.orderID}/capture`,{147method:"POST",148headers:{149"Content-Type":"application/json",150},151});152const orderData =await response.json();153// Three cases to handle:154// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()155// (2) Other non-recoverable errors -> Show a failure message156// (3) Successful transaction -> Show confirmation or thank you message157const transaction =158 orderData?.purchase_units?.[0]?.payments?.captures?.[0]||159 orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];160const errorDetail = orderData?.details?.[0];161// Optional to handle funding failures162// This actions.restart() behavior only applies to the buttons component163if(164 errorDetail?.issue ==="INSTRUMENT_DECLINED"&&165!data.card&&166 actions167){168// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()169// recoverable state, per https://developer.paypal.com/docs/multiparty/checkout/standard/customize/handle-funding-failures/170return actions.restart();171}elseif(172// End optional failure handling173if(errorDetail ||!transaction || transaction.status==="DECLINED"){174// (2) Other non-recoverable errors -> Show a failure message175let errorMessage;176if(transaction){177 errorMessage =`Transaction ${transaction.status}: ${transaction.id}`;178}elseif(errorDetail){179 errorMessage =`${errorDetail.description} (${orderData.debug_id})`;180}else{181 errorMessage =JSON.stringify(orderData);182}183thrownewError(errorMessage);184}else{185// (3) Successful transaction -> Show confirmation or thank you message186// Or go to another URL: actions.redirect('thank_you.html');187resultMessage(188`Transaction ${transaction.status}: ${transaction.id}<br><br>See console for all available details`189);190console.log(191"Capture result",192 orderData,193JSON.stringify(orderData,null,2)194);195}196}catch(error){197console.error(error);198resultMessage(199`Sorry, your transaction could not be processed...<br><br>${error}`200);201}202}203// Example function to show a result to the user. Your site's UI library can be used instead.204functionresultMessage(message){205const container =document.querySelector("#result-message");206 container.innerHTML= message;207}
This section explains how to customize the PayPal buttons and card fields for your integration.
Configure card field layout
Copy and paste both examples of card field style objects into your existing /client/checkout.ejs
and /public/app.js
files.
- Include the required card form elements:
NumberField
,CVVField
, andExpiryField
. To learn about the available card form elements, see card fields. - Add your own fields to accept billing address information.
- A complete set of sample integration code is available from the GitHub repo.
- Optional: Change the layout, width, height, and outer styling of the card fields, such as
border
,box-shadow
, andbackground
. You can modify the elements you supply as containers.
Configure buttons layout
Depending on where you want these buttons to appear on your website, you can lay out the buttons in a horizontal or vertical stack. You can also customize the buttons with different colors and shapes.
To override the default style settings for your page, use a style
object inside the Buttons
component. Read more about how to customize your payment buttons in the style section of the JavaScript SDK reference page.
Integrate 3D Secure
To trigger 3D Secure authentication, pass the verification method in the Create order payload. The verification method can be a contingencies parameter with SCA_ALWAYS
or SCA_WHEN_REQUIRED
:
- Pass
SCA_ALWAYS
to trigger an authentication for every transaction. - Pass
SCA_WHEN_REQUIRED
to trigger an authentication only when required by a regional compliance mandate such as PSD2. 3D Secure is supported only in countries with a PSD2 compliance mandate.
Integrate back end
This section explains how to set up your back end to integrate advanced Checkout payments.
- Your app shows a customer the available payment methods on the server side.
- Your app creates an order on the back end by making a call to the Create Orders v2 API endpoint.
- 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.
1. Generate a PayPal-Auth-Assertion header
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.js234function encodeObjectToBase64(object) {5 const objectString = JSON.stringify(object);6 return Buffer7 .from(objectString)8 .toString("base64");9}101112const clientId = "CLIENT-ID";13const sellerPayerId = "SELLER-PAYER-ID"; // preferred14// const sellerEmail = "SELLER-ACCOUNT-EMAIL"; // use instead if payer-id unknown151617const header = {18 alg: "none"19};20const encodedHeader = encodeObjectToBase64(header);212223const payload = {24 iss: clientId,25 payer_id: sellerPayerId26 // email: sellerEmail27};28const encodedPayload = encodeObjectToBase64(payload);293031const jwt = `${encodedHeader}.${encodedPayload}.`; // json web token32console.log(`Paypal-Auth-Assertion=${jwt}`);
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.
2. Set up your back end
Set up API endpoints on your server to call the PayPal Orders v2 API.
Create API endpoints on your server that communicate with the Orders v2 API to create an order and capture payment for an order.
The following /server/server.js
code sample shows how to integrate the back-end code for advanced Checkout. The code adds routes to an Express server to create orders and capture payments using the Orders v2 API.
Save the server.js
file in a folder named /server
.
1// server.js file2import express from "express";3import fetch from "node-fetch";4import "dotenv/config";567const {8 BN_CODE,9 PAYPAL_CLIENT_ID,10 PAYPAL_CLIENT_SECRET,11 PORT = 888812} = process.env;13const base = "https://api-m.sandbox.paypal.com";14const app = express();151617app.set("view engine", "ejs");18app.set("views", "./server/views");19app.use(express.static("client"));202122// parse post params sent in body in json format23app.use(express.json());242526const SELLER_PAYER_ID = "" //Add Seller Payer ID272829/**30 * Generate an OAuth 2.0 access token for authenticating with PayPal REST APIs.31 * @see https://developer.paypal.com/api/rest/authentication/32 */33const generateAccessToken = async () => {34 try {35 if (!PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) {36 throw new Error("MISSING_API_CREDENTIALS");37 }38 const auth = Buffer.from(39 PAYPAL_CLIENT_ID + ":" + PAYPAL_CLIENT_SECRET,40 ).toString("base64");41 //const bn_code = BN_CODE;42 const response = await fetch(`${base}/v1/oauth2/token`, {43 method: "POST",44 body: "grant_type=client_credentials",45 headers: {46 Authorization: `Basic ${auth}`,47 "PayPal-Partner-Attribution-Id": BN_CODE,48 },49 });505152 const data = await response.json();53 return data.access_token;54 } catch (error) {55 console.error("Failed to generate Access Token:", error);56 }57};585960const authassertion = async () => {616263 function encodeObjectToBase64(object) {64 const objectString = JSON.stringify(object);65 return Buffer66 .from(objectString)67 .toString("base64");68 }; ]n69 const clientId = PAYPAL_CLIENT_ID;70 const sellerPayerId = SELLER_PAYER_ID; // preferred71 // const sellerEmail = "SELLER-ACCOUNT-EMAIL"; // use instead if payer-id unknown727374 const header = {75 alg: "none"76 };77 const encodedHeader = encodeObjectToBase64(header);787980 const payload = {81 iss: clientId,82 payer_id: sellerPayerId83 // email: sellerEmail84 };85 const encodedPayload = encodeObjectToBase64(payload);868788 const jwt = `${encodedHeader}.${encodedPayload}.`; // json web token89 //console.log(`Paypal-Auth-Assertion=${jwt}`);909192 return jwt;93};94959697/**98 * Generate a client token for rendering the hosted card fields.99 * @see https://developer.paypal.com/docs/checkout/advanced/integrate/#link-integratebackend100 */101const generateClientToken = async () => {102 const accessToken = await generateAccessToken();103 const url = `${base}/v1/identity/generate-token`;104 const response = await fetch(url, {105 method: "POST",106 headers: {107 Authorization: `Bearer ${accessToken}`,108 "Accept-Language": "en_US",109 "Content-Type": "application/json",110 "PayPal-Partner-Attribution-Id": BN_CODE,111 },112 });113114115 return handleResponse(response);116};117118119/**120 * Create an order to start the transaction.121 * @see https://developer.paypal.com/docs/api/orders/v2/#orders_create122 */123const createOrder = async (cart) => {124 // Use the cart information passed from the front-end to calculate the purchase unit details125 console.log(126 "shopping cart information passed from the frontend createOrder() callback:",127 cart,128 );129130131 const accessToken = await generateAccessToken();132 const auth_assertion = await authassertion();133 const url = `${base}/v2/checkout/orders`;134 const payload = {135 intent: "CAPTURE",136 purchase_units: [{137 amount: {138 currency_code: "USD",139 value: "100.00",140 },141 }, ],142 };143144145 const response = await fetch(url, {146 headers: {147 "Content-Type": "application/json",148 Authorization: `Bearer ${accessToken}`,149 "PayPal-Partner-Attribution-Id": BN_CODE,150 "PayPal-Auth-Assertion": `${auth_assertion}`,151 // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:152 // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/153 // "PayPal-Mock-Response": '{"mock_application_codes": "MISSING_REQUIRED_PARAMETER"}'154 // "PayPal-Mock-Response": '{"mock_application_codes": "PERMISSION_DENIED"}'155 // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'156 },157 method: "POST",158 body: JSON.stringify(payload),159 });160161162 return handleResponse(response);163};164165166/**167 * Capture payment for the created order to complete the transaction.168 * @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture169 */170const captureOrder = async (orderID) => {171 const accessToken = await generateAccessToken();172 const auth_assertion = await authassertion();173 const url = `${base}/v2/checkout/orders/${orderID}/capture`;174175176 const response = await fetch(url, {177 method: "POST",178 headers: {179 "Content-Type": "application/json",180 Authorization: `Bearer ${accessToken}`,181 "PayPal-Partner-Attribution-Id": BN_CODE,182 "PayPal-Auth-Assertion": `${auth_assertion}`,183 // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:184 // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/185 // "PayPal-Mock-Response": '{"mock_application_codes": "INSTRUMENT_DECLINED"}'186 // "PayPal-Mock-Response": '{"mock_application_codes": "TRANSACTION_REFUSED"}'187 // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'188 },189 });190191192 return handleResponse(response);193};194195196async function handleResponse(response) {197 try {198 const jsonResponse = await response.json();199 return {200 jsonResponse,201 httpStatusCode: response.status,202 };203 } catch (err) {204 const errorMessage = await response.text();205 throw new Error(errorMessage);206 }207}208209210// Render checkout page with client id & unique client token211app.get("/", async (req, res) => {212 try {213 const {214 jsonResponse215 } = await generateClientToken();216 res.render("checkout", {217 clientId: PAYPAL_CLIENT_ID,218 clientToken: jsonResponse.client_token,219 BN_CODE: BN_CODE,220 });221 } catch (err) {222 res.status(500).send(err.message);223 }224});225226227app.post("/api/orders", async (req, res) => {228 try {229 // Use the cart information passed from the front-end to calculate the order amount details230 const {231 cart232 } = req.body;233 const {234 jsonResponse,235 httpStatusCode236 } = await createOrder(cart);237 res.status(httpStatusCode).json(jsonResponse);238 } catch (error) {239 console.error("Failed to create order:", error);240 res.status(500).json({241 error: "Failed to create order."242 });243 }244});245246247app.post("/api/orders/:orderID/capture", async (req, res) => {248 try {249 const {250 orderID251 } = req.params;252 const {253 jsonResponse,254 httpStatusCode255 } = await captureOrder(orderID);256 res.status(httpStatusCode).json(jsonResponse);257 } catch (error) {258 console.error("Failed to create order:", error);259 res.status(500).json({260 error: "Failed to capture order."261 });262 }263});264265266app.listen(PORT, () => {267 console.log(`Node server listening at http://localhost:${PORT}/`);268});269270271272/** If needed, refund a captured payment to buyer **/273const refundCapturedPayment = async (capturedPaymentId) => {274 const accessToken = await generateAccessToken();275 const url = `${base}/v2/payments/captures/${capturedPaymentId}/refund`;276277278 const response = await fetch(url, {279 headers: {280 "Content-Type": "application/json",281 Authorization: `Bearer ${accessToken}`,282 },283 method: "POST",284 });285286287 return handleResponse(response);288};289290291// refundCapturedPayment route292app.post("/api/payments/refund", async (req, res) => {293 try {294 const {295 capturedPaymentId296 } = req.body;297 const {298 jsonResponse,299 httpStatusCode300 } = await refundCapturedPayment(301 capturedPaymentId302 );303 res.status(httpStatusCode).json(jsonResponse);304 } catch (error) {305 console.error("Failed refund captured payment:", error);306 res.status(500).json({307 error: "Failed refund captured payment."308 });309 }310});
Test integration
Before going live, test your integration in the sandbox environment.
Learn more about the following resources on the Card Testing page:
- Use test card numbers to simulate successful payments for advanced Checkout integrations.
- Use negative testing to simulate card error scenarios.
- Test 3D Secure authentication scenarios.
- Test your integration by following these recommended use cases. See the Orders v2 API for details about billing address fields and other parameters. For example, use the 2-character country code to test the billing address.
- Save payment methods so payers don't have to enter details for future transactions.
- Use webhooks for event notifications.
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.