1
2import express from "express";
3import fetch from "node-fetch";
4import "dotenv/config";
5
6
7const {
8 BN_CODE,
9 PAYPAL_CLIENT_ID,
10 PAYPAL_CLIENT_SECRET,
11 PORT = 8888
12} = process.env;
13const base = "https://api-m.sandbox.paypal.com";
14const app = express();
15
16
17app.set("view engine", "ejs");
18app.set("views", "./server/views");
19app.use(express.static("client"));
20
21
22
23app.use(express.json());
24
25
26const SELLER_PAYER_ID = ""
27
28
29
30
31
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
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 });
50
51
52 const data = await response.json();
53 return data.access_token;
54 } catch (error) {
55 console.error("Failed to generate Access Token:", error);
56 }
57};
58
59
60const authassertion = async () => {
61
62
63 function encodeObjectToBase64(object) {
64 const objectString = JSON.stringify(object);
65 return Buffer
66 .from(objectString)
67 .toString("base64");
68 }; ]n
69 const clientId = PAYPAL_CLIENT_ID;
70 const sellerPayerId = SELLER_PAYER_ID;
71
72
73
74 const header = {
75 alg: "none"
76 };
77 const encodedHeader = encodeObjectToBase64(header);
78
79
80 const payload = {
81 iss: clientId,
82 payer_id: sellerPayerId
83
84 };
85 const encodedPayload = encodeObjectToBase64(payload);
86
87
88 const jwt = `${encodedHeader}.${encodedPayload}.`;
89
90
91
92 return jwt;
93};
94
95
96
97
98
99
100
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 });
113
114
115 return handleResponse(response);
116};
117
118
119
120
121
122
123const createOrder = async (cart) => {
124
125 console.log(
126 "shopping cart information passed from the frontend createOrder() callback:",
127 cart,
128 );
129
130
131 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 };
143
144
145 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
152
153
154
155
156 },
157 method: "POST",
158 body: JSON.stringify(payload),
159 });
160
161
162 return handleResponse(response);
163};
164
165
166
167
168
169
170const captureOrder = async (orderID) => {
171 const accessToken = await generateAccessToken();
172 const auth_assertion = await authassertion();
173 const url = `${base}/v2/checkout/orders/${orderID}/capture`;
174
175
176 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
184
185
186
187
188 },
189 });
190
191
192 return handleResponse(response);
193};
194
195
196async 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}
208
209
210
211app.get("/", async (req, res) => {
212 try {
213 const {
214 jsonResponse
215 } = 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});
225
226
227app.post("/api/orders", async (req, res) => {
228 try {
229
230 const {
231 cart
232 } = req.body;
233 const {
234 jsonResponse,
235 httpStatusCode
236 } = 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});
245
246
247app.post("/api/orders/:orderID/capture", async (req, res) => {
248 try {
249 const {
250 orderID
251 } = req.params;
252 const {
253 jsonResponse,
254 httpStatusCode
255 } = 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});
264
265
266app.listen(PORT, () => {
267 console.log(`Node server listening at http://localhost:${PORT}/`);
268});
269
270
271
272
273const refundCapturedPayment = async (capturedPaymentId) => {
274 const accessToken = await generateAccessToken();
275 const url = `${base}/v2/payments/captures/${capturedPaymentId}/refund`;
276
277
278 const response = await fetch(url, {
279 headers: {
280 "Content-Type": "application/json",
281 Authorization: `Bearer ${accessToken}`,
282 },
283 method: "POST",
284 });
285
286
287 return handleResponse(response);
288};
289
290
291
292app.post("/api/payments/refund", async (req, res) => {
293 try {
294 const {
295 capturedPaymentId
296 } = req.body;
297 const {
298 jsonResponse,
299 httpStatusCode
300 } = await refundCapturedPayment(
301 capturedPaymentId
302 );
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});