-
Notifications
You must be signed in to change notification settings - Fork 498
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: temporarily revert back to old code examples for advanced
- Loading branch information
Showing
10 changed files
with
442 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Advanced Integration Example | ||
|
||
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. | ||
|
||
## Instructions | ||
|
||
1. Rename `.env.example` to `.env` and update `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET`. | ||
2. Run `npm install` | ||
3. Run `npm start` | ||
4. Open http://localhost:8888 | ||
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "paypal-advanced-integration", | ||
"description": "Sample Node.js web app to integrate PayPal Advanced Checkout for online payments", | ||
"version": "1.0.0", | ||
"main": "server/server.js", | ||
"type": "module", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"start": "nodemon server/server.js", | ||
"format": "npx prettier --write **/*.{js,md}", | ||
"format:check": "npx prettier --check **/*.{js,md}", | ||
"lint": "npx eslint server/*.js --env=node && npx eslint client/*.js --env=browser" | ||
}, | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"dotenv": "^16.3.1", | ||
"ejs": "^3.1.9", | ||
"express": "^4.18.2", | ||
"node-fetch": "^3.3.2" | ||
}, | ||
"devDependencies": { | ||
"nodemon": "^3.0.1" | ||
} | ||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import fetch from "node-fetch"; | ||
|
||
// set some important variables | ||
const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET } = process.env; | ||
const base = "https://api-m.sandbox.paypal.com"; | ||
|
||
/** | ||
* Create an order | ||
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_create | ||
*/ | ||
export async function createOrder() { | ||
const purchaseAmount = "100.00"; // TODO: pull prices from a database | ||
const accessToken = await generateAccessToken(); | ||
const url = `${base}/v2/checkout/orders`; | ||
const response = await fetch(url, { | ||
method: "post", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${accessToken}`, | ||
}, | ||
body: JSON.stringify({ | ||
intent: "CAPTURE", | ||
purchase_units: [ | ||
{ | ||
amount: { | ||
currency_code: "USD", | ||
value: purchaseAmount, | ||
}, | ||
}, | ||
], | ||
}), | ||
}); | ||
|
||
return handleResponse(response); | ||
} | ||
|
||
/** | ||
* Capture payment for an order | ||
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture | ||
*/ | ||
export async function capturePayment(orderId) { | ||
const accessToken = await generateAccessToken(); | ||
const url = `${base}/v2/checkout/orders/${orderId}/capture`; | ||
const response = await fetch(url, { | ||
method: "post", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${accessToken}`, | ||
}, | ||
}); | ||
|
||
return handleResponse(response); | ||
} | ||
|
||
/** | ||
* Generate an OAuth 2.0 access token | ||
* @see https://developer.paypal.com/api/rest/authentication/ | ||
*/ | ||
export async function generateAccessToken() { | ||
const auth = Buffer.from( | ||
PAYPAL_CLIENT_ID + ":" + PAYPAL_CLIENT_SECRET, | ||
).toString("base64"); | ||
const response = await fetch(`${base}/v1/oauth2/token`, { | ||
method: "post", | ||
body: "grant_type=client_credentials", | ||
headers: { | ||
Authorization: `Basic ${auth}`, | ||
}, | ||
}); | ||
const jsonData = await handleResponse(response); | ||
return jsonData.access_token; | ||
} | ||
|
||
/** | ||
* Generate a client token | ||
* @see https://developer.paypal.com/docs/checkout/advanced/integrate/#link-sampleclienttokenrequest | ||
*/ | ||
export async function generateClientToken() { | ||
const accessToken = await generateAccessToken(); | ||
const response = await fetch(`${base}/v1/identity/generate-token`, { | ||
method: "post", | ||
headers: { | ||
Authorization: `Bearer ${accessToken}`, | ||
"Accept-Language": "en_US", | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
console.log("response", response.status); | ||
const jsonData = await handleResponse(response); | ||
return jsonData.client_token; | ||
} | ||
|
||
async function handleResponse(response) { | ||
if (response.status === 200 || response.status === 201) { | ||
return response.json(); | ||
} | ||
|
||
const errorMessage = await response.text(); | ||
throw new Error(errorMessage); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
window.paypal | ||
.Buttons({ | ||
// Sets up the transaction when a payment button is clicked | ||
createOrder: function () { | ||
return fetch("/api/orders", { | ||
method: "post", | ||
// use the "body" param to optionally pass additional order information | ||
// like product skus and quantities | ||
body: JSON.stringify({ | ||
cart: [ | ||
{ | ||
sku: "<YOUR_PRODUCT_STOCK_KEEPING_UNIT>", | ||
quantity: "<YOUR_PRODUCT_QUANTITY>", | ||
}, | ||
], | ||
}), | ||
}) | ||
.then((response) => response.json()) | ||
.then((order) => order.id); | ||
}, | ||
// Finalize the transaction after payer approval | ||
onApprove: function (data) { | ||
return fetch(`/api/orders/${data.orderID}/capture`, { | ||
method: "post", | ||
}) | ||
.then((response) => response.json()) | ||
.then((orderData) => { | ||
// Successful capture! For dev/demo purposes: | ||
console.log( | ||
"Capture result", | ||
orderData, | ||
JSON.stringify(orderData, null, 2), | ||
); | ||
const transaction = orderData.purchase_units[0].payments.captures[0]; | ||
alert(`Transaction ${transaction.status}: ${transaction.id} | ||
See console for all available details | ||
`); | ||
// When ready to go live, remove the alert and show a success message within this page. For example: | ||
// var element = document.getElementById('paypal-button-container'); | ||
// element.innerHTML = '<h3>Thank you for your payment!</h3>'; | ||
// Or go to another URL: actions.redirect('thank_you.html'); | ||
}); | ||
}, | ||
}) | ||
.render("#paypal-button-container"); | ||
|
||
// If this returns false or the card fields aren't visible, see Step #1. | ||
if (window.paypal.HostedFields.isEligible()) { | ||
let orderId; | ||
|
||
// Renders card fields | ||
window.paypal.HostedFields.render({ | ||
// Call your server to set up the transaction | ||
createOrder: () => { | ||
return fetch("/api/orders", { | ||
method: "post", | ||
// use the "body" param to optionally pass additional order information | ||
// like product skus and quantities | ||
body: JSON.stringify({ | ||
cart: [ | ||
{ | ||
sku: "<YOUR_PRODUCT_STOCK_KEEPING_UNIT>", | ||
quantity: "<YOUR_PRODUCT_QUANTITY>", | ||
}, | ||
], | ||
}), | ||
}) | ||
.then((res) => res.json()) | ||
.then((orderData) => { | ||
orderId = orderData.id; // needed later to complete capture | ||
return orderData.id; | ||
}); | ||
}, | ||
styles: { | ||
".valid": { | ||
color: "green", | ||
}, | ||
".invalid": { | ||
color: "red", | ||
}, | ||
}, | ||
fields: { | ||
number: { | ||
selector: "#card-number", | ||
placeholder: "4111 1111 1111 1111", | ||
}, | ||
cvv: { | ||
selector: "#cvv", | ||
placeholder: "123", | ||
}, | ||
expirationDate: { | ||
selector: "#expiration-date", | ||
placeholder: "MM/YY", | ||
}, | ||
}, | ||
}).then((cardFields) => { | ||
document.querySelector("#card-form").addEventListener("submit", (event) => { | ||
event.preventDefault(); | ||
cardFields | ||
.submit({ | ||
// Cardholder's first and last name | ||
cardholderName: document.getElementById("card-holder-name").value, | ||
// Billing Address | ||
billingAddress: { | ||
// Street address, line 1 | ||
streetAddress: document.getElementById( | ||
"card-billing-address-street", | ||
).value, | ||
// Street address, line 2 (Ex: Unit, Apartment, etc.) | ||
extendedAddress: document.getElementById( | ||
"card-billing-address-unit", | ||
).value, | ||
// State | ||
region: document.getElementById("card-billing-address-state").value, | ||
// City | ||
locality: document.getElementById("card-billing-address-city") | ||
.value, | ||
// Postal Code | ||
postalCode: document.getElementById("card-billing-address-zip") | ||
.value, | ||
// Country Code | ||
countryCodeAlpha2: document.getElementById( | ||
"card-billing-address-country", | ||
).value, | ||
}, | ||
}) | ||
.then(() => { | ||
fetch(`/api/orders/${orderId}/capture`, { | ||
method: "post", | ||
}) | ||
.then((res) => res.json()) | ||
.then((orderData) => { | ||
// Two cases to handle: | ||
// (1) Other non-recoverable errors -> Show a failure message | ||
// (2) Successful transaction -> Show confirmation or thank you | ||
// This example reads a v2/checkout/orders capture response, propagated from the server | ||
// You could use a different API or structure for your 'orderData' | ||
const errorDetail = | ||
Array.isArray(orderData.details) && orderData.details[0]; | ||
if (errorDetail) { | ||
var msg = "Sorry, your transaction could not be processed."; | ||
if (errorDetail.description) | ||
msg += "\n\n" + errorDetail.description; | ||
if (orderData.debug_id) msg += " (" + orderData.debug_id + ")"; | ||
return alert(msg); // Show a failure message | ||
} | ||
// Show a success message or redirect | ||
alert("Transaction completed!"); | ||
}); | ||
}) | ||
.catch((err) => { | ||
alert("Payment could not be captured! " + JSON.stringify(err)); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
// Hides card fields if the merchant isn't eligible | ||
document.querySelector("#card-form").style = "display: none"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import "dotenv/config"; | ||
import express from "express"; | ||
import * as paypal from "./paypal-api.js"; | ||
const { PORT = 8888 } = process.env; | ||
|
||
const app = express(); | ||
app.set("view engine", "ejs"); | ||
app.use(express.static("public")); | ||
|
||
// render checkout page with client id & unique client token | ||
app.get("/", async (req, res) => { | ||
const clientId = process.env.PAYPAL_CLIENT_ID; | ||
try { | ||
const clientToken = await paypal.generateClientToken(); | ||
res.render("checkout", { clientId, clientToken }); | ||
} catch (err) { | ||
res.status(500).send(err.message); | ||
} | ||
}); | ||
|
||
// create order | ||
app.post("/api/orders", async (req, res) => { | ||
try { | ||
const order = await paypal.createOrder(); | ||
res.json(order); | ||
} catch (err) { | ||
res.status(500).send(err.message); | ||
} | ||
}); | ||
|
||
// capture payment | ||
app.post("/api/orders/:orderID/capture", async (req, res) => { | ||
const { orderID } = req.params; | ||
try { | ||
const captureData = await paypal.capturePayment(orderID); | ||
res.json(captureData); | ||
} catch (err) { | ||
res.status(500).send(err.message); | ||
} | ||
}); | ||
|
||
app.listen(PORT, () => { | ||
console.log(`Server listening at http://localhost:${PORT}/`); | ||
}); |
Oops, something went wrong.