diff --git a/.devcontainer/standard-integration/devcontainer.json b/.devcontainer/standard-integration/devcontainer.json
index dd03d372..22576145 100644
--- a/.devcontainer/standard-integration/devcontainer.json
+++ b/.devcontainer/standard-integration/devcontainer.json
@@ -2,7 +2,7 @@
{
"name": "PayPal Standard Integration",
"image": "mcr.microsoft.com/devcontainers/universal:2",
- "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/advanced-integration",
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/standard-integration",
// Use 'onCreateCommand' to run commands when creating the container.
"onCreateCommand": "bash ../.devcontainer/standard-integration/welcome-message.sh",
@@ -21,17 +21,17 @@
],
"portsAttributes": {
"8888": {
- "label": "Preview of Advanced Checkout Flow",
+ "label": "Preview of Standard Checkout Flow",
"onAutoForward": "openBrowserOnce"
}
},
"secrets": {
- "CLIENT_ID": {
+ "PAYPAL_CLIENT_ID": {
"description": "Sandbox client ID of the application.",
"documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox"
},
- "APP_SECRET": {
+ "PAYPAL_CLIENT_SECRET": {
"description": "Sandbox secret of the application.",
"documentationUrl": "https://developer.paypal.com/dashboard/applications/sandbox"
}
diff --git a/README.md b/README.md
index a3d1c9f5..d37a5e98 100644
--- a/README.md
+++ b/README.md
@@ -38,4 +38,4 @@ Once you've setup a PayPal account, you'll need to obtain a **Client ID** and **
These examples will ask you to run commands like `npm install` and `npm start`.
-You'll need a version of node >= 14 which can be downloaded from the [Node.js website](https://nodejs.org/en/download/).
\ No newline at end of file
+You'll need a version of node >= 16 which can be downloaded from the [Node.js website](https://nodejs.org/en/download/).
\ No newline at end of file
diff --git a/standard-integration/.env.example b/standard-integration/.env.example
index ed50f9b7..0fb8a60a 100644
--- a/standard-integration/.env.example
+++ b/standard-integration/.env.example
@@ -1,5 +1,5 @@
# Create an application to obtain credentials at
# https://developer.paypal.com/dashboard/applications/sandbox
-CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
-APP_SECRET="YOUR_SECRET_GOES_HERE"
+PAYPAL_CLIENT_ID="YOUR_CLIENT_ID_GOES_HERE"
+PAYPAL_CLIENT_SECRET="YOUR_SECRET_GOES_HERE"
diff --git a/standard-integration/README.md b/standard-integration/README.md
index b40aee92..969b8fa3 100644
--- a/standard-integration/README.md
+++ b/standard-integration/README.md
@@ -5,7 +5,7 @@ This folder contains example code for a standard PayPal integration using both t
## Instructions
1. [Create an application](https://developer.paypal.com/dashboard/applications/sandbox/create)
-3. Rename `.env.example` to `.env` and update `CLIENT_ID` and `APP_SECRET`
+3. Rename `.env.example` to `.env` and update `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET`
2. Replace `test` in `public/index.html` with your app's client-id
4. Run `npm install`
5. Run `npm start`
diff --git a/standard-integration/index.html b/standard-integration/index.html
new file mode 100644
index 00000000..a8baf73f
--- /dev/null
+++ b/standard-integration/index.html
@@ -0,0 +1,119 @@
+
+
+
+
+
+ PayPal JS SDK Standard Integration
+
+
+
+
+
+
+
+
diff --git a/standard-integration/package.json b/standard-integration/package.json
index 766860ec..c8f4c157 100644
--- a/standard-integration/package.json
+++ b/standard-integration/package.json
@@ -11,8 +11,8 @@
"license": "Apache-2.0",
"description": "",
"dependencies": {
- "dotenv": "^16.0.0",
- "express": "^4.17.3",
- "node-fetch": "^3.2.1"
+ "dotenv": "^16.3.1",
+ "express": "^4.18.2",
+ "node-fetch": "^3.3.2"
}
}
diff --git a/standard-integration/paypal-api.js b/standard-integration/paypal-api.js
deleted file mode 100644
index 998102ca..00000000
--- a/standard-integration/paypal-api.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import fetch from "node-fetch";
-
-const { CLIENT_ID, APP_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 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: "100.00",
- },
- },
- ],
- }),
- });
-
- 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(CLIENT_ID + ":" + APP_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;
-}
-
-async function handleResponse(response) {
- if (response.status === 200 || response.status === 201) {
- return response.json();
- }
-
- const errorMessage = await response.text();
- throw new Error(errorMessage);
-}
diff --git a/standard-integration/public/index.html b/standard-integration/public/index.html
deleted file mode 100644
index 7ba43047..00000000
--- a/standard-integration/public/index.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/standard-integration/server.js b/standard-integration/server.js
index d39f9aa0..87826f70 100644
--- a/standard-integration/server.js
+++ b/standard-integration/server.js
@@ -1,34 +1,151 @@
-import "dotenv/config"; // loads variables from .env file
-import express from "express";
-import * as paypal from "./paypal-api.js";
-const {PORT = 8888} = process.env;
+import express from 'express';
+import fetch from 'node-fetch';
+import 'dotenv/config';
+import path from 'path';
+const { PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET } = process.env;
+const base = 'https://api-m.sandbox.paypal.com';
const app = express();
-app.use(express.static("public"));
-
// parse post params sent in body in json format
app.use(express.json());
-app.post("/my-server/create-paypal-order", async (req, res) => {
+/**
+ * Generate an OAuth 2.0 access token for authenticating with PayPal REST APIs.
+ * @see https://developer.paypal.com/api/rest/authentication/
+ */
+const generateAccessToken = async () => {
try {
- const order = await paypal.createOrder();
- res.json(order);
+ if (!PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) {
+ throw new Error('MISSING_API_CREDENTIALS');
+ }
+ 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 data = await response.json();
+ return data.access_token;
+ } catch (error) {
+ console.error('Failed to generate Access Token:', error);
+ }
+};
+
+/**
+ * Create an order to start the transaction.
+ * @see https://developer.paypal.com/docs/api/orders/v2/#orders_create
+ */
+const createOrder = async (cart) => {
+ // use the cart information passed from the front-end to calculate the purchase unit details
+ console.log(
+ 'shopping cart information passed from the frontend createOrder() callback:',
+ cart,
+ );
+
+ const accessToken = await generateAccessToken();
+ const url = `${base}/v2/checkout/orders`;
+ const payload = {
+ intent: 'CAPTURE',
+ purchase_units: [
+ {
+ amount: {
+ currency_code: 'USD',
+ value: '0.02',
+ },
+ },
+ ],
+ };
+
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:
+ // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/
+ // "PayPal-Mock-Response": '{"mock_application_codes": "MISSING_REQUIRED_PARAMETER"}'
+ // "PayPal-Mock-Response": '{"mock_application_codes": "PERMISSION_DENIED"}'
+ // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'
+ },
+ method: 'POST',
+ body: JSON.stringify(payload),
+ });
+
+ return handleResponse(response);
+};
+
+/**
+ * Capture payment for the created order to complete the transaction.
+ * @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture
+ */
+const captureOrder = async (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}`,
+ // Uncomment one of these to force an error for negative testing (in sandbox mode only). Documentation:
+ // https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/
+ // "PayPal-Mock-Response": '{"mock_application_codes": "INSTRUMENT_DECLINED"}'
+ // "PayPal-Mock-Response": '{"mock_application_codes": "TRANSACTION_REFUSED"}'
+ // "PayPal-Mock-Response": '{"mock_application_codes": "INTERNAL_SERVER_ERROR"}'
+ },
+ });
+
+ return handleResponse(response);
+};
+
+async function handleResponse(response) {
+ try {
+ const jsonResponse = await response.json();
+ return {
+ jsonResponse,
+ httpStatusCode: response.status,
+ };
} catch (err) {
- res.status(500).send(err.message);
+ const errorMessage = await response.text();
+ throw new Error(errorMessage);
+ }
+}
+
+app.post('/api/orders', async (req, res) => {
+ try {
+ // use the cart information passed from the front-end to calculate the order amount detals
+ const { cart } = req.body;
+ const { jsonResponse, httpStatusCode } = await createOrder(cart);
+ res.status(httpStatusCode).json(jsonResponse);
+ } catch (error) {
+ console.error('Failed to create order:', error);
+ res.status(500).json({ error: 'Failed to create order.' });
}
});
-app.post("/my-server/capture-paypal-order", async (req, res) => {
- const { orderID } = req.body;
+app.post('/api/orders/:orderID/capture', async (req, res) => {
try {
- const captureData = await paypal.capturePayment(orderID);
- res.json(captureData);
- } catch (err) {
- res.status(500).send(err.message);
+ const { orderID } = req.params;
+ const { jsonResponse, httpStatusCode } = await captureOrder(orderID);
+ res.status(httpStatusCode).json(jsonResponse);
+ } catch (error) {
+ console.error('Failed to create order:', error);
+ res.status(500).json({ error: 'Failed to capture order.' });
}
});
+// serve index.html
+app.get('/', (req, res) => {
+ res.sendFile(path.resolve('./index.html'));
+});
+
+const PORT = Number(process.env.PORT) || 8888;
+
app.listen(PORT, () => {
- console.log(`Server listening at http://localhost:${PORT}/`);
+ console.log(`Node server listening at http://localhost:${PORT}/`);
});