You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.5 KiB
161 lines
4.5 KiB
|
3 weeks ago
|
import express from "express";
|
||
|
|
import "dotenv/config";
|
||
|
|
import {
|
||
|
|
ApiError,
|
||
|
|
CheckoutPaymentIntent,
|
||
|
|
Client,
|
||
|
|
Environment,
|
||
|
|
LogLevel,
|
||
|
|
OrdersController,
|
||
|
|
PaymentsController,
|
||
|
|
PaypalExperienceLandingPage,
|
||
|
|
PaypalExperienceUserAction,
|
||
|
|
ShippingPreference,
|
||
|
|
} from "@paypal/paypal-server-sdk";
|
||
|
|
import bodyParser from "body-parser";
|
||
|
|
|
||
|
|
const app = express();
|
||
|
|
app.use(bodyParser.json());
|
||
|
|
|
||
|
|
const {
|
||
|
|
PAYPAL_CLIENT_ID,
|
||
|
|
PAYPAL_CLIENT_SECRET,
|
||
|
|
PORT = 8080,
|
||
|
|
} = process.env;
|
||
|
|
|
||
|
|
const client = new Client({
|
||
|
|
clientCredentialsAuthCredentials: {
|
||
|
|
oAuthClientId: PAYPAL_CLIENT_ID,
|
||
|
|
oAuthClientSecret: PAYPAL_CLIENT_SECRET,
|
||
|
|
},
|
||
|
|
timeout: 0,
|
||
|
|
environment: Environment.Sandbox,
|
||
|
|
logging: {
|
||
|
|
logLevel: LogLevel.Info,
|
||
|
|
logRequest: { logBody: true },
|
||
|
|
logResponse: { logHeaders: true },
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const ordersController = new OrdersController(client);
|
||
|
|
const paymentsController = new PaymentsController(client);
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create an order to start the transaction.
|
||
|
|
* @see https://developer.paypal.com/docs/api/orders/v2/#orders_create
|
||
|
|
*/
|
||
|
|
const createOrder = async (cart) => {
|
||
|
|
const collect = {
|
||
|
|
body: {
|
||
|
|
intent: "CAPTURE",
|
||
|
|
purchaseUnits: [
|
||
|
|
{
|
||
|
|
amount: {
|
||
|
|
currencyCode: "USD",
|
||
|
|
value: "100",
|
||
|
|
breakdown: {
|
||
|
|
itemTotal: {
|
||
|
|
currencyCode: "USD",
|
||
|
|
value: "100",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
// lookup item details in `cart` from database
|
||
|
|
items: [
|
||
|
|
{
|
||
|
|
name: "T-Shirt",
|
||
|
|
unitAmount: {
|
||
|
|
currencyCode: "USD",
|
||
|
|
value: "100",
|
||
|
|
},
|
||
|
|
quantity: "1",
|
||
|
|
description: "Super Fresh Shirt",
|
||
|
|
sku: "sku01",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
prefer: "return=minimal",
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { body, ...httpResponse } = await ordersController.createOrder(
|
||
|
|
collect
|
||
|
|
);
|
||
|
|
// Get more response info...
|
||
|
|
// const { statusCode, headers } = httpResponse;
|
||
|
|
return {
|
||
|
|
jsonResponse: JSON.parse(body),
|
||
|
|
httpStatusCode: httpResponse.statusCode,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof ApiError) {
|
||
|
|
// const { statusCode, headers } = error;
|
||
|
|
throw new Error(error.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// createOrder route
|
||
|
|
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." });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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 collect = {
|
||
|
|
id: orderID,
|
||
|
|
prefer: "return=minimal",
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { body, ...httpResponse } = await ordersController.captureOrder(
|
||
|
|
collect
|
||
|
|
);
|
||
|
|
// Get more response info...
|
||
|
|
// const { statusCode, headers } = httpResponse;
|
||
|
|
return {
|
||
|
|
jsonResponse: JSON.parse(body),
|
||
|
|
httpStatusCode: httpResponse.statusCode,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof ApiError) {
|
||
|
|
// const { statusCode, headers } = error;
|
||
|
|
throw new Error(error.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// captureOrder route
|
||
|
|
app.post("/api/orders/:orderID/capture", async (req, res) => {
|
||
|
|
try {
|
||
|
|
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." });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
app.listen(PORT, () => {
|
||
|
|
console.log(`Node server listening at http://localhost:${PORT}/`);
|
||
|
|
});
|
||
|
|
|