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-src/_Order.d.ts | 7 ---- api-src/_env.ts | 48 --------------------- api-src/_paypal.ts | 114 -------------------------------------------------- api-src/order.ts | 61 --------------------------- api-src/tsconfig.json | 15 ------- 5 files changed, 245 deletions(-) delete mode 100644 api-src/_Order.d.ts delete mode 100644 api-src/_env.ts delete mode 100644 api-src/_paypal.ts delete mode 100644 api-src/order.ts delete mode 100644 api-src/tsconfig.json (limited to 'api-src') diff --git a/api-src/_Order.d.ts b/api-src/_Order.d.ts deleted file mode 100644 index cf1ff81..0000000 --- a/api-src/_Order.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -type Order = { - productDescription: string; - shortDescription: string; - totalPrice: number; -}; - -export default Order; diff --git a/api-src/_env.ts b/api-src/_env.ts deleted file mode 100644 index 536f8d7..0000000 --- a/api-src/_env.ts +++ /dev/null @@ -1,48 +0,0 @@ -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-src/_paypal.ts b/api-src/_paypal.ts deleted file mode 100644 index 99e45f7..0000000 --- a/api-src/_paypal.ts +++ /dev/null @@ -1,114 +0,0 @@ -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-src/order.ts b/api-src/order.ts deleted file mode 100644 index 25def6d..0000000 --- a/api-src/order.ts +++ /dev/null @@ -1,61 +0,0 @@ -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-src/tsconfig.json b/api-src/tsconfig.json deleted file mode 100644 index 84d8095..0000000 --- a/api-src/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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