Skip to content

Commit cb2f6a9

Browse files
authored
fix: temporarily revert back to old code examples for advanced (#78)
1 parent 1d0216d commit cb2f6a9

File tree

10 files changed

+442
-2
lines changed

10 files changed

+442
-2
lines changed

advanced-integration/new/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Advanced Integration Example
2+
3+
This folder contains example code for an Advanced PayPal integration using both the JS SDK and Node.js to complete transactions with the PayPal REST API.
4+
5+
## Instructions
6+
7+
1. Rename `.env.example` to `.env` and update `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET`.
8+
2. Run `npm install`
9+
3. Run `npm start`
10+
4. Open http://localhost:8888
11+
5. Enter the credit card number provided from one of your [sandbox accounts](https://developer.paypal.com/dashboard/accounts) or [generate a new credit card](https://developer.paypal.com/dashboard/creditCardGenerator)
File renamed without changes.

advanced-integration/new/package.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "paypal-advanced-integration",
3+
"description": "Sample Node.js web app to integrate PayPal Advanced Checkout for online payments",
4+
"version": "1.0.0",
5+
"main": "server/server.js",
6+
"type": "module",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1",
9+
"start": "nodemon server/server.js",
10+
"format": "npx prettier --write **/*.{js,md}",
11+
"format:check": "npx prettier --check **/*.{js,md}",
12+
"lint": "npx eslint server/*.js --env=node && npx eslint client/*.js --env=browser"
13+
},
14+
"license": "Apache-2.0",
15+
"dependencies": {
16+
"dotenv": "^16.3.1",
17+
"ejs": "^3.1.9",
18+
"express": "^4.18.2",
19+
"node-fetch": "^3.3.2"
20+
},
21+
"devDependencies": {
22+
"nodemon": "^3.0.1"
23+
}
24+
}

advanced-integration/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"type": "module",
77
"scripts": {
88
"test": "echo \"Error: no test specified\" && exit 1",
9-
"start": "nodemon server/server.js",
9+
"start": "nodemon server.js",
1010
"format": "npx prettier --write **/*.{js,md}",
1111
"format:check": "npx prettier --check **/*.{js,md}",
12-
"lint": "npx eslint server/*.js --env=node && npx eslint client/*.js --env=browser"
12+
"lint": "npx eslint server.js paypal-api.js --env=node && npx eslint public/*.js --env=browser"
1313
},
1414
"license": "Apache-2.0",
1515
"dependencies": {

advanced-integration/paypal-api.js

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import fetch from "node-fetch";
2+
3+
// set some important variables
4+
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET } = process.env;
5+
const base = "https://api-m.sandbox.paypal.com";
6+
7+
/**
8+
* Create an order
9+
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_create
10+
*/
11+
export async function createOrder() {
12+
const purchaseAmount = "100.00"; // TODO: pull prices from a database
13+
const accessToken = await generateAccessToken();
14+
const url = `${base}/v2/checkout/orders`;
15+
const response = await fetch(url, {
16+
method: "post",
17+
headers: {
18+
"Content-Type": "application/json",
19+
Authorization: `Bearer ${accessToken}`,
20+
},
21+
body: JSON.stringify({
22+
intent: "CAPTURE",
23+
purchase_units: [
24+
{
25+
amount: {
26+
currency_code: "USD",
27+
value: purchaseAmount,
28+
},
29+
},
30+
],
31+
}),
32+
});
33+
34+
return handleResponse(response);
35+
}
36+
37+
/**
38+
* Capture payment for an order
39+
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture
40+
*/
41+
export async function capturePayment(orderId) {
42+
const accessToken = await generateAccessToken();
43+
const url = `${base}/v2/checkout/orders/${orderId}/capture`;
44+
const response = await fetch(url, {
45+
method: "post",
46+
headers: {
47+
"Content-Type": "application/json",
48+
Authorization: `Bearer ${accessToken}`,
49+
},
50+
});
51+
52+
return handleResponse(response);
53+
}
54+
55+
/**
56+
* Generate an OAuth 2.0 access token
57+
* @see https://developer.paypal.com/api/rest/authentication/
58+
*/
59+
export async function generateAccessToken() {
60+
const auth = Buffer.from(
61+
PAYPAL_CLIENT_ID + ":" + PAYPAL_CLIENT_SECRET,
62+
).toString("base64");
63+
const response = await fetch(`${base}/v1/oauth2/token`, {
64+
method: "post",
65+
body: "grant_type=client_credentials",
66+
headers: {
67+
Authorization: `Basic ${auth}`,
68+
},
69+
});
70+
const jsonData = await handleResponse(response);
71+
return jsonData.access_token;
72+
}
73+
74+
/**
75+
* Generate a client token
76+
* @see https://developer.paypal.com/docs/checkout/advanced/integrate/#link-sampleclienttokenrequest
77+
*/
78+
export async function generateClientToken() {
79+
const accessToken = await generateAccessToken();
80+
const response = await fetch(`${base}/v1/identity/generate-token`, {
81+
method: "post",
82+
headers: {
83+
Authorization: `Bearer ${accessToken}`,
84+
"Accept-Language": "en_US",
85+
"Content-Type": "application/json",
86+
},
87+
});
88+
console.log("response", response.status);
89+
const jsonData = await handleResponse(response);
90+
return jsonData.client_token;
91+
}
92+
93+
async function handleResponse(response) {
94+
if (response.status === 200 || response.status === 201) {
95+
return response.json();
96+
}
97+
98+
const errorMessage = await response.text();
99+
throw new Error(errorMessage);
100+
}

advanced-integration/public/app.js

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
window.paypal
2+
.Buttons({
3+
// Sets up the transaction when a payment button is clicked
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(`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+
},
45+
})
46+
.render("#paypal-button-container");
47+
48+
// If this returns false or the card fields aren't visible, see Step #1.
49+
if (window.paypal.HostedFields.isEligible()) {
50+
let orderId;
51+
52+
// Renders card fields
53+
window.paypal.HostedFields.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; // needed later to complete capture
72+
return orderData.id;
73+
});
74+
},
75+
styles: {
76+
".valid": {
77+
color: "green",
78+
},
79+
".invalid": {
80+
color: "red",
81+
},
82+
},
83+
fields: {
84+
number: {
85+
selector: "#card-number",
86+
placeholder: "4111 1111 1111 1111",
87+
},
88+
cvv: {
89+
selector: "#cvv",
90+
placeholder: "123",
91+
},
92+
expirationDate: {
93+
selector: "#expiration-date",
94+
placeholder: "MM/YY",
95+
},
96+
},
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+
});
157+
} else {
158+
// Hides card fields if the merchant isn't eligible
159+
document.querySelector("#card-form").style = "display: none";
160+
}

advanced-integration/server.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import "dotenv/config";
2+
import express from "express";
3+
import * as paypal from "./paypal-api.js";
4+
const { PORT = 8888 } = process.env;
5+
6+
const app = express();
7+
app.set("view engine", "ejs");
8+
app.use(express.static("public"));
9+
10+
// render checkout page with client id & unique client token
11+
app.get("/", async (req, res) => {
12+
const clientId = process.env.PAYPAL_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+
});
20+
21+
// create order
22+
app.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+
});
30+
31+
// capture payment
32+
app.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+
});
41+
42+
app.listen(PORT, () => {
43+
console.log(`Server listening at http://localhost:${PORT}/`);
44+
});

0 commit comments

Comments
 (0)