Skip to content

Commit 0d27886

Browse files
committed
Refactor the backend for the advanced integration guide
1 parent ed5eb63 commit 0d27886

File tree

8 files changed

+167
-133
lines changed

8 files changed

+167
-133
lines changed

.devcontainer/advanced-integration/devcontainer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
},
2828

2929
"secrets": {
30-
"CLIENT_ID": {
30+
"PAYPAL_CLIENT_ID": {
3131
"description": "Sandbox client ID of the application.",
3232
"documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox"
3333
},
34-
"APP_SECRET": {
34+
"PAYPAL_CLIENT_SECRET": {
3535
"description": "Sandbox secret of the application.",
3636
"documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox"
3737
}

advanced-integration/.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Create an application to obtain credentials at
22
# https://developer.paypal.com/dashboard/applications/sandbox
33

4-
CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
5-
APP_SECRET="YOUR_SECRET_GOES_HERE"
4+
PAYPAL_CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
5+
PAYPAL_CLIENT_SECRET="YOUR_SECRET_GOES_HERE"

advanced-integration/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Instructions
44

5-
1. Rename `.env.example` to `.env` and update `CLIENT_ID` and `APP_SECRET`.
5+
1. Rename `.env.example` to `.env` and update `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET`.
66
2. Run `npm install`
77
3. Run `npm start`
88
4. Open http://localhost:8888

advanced-integration/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@paypalcorp/advanced-integration",
2+
"name": "paypal-advanced-integration",
33
"version": "1.0.0",
44
"description": "",
55
"main": "server.js",
@@ -11,9 +11,9 @@
1111
"author": "",
1212
"license": "Apache-2.0",
1313
"dependencies": {
14-
"dotenv": "^16.0.0",
15-
"ejs": "^3.1.6",
16-
"express": "^4.17.3",
17-
"node-fetch": "^3.2.1"
14+
"dotenv": "^16.3.1",
15+
"ejs": "^3.1.9",
16+
"express": "^4.18.2",
17+
"node-fetch": "^3.3.2"
1818
}
1919
}

advanced-integration/paypal-api.js

Lines changed: 0 additions & 98 deletions
This file was deleted.

advanced-integration/server.js

Lines changed: 155 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,178 @@
1-
import "dotenv/config";
2-
import express from "express";
3-
import * as paypal from "./paypal-api.js";
4-
const {PORT = 8888} = process.env;
1+
import express from 'express';
2+
import fetch from 'node-fetch';
3+
import 'dotenv/config';
4+
import path from 'path';
55

6+
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env;
7+
const base = 'https://api-m.sandbox.paypal.com';
68
const app = express();
79
app.set("view engine", "ejs");
810
app.use(express.static("public"));
911

12+
// parse post params sent in body in json format
13+
app.use(express.json());
14+
15+
/**
16+
* Generate an OAuth 2.0 access token for authenticating with PayPal REST APIs.
17+
* @see https://developer.paypal.com/api/rest/authentication/
18+
*/
19+
const generateAccessToken = async () => {
20+
try {
21+
if (!PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) {
22+
throw new Error('MISSING_API_CREDENTIALS');
23+
}
24+
const auth = Buffer.from(
25+
PAYPAL_CLIENT_ID + ':' + PAYPAL_CLIENT_SECRET,
26+
).toString('base64');
27+
const response = await fetch(`${base}/v1/oauth2/token`, {
28+
method: 'POST',
29+
body: 'grant_type=client_credentials',
30+
headers: {
31+
Authorization: `Basic ${auth}`,
32+
},
33+
});
34+
35+
const data = await response.json();
36+
return data.access_token;
37+
} catch (error) {
38+
console.error('Failed to generate Access Token:', error);
39+
}
40+
};
41+
42+
/**
43+
* Generate a client token for rendering the hosted card fields.
44+
* @see https://developer.paypal.com/docs/checkout/advanced/integrate/#link-integratebackend
45+
*/
46+
const 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+
});
57+
58+
return handleResponse(response);
59+
};
60+
61+
/**
62+
* Create an order to start the transaction.
63+
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_create
64+
*/
65+
const createOrder = async (cart) => {
66+
// use the cart information passed from the front-end to calculate the purchase unit details
67+
console.log(
68+
'shopping cart information passed from the frontend createOrder() callback:',
69+
cart,
70+
);
71+
72+
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: '0.02',
81+
},
82+
},
83+
],
84+
};
85+
86+
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+
});
99+
100+
return handleResponse(response);
101+
};
102+
103+
/**
104+
* Capture payment for the created order to complete the transaction.
105+
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture
106+
*/
107+
const captureOrder = async (orderID) => {
108+
const accessToken = await generateAccessToken();
109+
const url = `${base}/v2/checkout/orders/${orderID}/capture`;
110+
111+
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+
});
123+
124+
return handleResponse(response);
125+
};
126+
127+
async function handleResponse(response) {
128+
try {
129+
const jsonResponse = await response.json();
130+
return {
131+
jsonResponse,
132+
httpStatusCode: response.status,
133+
};
134+
} catch (err) {
135+
const errorMessage = await response.text();
136+
throw new Error(errorMessage);
137+
}
138+
}
139+
10140
// render checkout page with client id & unique client token
11141
app.get("/", async (req, res) => {
12-
const clientId = process.env.CLIENT_ID;
13142
try {
14-
const clientToken = await paypal.generateClientToken();
15-
res.render("checkout", { clientId, clientToken });
143+
const { jsonResponse } = await generateClientToken();
144+
res.render("checkout", {
145+
clientId: PAYPAL_CLIENT_ID,
146+
clientToken: jsonResponse.client_token
147+
});
16148
} catch (err) {
17149
res.status(500).send(err.message);
18150
}
19151
});
20152

21-
// create order
22-
app.post("/api/orders", async (req, res) => {
153+
app.post('/api/orders', async (req, res) => {
23154
try {
24-
const order = await paypal.createOrder();
25-
res.json(order);
26-
} catch (err) {
27-
res.status(500).send(err.message);
155+
// use the cart information passed from the front-end to calculate the order amount detals
156+
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.' });
28162
}
29163
});
30164

31-
// capture payment
32-
app.post("/api/orders/:orderID/capture", async (req, res) => {
33-
const { orderID } = req.params;
165+
app.post('/api/orders/:orderID/capture', async (req, res) => {
34166
try {
35-
const captureData = await paypal.capturePayment(orderID);
36-
res.json(captureData);
37-
} catch (err) {
38-
res.status(500).send(err.message);
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.' });
39173
}
40174
});
41175

42176
app.listen(PORT, () => {
43-
console.log(`Server listening at http://localhost:${PORT}/`);
177+
console.log(`Node server listening at http://localhost:${PORT}/`);
44178
});

standard-integration/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@paypalcorp/standard-integration",
2+
"name": "paypal-standard-integration",
33
"version": "1.0.0",
44
"main": "paypal-api.js",
55
"type": "module",

standard-integration/server.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import fetch from 'node-fetch';
33
import 'dotenv/config';
44
import path from 'path';
55

6-
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET } = process.env;
6+
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env;
77
const base = 'https://api-m.sandbox.paypal.com';
88
const app = express();
99

@@ -144,8 +144,6 @@ app.get('/', (req, res) => {
144144
res.sendFile(path.resolve('./index.html'));
145145
});
146146

147-
const PORT = Number(process.env.PORT) || 8888;
148-
149147
app.listen(PORT, () => {
150148
console.log(`Node server listening at http://localhost:${PORT}/`);
151149
});

0 commit comments

Comments
 (0)