From 6549313a54e7fede18cfa170df82c29f511d76d7 Mon Sep 17 00:00:00 2001 From: Joe Carstairs <65492573+Sycamost@users.noreply.github.com> Date: Sat, 12 Aug 2023 09:39:41 +0100 Subject: No more API build step --- api/Order.js | 135 ------------------------------------ api/Order.js.map | 1 - api/_Order.d.ts | 7 ++ api/_env.js | 33 --------- api/_env.js.map | 1 - api/_env.ts | 48 +++++++++++++ api/_paypal.js | 200 ----------------------------------------------------- api/_paypal.js.map | 1 - api/_paypal.ts | 114 ++++++++++++++++++++++++++++++ api/order.ts | 61 ++++++++++++++++ api/tsconfig.json | 15 ++++ 11 files changed, 245 insertions(+), 371 deletions(-) delete mode 100644 api/Order.js delete mode 100644 api/Order.js.map create mode 100644 api/_Order.d.ts delete mode 100644 api/_env.js delete mode 100644 api/_env.js.map create mode 100644 api/_env.ts delete mode 100644 api/_paypal.js delete mode 100644 api/_paypal.js.map create mode 100644 api/_paypal.ts create mode 100644 api/order.ts create mode 100644 api/tsconfig.json (limited to 'api') diff --git a/api/Order.js b/api/Order.js deleted file mode 100644 index afc322e..0000000 --- a/api/Order.js +++ /dev/null @@ -1,135 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var _paypal_1 = require("./_paypal"); -function handler(request, response) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!(request.method === 'POST')) return [3 /*break*/, 2]; - return [4 /*yield*/, handlePost(request, response)]; - case 1: - _a.sent(); - return [3 /*break*/, 5]; - case 2: - if (!(request.method === 'PATCH')) return [3 /*break*/, 4]; - return [4 /*yield*/, handlePatch(request, response)]; - case 3: - _a.sent(); - return [3 /*break*/, 5]; - case 4: - console.warn('Method was not POST. Method was ' + request.method); - response.status(404); - return [2 /*return*/]; - case 5: return [2 /*return*/]; - } - }); - }); -} -exports.default = handler; -function handlePost(request, response) { - return __awaiter(this, void 0, void 0, function () { - var _a, productDescription, shortDescription, totalPrice, paypalResponse, err_1; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _a = request.body, productDescription = _a.productDescription, shortDescription = _a.shortDescription, totalPrice = _a.totalPrice; - if (!!productDescription) return [3 /*break*/, 1]; - response.status(400).send('Expected body to contain productDescription, but none provided.'); - return [3 /*break*/, 6]; - case 1: - if (!!shortDescription) return [3 /*break*/, 2]; - response.status(400).send('Expected body to contain shortDescription, but none provided.'); - return [3 /*break*/, 6]; - case 2: - if (!!totalPrice) return [3 /*break*/, 3]; - response.status(400).send('Expected body to contain totalPrice, but none provided.'); - return [3 /*break*/, 6]; - case 3: - _b.trys.push([3, 5, , 6]); - return [4 /*yield*/, (0, _paypal_1.paypalCreateOrder)({ productDescription: productDescription, shortDescription: shortDescription, totalPrice: totalPrice })]; - case 4: - paypalResponse = _b.sent(); - response.status(201).json({ - orderId: paypalResponse.id, - }); - return [3 /*break*/, 6]; - case 5: - err_1 = _b.sent(); - response.status(500).send("Internal server error. ".concat(err_1)); - return [3 /*break*/, 6]; - case 6: return [2 /*return*/]; - } - }); - }); -} -function handlePatch(request, response) { - return __awaiter(this, void 0, void 0, function () { - var _a, id, isApproved, paypalResponse, err_2; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _a = request.body, id = _a.id, isApproved = _a.isApproved; - if (!!id) return [3 /*break*/, 1]; - response.status(400).send('Expected body to contain id, but none provided.'); - return [3 /*break*/, 5]; - case 1: - if (!!isApproved) return [3 /*break*/, 2]; - response.status(400).send('Expected body to contain isApproved, but none provided.'); - return [3 /*break*/, 5]; - case 2: - _b.trys.push([2, 4, , 5]); - return [4 /*yield*/, (0, _paypal_1.paypalCaptureOrder)(id)]; - case 3: - paypalResponse = _b.sent(); - response.status(201).json({ - orderId: paypalResponse.id, - paypalStatus: paypalResponse.status, - }); - return [3 /*break*/, 5]; - case 4: - err_2 = _b.sent(); - response.status(500).send("Internal server error. ".concat(err_2)); - return [3 /*break*/, 5]; - case 5: return [2 /*return*/]; - } - }); - }); -} -//# sourceMappingURL=order.js.map \ No newline at end of file diff --git a/api/Order.js.map b/api/Order.js.map deleted file mode 100644 index 0abdce4..0000000 --- a/api/Order.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"order.js","sourceRoot":"","sources":["../api-src/order.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,qCAAkE;AAElE,SAA8B,OAAO,CAAC,OAAsB,EAAE,QAAwB;;;;;yBAChF,CAAA,OAAO,CAAC,MAAM,KAAK,MAAM,CAAA,EAAzB,wBAAyB;oBAC3B,qBAAM,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAA;;oBAAnC,SAAmC,CAAC;;;yBAC3B,CAAA,OAAO,CAAC,MAAM,KAAK,OAAO,CAAA,EAA1B,wBAA0B;oBACnC,qBAAM,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAA;;oBAApC,SAAoC,CAAC;;;oBAErC,OAAO,CAAC,IAAI,CAAC,kCAAkC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;oBAClE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBACrB,sBAAO;;;;;CAEV;AAVD,0BAUC;AAED,SAAe,UAAU,CAAC,OAAsB,EAAE,QAAwB;;;;;;oBAClE,KAAuE,OAAO,CAAC,IAAI,EAAjF,kBAAkB,wBAAA,EAAE,gBAAgB,sBAAA,EAAE,UAAU,gBAAA,CAAkC;yBAEtF,CAAC,kBAAkB,EAAnB,wBAAmB;oBACrB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;;;yBACpF,CAAC,gBAAgB,EAAjB,wBAAiB;oBAC1B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;;;yBAClF,CAAC,UAAU,EAAX,wBAAW;oBACpB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;;;;oBAG5D,qBAAM,IAAA,2BAAiB,EAAC,EAAE,kBAAkB,oBAAA,EAAE,gBAAgB,kBAAA,EAAE,UAAU,YAAA,EAAE,CAAC,EAAA;;oBAA9F,cAAc,GAAG,SAA6E;oBACpG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;wBACxB,OAAO,EAAE,cAAc,CAAC,EAAE;qBAC3B,CAAC,CAAC;;;;oBAEH,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iCAA0B,KAAG,CAAE,CAAC,CAAC;;;;;;CAGhE;AAED,SAAe,WAAW,CAAC,OAAsB,EAAE,QAAwB;;;;;;oBACnE,KAAgD,OAAO,CAAC,IAAI,EAA1D,EAAE,QAAA,EAAE,UAAU,gBAAA,CAA6C;yBAE/D,CAAC,EAAE,EAAH,wBAAG;oBACL,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;;;yBACpE,CAAC,UAAU,EAAX,wBAAW;oBACpB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;;;;oBAG5D,qBAAM,IAAA,4BAAkB,EAAC,EAAE,CAAC,EAAA;;oBAA7C,cAAc,GAAG,SAA4B;oBACnD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;wBACxB,OAAO,EAAE,cAAc,CAAC,EAAE;wBAC1B,YAAY,EAAE,cAAc,CAAC,MAAM;qBACpC,CAAC,CAAC;;;;oBAEH,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iCAA0B,KAAG,CAAE,CAAC,CAAC;;;;;;CAGhE"} \ No newline at end of file diff --git a/api/_Order.d.ts b/api/_Order.d.ts new file mode 100644 index 0000000..cf1ff81 --- /dev/null +++ b/api/_Order.d.ts @@ -0,0 +1,7 @@ +type Order = { + productDescription: string; + shortDescription: string; + totalPrice: number; +}; + +export default Order; diff --git a/api/_env.js b/api/_env.js deleted file mode 100644 index 409a6df..0000000 --- a/api/_env.js +++ /dev/null @@ -1,33 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var path_1 = __importDefault(require("path")); -var dotenv_1 = __importDefault(require("dotenv")); -// Parsing the env file. -dotenv_1.default.config({ path: path_1.default.resolve(__dirname, '../.env') }); -var ENV = { - PAYPAL_LIVE_CLIENT_ID: 1, - PAYPAL_LIVE_SECRET: 1, - PAYPAL_SANDBOX_CLIENT_ID: 1, - PAYPAL_SANDBOX_SECRET: 1, - ENVIRONMENT: 1, -}; -var env = process.env; -var sanitisedEnvEntries = Object.keys(ENV) - .map(function (key) { - var value = env[key]; - if (!value) { - throw new Error("Environment not configured correctly. ".concat(key, " was not defined.")); - } - if (key === 'ENVIRONMENT') { - if (!['dev', 'prod'].includes(value)) { - throw new Error("\n Environment not configured correctly. ".concat(key, " must be\n either 'dev' or 'prod', but '").concat(value, "' was provided.\n ")); - } - } - return [key, value]; -}); -var sanitisedEnv = Object.fromEntries(sanitisedEnvEntries); -exports.default = sanitisedEnv; -//# sourceMappingURL=_env.js.map \ No newline at end of file diff --git a/api/_env.js.map b/api/_env.js.map deleted file mode 100644 index e76fe63..0000000 --- a/api/_env.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"_env.js","sourceRoot":"","sources":["../api-src/_env.ts"],"names":[],"mappings":";;;;;AAAA,8CAAwB;AACxB,kDAA4B;AAE5B,wBAAwB;AACxB,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AAY5D,IAAM,GAAG,GAAmC;IAC1C,qBAAqB,EAAE,CAAC;IACxB,kBAAkB,EAAE,CAAC;IACrB,wBAAwB,EAAE,CAAC;IAC3B,qBAAqB,EAAE,CAAC;IACxB,WAAW,EAAE,CAAC;CACf,CAAC;AAEM,IAAA,GAAG,GAAwB,OAAO,IAA/B,CAAgC;AAE3C,IAAM,mBAAmB,GACtB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAmB;KAClC,GAAG,CAAsB,UAAA,GAAG;IAC3B,IAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE;QACV,MAAM,IAAI,KAAK,CAAC,gDAAyC,GAAG,sBAAmB,CAAC,CAAC;KAClF;IAED,IAAI,GAAG,KAAK,aAAa,EAAE;QACzB,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;YACpC,MAAM,IAAI,KAAK,CAAC,4DAC0B,GAAG,8DACZ,KAAK,8BACrC,CAAC,CAAC;SACJ;KACF;IACD,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AACtB,CAAC,CAAC,CAAC;AAEL,IAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAQ,CAAC;AAEpE,kBAAe,YAAY,CAAC"} \ No newline at end of file diff --git a/api/_env.ts b/api/_env.ts new file mode 100644 index 0000000..536f8d7 --- /dev/null +++ b/api/_env.ts @@ -0,0 +1,48 @@ +import path from 'path'; +import dotenv from 'dotenv'; + +// Parsing the env file. +dotenv.config({ path: path.resolve(__dirname, '../.env') }); + +type Env = { + PAYPAL_LIVE_CLIENT_ID: string, + PAYPAL_LIVE_SECRET: string, + PAYPAL_SANDBOX_CLIENT_ID: string, + PAYPAL_SANDBOX_SECRET: string, + ENVIRONMENT: 'dev' | 'prod', +}; + +type DirtyEnv = { [key in keyof Env]?: string }; + +const ENV: { [key in keyof Env]: number } = { + PAYPAL_LIVE_CLIENT_ID: 1, + PAYPAL_LIVE_SECRET: 1, + PAYPAL_SANDBOX_CLIENT_ID: 1, + PAYPAL_SANDBOX_SECRET: 1, + ENVIRONMENT: 1, +}; + +const { env }: { env: DirtyEnv } = process; + +const sanitisedEnvEntries = + (Object.keys(ENV) as (keyof Env)[]) + .map<[keyof Env, string]>(key => { + const value = env[key]; + if (!value) { + throw new Error(`Environment not configured correctly. ${key} was not defined.`); + } + + if (key === 'ENVIRONMENT') { + if (!['dev', 'prod'].includes(value)) { + throw new Error(` + Environment not configured correctly. ${key} must be + either 'dev' or 'prod', but '${value}' was provided. + `); + } + } + return [key, value]; + }); + +const sanitisedEnv = Object.fromEntries(sanitisedEnvEntries) as Env; + +export default sanitisedEnv; \ No newline at end of file diff --git a/api/_paypal.js b/api/_paypal.js deleted file mode 100644 index 6b5f95a..0000000 --- a/api/_paypal.js +++ /dev/null @@ -1,200 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.paypalCaptureOrder = exports.paypalCreateOrder = void 0; -var _env_1 = __importDefault(require("./_env")); -function paypalCreateOrder(order) { - return __awaiter(this, void 0, void 0, function () { - var accessToken, url, response; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, getLazyAccessToken()]; - case 1: - accessToken = _a.sent(); - url = "".concat(PAYPAL_URL, "/v2/checkout/orders"); - return [4 /*yield*/, fetch(url, { - method: 'POST', - headers: paypalCreateOrderRequestHeaders(accessToken), - body: paypalCreateOrderRequestBody(order), - })]; - case 2: - response = _a.sent(); - return [4 /*yield*/, handle(response)]; - case 3: return [2 /*return*/, _a.sent()]; - } - }); - }); -} -exports.paypalCreateOrder = paypalCreateOrder; -function paypalCaptureOrder(orderId) { - return __awaiter(this, void 0, void 0, function () { - var accessToken, url, response; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, getLazyAccessToken()]; - case 1: - accessToken = _a.sent(); - url = "".concat(PAYPAL_URL, "/v2/checkout/orders/").concat(orderId, "/capture"); - return [4 /*yield*/, fetch(url, { - method: 'POST', - headers: paypalCaptureOrderRequestHeaders(accessToken), - })]; - case 2: - response = _a.sent(); - return [4 /*yield*/, handle(response)]; - case 3: return [2 /*return*/, _a.sent()]; - } - }); - }); -} -exports.paypalCaptureOrder = paypalCaptureOrder; -function handle(response) { - return __awaiter(this, void 0, void 0, function () { - var _a, _b; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: - if (!response.ok) return [3 /*break*/, 2]; - return [4 /*yield*/, response.json()]; - case 1: return [2 /*return*/, _c.sent()]; - case 2: - _a = Error.bind; - _b = 'Failed to communicate with PayPal. '; - return [4 /*yield*/, response.text()]; - case 3: throw new (_a.apply(Error, [void 0, _b + (_c.sent())]))(); - } - }); - }); -} -function paypalCreateOrderRequestBody(order) { - var body = JSON.stringify({ - intent: 'CAPTURE', - purchase_units: [ - { - description: order.productDescription, - soft_descriptor: order.shortDescription, - amount: { - currency_code: 'GBP', - value: order.totalPrice.toFixed(2), - }, - }, - ], - }); - return body; -} -function paypalCreateOrderRequestHeaders(accessToken) { - return { - 'Content-Type': 'application/json', - Authorization: "Bearer ".concat(accessToken), - }; -} -function paypalCaptureOrderRequestHeaders(accessToken) { - return { - Authorization: "Bearer ".concat(accessToken), - }; -} -var getLazyAccessToken = (function () { - var accessToken; - var timeRequested; - var timeExpires; - return function () { - return __awaiter(this, void 0, void 0, function () { - var isNearlyExpired, secondsUntilExpires, msUntilExpires; - var _a; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - isNearlyExpired = timeExpires - Date.now() < 10000; - if (!(!accessToken || isNearlyExpired)) return [3 /*break*/, 2]; - timeRequested = Date.now(); - secondsUntilExpires = void 0; - return [4 /*yield*/, generateAccessToken()]; - case 1: - (_a = _b.sent(), accessToken = _a.accessToken, secondsUntilExpires = _a.secondsUntilExpires); - msUntilExpires = secondsUntilExpires * 1000; - timeExpires = timeRequested + msUntilExpires; - _b.label = 2; - case 2: return [2 /*return*/, accessToken]; - } - }); - }); - }; -})(); -function generateAccessToken() { - return __awaiter(this, void 0, void 0, function () { - var auth, response, _a, access_token, expires_in; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - auth = Buffer.from(PAYPAL_CLIENT_ID + ':' + PAYPAL_SECRET).toString('base64'); - return [4 /*yield*/, fetch("".concat(PAYPAL_URL, "/v1/oauth2/token"), { - method: 'POST', - body: 'grant_type=client_credentials', - headers: { - Authorization: "Basic ".concat(auth), - }, - })]; - case 1: - response = _b.sent(); - return [4 /*yield*/, response.json()]; - case 2: - _a = _b.sent(), access_token = _a.access_token, expires_in = _a.expires_in; - if (access_token && expires_in) { - return [2 /*return*/, { - accessToken: access_token, - secondsUntilExpires: expires_in, - }]; - } - throw new Error('Could not fetch access token from PayPal.'); - } - }); - }); -} -var PAYPAL_CLIENT_ID = _env_1.default.ENVIRONMENT === 'prod' - ? _env_1.default.PAYPAL_LIVE_CLIENT_ID - : _env_1.default.PAYPAL_SANDBOX_CLIENT_ID; -var PAYPAL_SECRET = _env_1.default.ENVIRONMENT === 'prod' - ? _env_1.default.PAYPAL_LIVE_SECRET - : _env_1.default.PAYPAL_SANDBOX_SECRET; -var PAYPAL_URL = _env_1.default.ENVIRONMENT === 'prod' - ? 'https://api-m.paypal.com' - : 'https://api-m.sandbox.paypal.com'; -//# sourceMappingURL=_paypal.js.map \ No newline at end of file diff --git a/api/_paypal.js.map b/api/_paypal.js.map deleted file mode 100644 index 0c69282..0000000 --- a/api/_paypal.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"_paypal.js","sourceRoot":"","sources":["../api-src/_paypal.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/api/_paypal.ts b/api/_paypal.ts new file mode 100644 index 0000000..99e45f7 --- /dev/null +++ b/api/_paypal.ts @@ -0,0 +1,114 @@ +import type Order from './_Order'; +import env from './_env'; + +export async function paypalCreateOrder(order: Order) { + const accessToken = await getLazyAccessToken(); + const url = `${PAYPAL_URL}/v2/checkout/orders`; + const response = await fetch(url, { + method: 'POST', + headers: paypalCreateOrderRequestHeaders(accessToken), + body: paypalCreateOrderRequestBody(order), + }); + return await handle(response); +} + +export async function paypalCaptureOrder(orderId: string) { + const accessToken = await getLazyAccessToken(); + const url = `${PAYPAL_URL}/v2/checkout/orders/${orderId}/capture`; + const response = await fetch(url, { + method: 'POST', + headers: paypalCaptureOrderRequestHeaders(accessToken), + }); + return await handle(response); +} + +async function handle(response: Response) { + if (response.ok) { + return await response.json(); + } + throw new Error('Failed to communicate with PayPal. ' + await response.text()); +} + +function paypalCreateOrderRequestBody(order: Order) { + const body = JSON.stringify({ + intent: 'CAPTURE', + purchase_units: [ + { + description: order.productDescription, + soft_descriptor: order.shortDescription, + amount: { + currency_code: 'GBP', + value: order.totalPrice.toFixed(2), + }, + }, + ], + }); + return body; +} + +function paypalCreateOrderRequestHeaders(accessToken: string) { + return { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }; +} + +function paypalCaptureOrderRequestHeaders(accessToken: string) { + return { + Authorization: `Bearer ${accessToken}`, + }; +} + +const getLazyAccessToken = (() => { + let accessToken: string; + let timeRequested: number; + let timeExpires: number; + return async function (): Promise { + const isNearlyExpired = timeExpires - Date.now() < 10_000; + if (!accessToken || isNearlyExpired) { + timeRequested = Date.now(); + let secondsUntilExpires: number; + ({ accessToken, secondsUntilExpires } = await generateAccessToken()); + const msUntilExpires = secondsUntilExpires * 1000; + timeExpires = timeRequested + msUntilExpires; + } + return accessToken; + }; +})(); + +async function generateAccessToken(): Promise { + const auth = Buffer.from(PAYPAL_CLIENT_ID + ':' + PAYPAL_SECRET).toString('base64'); + const response = await fetch(`${PAYPAL_URL}/v1/oauth2/token`, { + method: 'POST', + body: 'grant_type=client_credentials', + headers: { + Authorization: `Basic ${auth}`, + }, + }); + const { access_token, expires_in } = await response.json(); + if (access_token && expires_in) { + return { + accessToken: access_token, + secondsUntilExpires: expires_in, + }; + } + throw new Error('Could not fetch access token from PayPal.'); +} + +type AccessTokenResponse = { + accessToken: string; + secondsUntilExpires: number; +}; + +const PAYPAL_CLIENT_ID = + env.ENVIRONMENT === 'prod' + ? env.PAYPAL_LIVE_CLIENT_ID + : env.PAYPAL_SANDBOX_CLIENT_ID; +const PAYPAL_SECRET = + env.ENVIRONMENT === 'prod' + ? env.PAYPAL_LIVE_SECRET + : env.PAYPAL_SANDBOX_SECRET; +const PAYPAL_URL = + env.ENVIRONMENT === 'prod' + ? 'https://api-m.paypal.com' + : 'https://api-m.sandbox.paypal.com' \ No newline at end of file diff --git a/api/order.ts b/api/order.ts new file mode 100644 index 0000000..25def6d --- /dev/null +++ b/api/order.ts @@ -0,0 +1,61 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node'; +import type Order from './_Order'; +import { paypalCaptureOrder, paypalCreateOrder } from './_paypal'; + +export default async function handler(request: VercelRequest, response: VercelResponse) { + if (request.method === 'POST') { + await handlePost(request, response); + } else if (request.method === 'PATCH') { + await handlePatch(request, response); + } else { + console.warn('Method was not POST. Method was ' + request.method); + response.status(404); + return; + } +} + +async function handlePost(request: VercelRequest, response: VercelResponse) { + const { productDescription, shortDescription, totalPrice }: Partial = request.body; + + if (!productDescription) { + response.status(400).send('Expected body to contain productDescription, but none provided.'); + } else if (!shortDescription) { + response.status(400).send('Expected body to contain shortDescription, but none provided.'); + } else if (!totalPrice) { + response.status(400).send('Expected body to contain totalPrice, but none provided.'); + } else { + try { + const paypalResponse = await paypalCreateOrder({ productDescription, shortDescription, totalPrice }); + response.status(201).json({ + orderId: paypalResponse.id, + }); + } catch (err) { + response.status(500).send(`Internal server error. ${err}`); + } + } +} + +async function handlePatch(request: VercelRequest, response: VercelResponse) { + const { id, isApproved }: Partial = request.body; + + if (!id) { + response.status(400).send('Expected body to contain id, but none provided.'); + } else if (!isApproved) { + response.status(400).send('Expected body to contain isApproved, but none provided.'); + } else { + try { + const paypalResponse = await paypalCaptureOrder(id); + response.status(201).json({ + orderId: paypalResponse.id, + paypalStatus: paypalResponse.status, + }); + } catch (err) { + response.status(500).send(`Internal server error. ${err}`); + } + } +} + +type PatchRequestBody = { + id: string; + isApproved: boolean; +}; diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 0000000..84d8095 --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,15 @@ +{ + "strict": true, + "compilerOptions": { + "target": "ES5", + "module": "CommonJS", + "sourceMap": true, + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": ".", + "outDir": "../api", + "lib": ["ESNext", "DOM"] + }, + "include": ["."], + "exclude": ["./node_modules"] +} -- cgit v1.2.3