diff --git a/advanced-integration/public/app.js b/advanced-integration/client/app.js similarity index 100% rename from advanced-integration/public/app.js rename to advanced-integration/client/app.js diff --git a/advanced-integration/package.json b/advanced-integration/package.json index 424f853e..1d8d34ed 100644 --- a/advanced-integration/package.json +++ b/advanced-integration/package.json @@ -2,11 +2,12 @@ "name": "paypal-advanced-integration", "description": "Sample Node.js web app to integrate PayPal Advanced Checkout for online payments", "version": "1.0.0", - "main": "server.js", + "main": "server/server.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "start": "node server/server.js", + "format": "npx prettier --write **.js" }, "license": "Apache-2.0", "dependencies": { diff --git a/advanced-integration/server.js b/advanced-integration/server/server.js similarity index 98% rename from advanced-integration/server.js rename to advanced-integration/server/server.js index becdb6a2..a7d84407 100644 --- a/advanced-integration/server.js +++ b/advanced-integration/server/server.js @@ -1,13 +1,13 @@ import express from "express"; import fetch from "node-fetch"; import "dotenv/config"; -import path from "path"; const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env; const base = "https://api-m.sandbox.paypal.com"; const app = express(); app.set("view engine", "ejs"); -app.use(express.static("public")); +app.set("views", "./server/views"); +app.use(express.static("client")); // parse post params sent in body in json format app.use(express.json()); diff --git a/advanced-integration/views/checkout.ejs b/advanced-integration/server/views/checkout.ejs similarity index 100% rename from advanced-integration/views/checkout.ejs rename to advanced-integration/server/views/checkout.ejs diff --git a/standard-integration/README.md b/standard-integration/README.md index c1c22ad8..c0bf83f3 100644 --- a/standard-integration/README.md +++ b/standard-integration/README.md @@ -6,7 +6,7 @@ This folder contains example code for a Standard PayPal integration using both t 1. [Create an application](https://developer.paypal.com/dashboard/applications/sandbox/create) 2. Rename `.env.example` to `.env` and update `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET` -3. Replace `test` in `index.html` with your app's client-id +3. Replace `test` in `client/index.html` with your app's client-id 4. Run `npm install` 5. Run `npm start` 6. Open http://localhost:8888 diff --git a/standard-integration/client/app.js b/standard-integration/client/app.js new file mode 100644 index 00000000..af056c7d --- /dev/null +++ b/standard-integration/client/app.js @@ -0,0 +1,94 @@ +window.paypal + .Buttons({ + createOrder: async () => { + try { + const response = await fetch("/api/orders", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + // use the "body" param to optionally pass additional order information + // like product ids and quantities + body: JSON.stringify({ + cart: [ + { + id: "YOUR_PRODUCT_ID", + quantity: "YOUR_PRODUCT_QUANTITY", + }, + ], + }), + }); + + const orderData = await response.json(); + + if (orderData.id) { + return orderData.id; + } else { + const errorDetail = orderData?.details?.[0]; + const errorMessage = errorDetail + ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})` + : JSON.stringify(orderData); + + throw new Error(errorMessage); + } + } catch (error) { + console.error(error); + resultMessage(`Could not initiate PayPal Checkout...

${error}`); + } + }, + onApprove: async (data, actions) => { + try { + const response = await fetch(`/api/orders/${data.orderID}/capture`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + const orderData = await response.json(); + // Three cases to handle: + // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart() + // (2) Other non-recoverable errors -> Show a failure message + // (3) Successful transaction -> Show confirmation or thank you message + + const errorDetail = orderData?.details?.[0]; + + if (errorDetail?.issue === "INSTRUMENT_DECLINED") { + // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart() + // recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/ + return actions.restart(); + } else if (errorDetail) { + // (2) Other non-recoverable errors -> Show a failure message + throw new Error(`${errorDetail.description} (${orderData.debug_id})`); + } else if (!orderData.purchase_units) { + throw new Error(JSON.stringify(orderData)); + } else { + // (3) Successful transaction -> Show confirmation or thank you message + // Or go to another URL: actions.redirect('thank_you.html'); + const transaction = + orderData?.purchase_units?.[0]?.payments?.captures?.[0] || + orderData?.purchase_units?.[0]?.payments?.authorizations?.[0]; + resultMessage( + `Transaction ${transaction.status}: ${transaction.id}

See console for all available details`, + ); + console.log( + "Capture result", + orderData, + JSON.stringify(orderData, null, 2), + ); + } + } catch (error) { + console.error(error); + resultMessage( + `Sorry, your transaction could not be processed...

${error}`, + ); + } + }, + }) + .render("#paypal-button-container"); + +// Example function to show a result to the user. Your site's UI library can be used instead. +function resultMessage(message) { + const container = document.querySelector("#result-message"); + container.innerHTML = message; +} diff --git a/standard-integration/client/checkout.html b/standard-integration/client/checkout.html new file mode 100644 index 00000000..7b959c2f --- /dev/null +++ b/standard-integration/client/checkout.html @@ -0,0 +1,15 @@ + + + + + + PayPal JS SDK Standard Integration + + +
+

+ + + + + diff --git a/standard-integration/index.html b/standard-integration/index.html deleted file mode 100644 index 6a355374..00000000 --- a/standard-integration/index.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - PayPal JS SDK Standard Integration - - -
-

- - - - - diff --git a/standard-integration/package.json b/standard-integration/package.json index e776dad9..3c26d897 100644 --- a/standard-integration/package.json +++ b/standard-integration/package.json @@ -2,11 +2,12 @@ "name": "paypal-standard-integration", "description": "Sample Node.js web app to integrate PayPal Standard Checkout for online payments", "version": "1.0.0", - "main": "server.js", + "main": "server/server.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "start": "node server/server.js", + "format": "npx prettier --write **.js" }, "license": "Apache-2.0", "dependencies": { diff --git a/standard-integration/server.js b/standard-integration/server/server.js similarity index 97% rename from standard-integration/server.js rename to standard-integration/server/server.js index 1e998011..0d8d3cb8 100644 --- a/standard-integration/server.js +++ b/standard-integration/server/server.js @@ -7,6 +7,9 @@ const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PORT = 8888 } = process.env; const base = "https://api-m.sandbox.paypal.com"; const app = express(); +// host static files +app.use(express.static("client")); + // parse post params sent in body in json format app.use(express.json()); @@ -141,7 +144,7 @@ app.post("/api/orders/:orderID/capture", async (req, res) => { // serve index.html app.get("/", (req, res) => { - res.sendFile(path.resolve("./index.html")); + res.sendFile(path.resolve("./client/checkout.html")); }); app.listen(PORT, () => {