This is a breakdown of the /server/server.js
code sample.
Declare imports
This section of code imports the dependencies for your Node.js application:
1import express from "express";2import fetch from "node-fetch";3import "dotenv/config";
- Line 1 imports the Express.js framework to get started with creating a web server.
- Line 2 imports a
node-fetch
dependency needed to makehttp
requests to PayPal REST APIs. - Line 3 imports the dotenv library needed for loading sensitive data from environment variables, such as a PayPal client secret.
Set up port and server
This section of code collects the environment variables, sets up the port to run your server, and sets the base sandbox URL:
5const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env;6const base = "https://api-m.sandbox.paypal.com";
Lines 7-10 start the express Node.js web application framework:
7const app = express();8app.set("view engine", "ejs");9app.set("views", "./server/views");10app.use(express.static("client"));
Line 8 declares that the rendering engine uses Embedded JavaScript templates.
Generate client token
A client token uniquely identifies your payer. You need a client token for the payer so your app can use the hosted card fields. The following code sample creates a client token for you by making a POST
call to the /v1/identity/generate-token
endpoint:
42/**43 * Generate a client token for rendering the hosted card fields.44 * See https://developer.paypal.com/docs/checkout/advanced/sdk/v1/#link-integratebackend45 */46const generateClientToken = async () => {47 const accessToken = await generateAccessToken();48 const url = `${base}/v1/identity/generate-token`;49 const response = await fetch(url, {50 method: "POST",51 headers: {52 Authorization: `Bearer ${accessToken}`,53 "Accept-Language": "en_US",54 "Content-Type": "application/json",55 },56 });5758 return handleResponse(response);59};
This code runs your app on localhost
, using the port 8888
that you set up in line 5 of the API call:
176app.listen(PORT, () => {177 console.log(`Node server listening at http://localhost:${PORT}/`);178});
Render checkout page
Set up your app's checkout page to load this section of code when it's rendered by your server:
140// render checkout page with client id & unique client token141app.get("/", async (req, res) => {142 try {143 const { jsonResponse } = await generateClientToken();144 res.render("checkout", {145 clientId: PAYPAL_CLIENT_ID,146 clientToken: jsonResponse.client_token,147 });148 } catch (err) {149 res.status(500).send(err.message);150 }151});152153app.post("/api/orders", async (req, res) => {154 try {155 // use the cart information passed from the front-end to calculate the order amount details156 const { cart } = req.body;157 const { jsonResponse, httpStatusCode } = await createOrder(cart);158 res.status(httpStatusCode).json(jsonResponse);159 } catch (error) {160 console.error("Failed to create order:", error);161 res.status(500).json({ error: "Failed to create order." });162 }163});164165app.post("/api/orders/:orderID/capture", async (req, res) => {166 try {167 const { orderID } = req.params;168 const { jsonResponse, httpStatusCode } = await captureOrder(orderID);169 res.status(httpStatusCode).json(jsonResponse);170 } catch (error) {171 console.error("Failed to create order:", error);172 res.status(500).json({ error: "Failed to capture order." });173 }174});
- Line 143 generates a client token by calling the
generateClientToken()
function. - Lines 144-147 render the checkout page, pass the
clientId
andclientToken
to the page, and inject the value into the script tag.
Run createOrder()
This section of code defines a createOrder()
callback function for the Create orders endpoint of the Orders v2 API. Call the function when the payment is submitted, for example, when the payer selects the PayPal button, or submits a card payment. See the /client/app.js
section for more information.
This createOrder()
callback returns a promise that contains the orderID
. You can customize this callback for many use cases, such as a physical inventory for a cart, or a digital good with a fixed amount.
Important: To keep your integration secure, don't pass dollar amounts in your finished integration. Define your purchase_units
payload on the server side and pass the cart
array from the front-end to the server.
61/**62 * Create an order to start the transaction.63 * See https://developer.paypal.com/docs/api/orders/v2/#orders_create64 */65const createOrder = async (cart) => {66 // use the cart information passed from the front-end to calculate the purchase unit details67 console.log(68 "shopping cart information passed from the frontend createOrder() callback:",69 cart,70 );7172 const accessToken = await generateAccessToken();73 const url = `${base}/v2/checkout/orders`;74 const payload = {75 intent: "CAPTURE",76 purchase_units: [77 {78 amount: {79 currency_code: "USD",80 value: "100.00",81 },82 },83 ],84 };8586 const response = await fetch(url, {87 headers: {88 "Content-Type": "application/json",89 Authorization: `Bearer ${accessToken}`,90 // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:91 // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/92 // "PayPal-Mock-Response": '{"mock_application_codes": "MISSING_REQUIRED_PARAMETER"}'93 // "PayPal-Mock-Response": '{"mock_application_codes": "PERMISSION_DENIED"}'94 // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'95 },96 method: "POST",97 body: JSON.stringify(payload),98 });99100 return handleResponse(response);101};
- Line 65 passes information from the
cart
into thecreateOrder
function. - Line 72 checks that you have the correct access token.
- Line 73 sets up the
url
for the API call. - Lines 74-84 pass the
intent
andpurchase_units
to an object calledpayload
. This gets passed to the API call. - Lines 86-98 create an order by sending a
POST
request to the Create orders endpoint of the Orders v2 API, usingaccessToken
,url
, and thepayload
from lines 74-84. - Line 97 converts the
payload
information into JSON format and passes it into thebody
section of the request.
Negative testing for createOrder()
This section of the createOrder()
function provides 3 negative testing opportunities behind content tags:
MISSING_REQUIRED_PARAMETER
PERMISSION_DENIED
INTERNAL_SERVER_ERROR
Remove the //
from the beginning of a line to simulate that error. the createOrder()
call will pass that line as a PayPal-Mock-Response
header in the POST
request to the Orders v2 API:
90// Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:91// https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/92// "PayPal-Mock-Response": '{"mock_application_codes": "MISSING_REQUIRED_PARAMETER"}'93// "PayPal-Mock-Response": '{"mock_application_codes": "PERMISSION_DENIED"}'94// "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'
Run captureOrder()
This section of code defines the captureOrder()
function. Call the function to capture the order and move money from the payer's payment method to the seller:
103/**104 * Capture payment for the created order to complete the transaction.105 * See https://developer.paypal.com/docs/api/orders/v2/#orders_capture106 */107const captureOrder = async (orderID) => {108 const accessToken = await generateAccessToken();109 const url = `${base}/v2/checkout/orders/${orderID}/capture`;110111 const response = await fetch(url, {112 method: "POST",113 headers: {114 "Content-Type": "application/json",115 Authorization: `Bearer ${accessToken}`,116 // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:117 // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/118 // "PayPal-Mock-Response": '{"mock_application_codes": "INSTRUMENT_DECLINED"}'119 // "PayPal-Mock-Response": '{"mock_application_codes": "TRANSACTION_REFUSED"}'120 // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'121 },122 });123124 return handleResponse(response);125};
- Line 107 passes the
orderID
from the request parameters into thecreateOrder
function. - Line 108 checks that you have the correct access token.
- Line 109 sets up the
url
for the API call, which includes theorderID
. - Lines 111-122 capture an order by sending a
POST
request to the Capture payment endpoint of the Orders v2 API, usingaccessToken
andurl
.
See /client/app.js
in the next section for more details on capturing payments from the front-end.
Negative testing for captureOrder()
This section of the captureOrder()
function provides 3 negative testing opportunities behind content tags:
INSTRUMENT_DECLINED
TRANSACTION_REFUSED
INTERNAL_SERVER_ERROR
Remove the //
from the beginning of a line to simulate that error. The captureOrder()
call will pass that line as a PayPal-Mock-Response
header in the POST
request to the Capture payment endpoint of the Orders v2 API:
116// Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:117// https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/118// "PayPal-Mock-Response": '{"mock_application_codes": "INSTRUMENT_DECLINED"}'119// "PayPal-Mock-Response": '{"mock_application_codes": "TRANSACTION_REFUSED"}'120// "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'
Test server
Run npm start
to confirm your setup is correct.
This command starts the server on localhost:8888
. You can specify a port number by modifying line 4 in the sample /server/server.js
file.