diff options
| author | Joe Carstairs <65492573+Sycamost@users.noreply.github.com> | 2023-08-07 20:46:07 +0100 |
|---|---|---|
| committer | Joe Carstairs <65492573+Sycamost@users.noreply.github.com> | 2023-08-07 20:50:17 +0100 |
| commit | 48f6b38c333fdaf57fe418c6e6a5fc4bfb928921 (patch) | |
| tree | 2f13763840bd55545b5ce7085517ce369dbf1bef /api | |
| parent | 1ec33f27f5bff2b96c80b954b53a62951b6cd354 (diff) | |
Pre-compiles api
Diffstat (limited to 'api')
| -rw-r--r-- | api/Order.d.ts | 7 | ||||
| -rw-r--r-- | api/_env.ts | 48 | ||||
| -rw-r--r-- | api/_paypal.ts | 114 | ||||
| -rw-r--r-- | api/createPaypalOrder.ts | 34 | ||||
| -rw-r--r-- | api/package.json | 3 |
5 files changed, 3 insertions, 203 deletions
diff --git a/api/Order.d.ts b/api/Order.d.ts deleted file mode 100644 index cf1ff81..0000000 --- a/api/Order.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -type Order = { - productDescription: string; - shortDescription: string; - totalPrice: number; -}; - -export default Order; diff --git a/api/_env.ts b/api/_env.ts deleted file mode 100644 index 69ed902..0000000 --- a/api/_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/_paypal.ts b/api/_paypal.ts deleted file mode 100644 index 348c972..0000000 --- a/api/_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<string> { - 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<AccessTokenResponse> { - 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/createPaypalOrder.ts b/api/createPaypalOrder.ts deleted file mode 100644 index c0053d4..0000000 --- a/api/createPaypalOrder.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { VercelRequest, VercelResponse } from '@vercel/node'; -import type Order from './Order'; -import { paypalCreateOrder } from './_paypal'; - -export default async function handler(request: VercelRequest, response: VercelResponse) { - const { productDescription, shortDescription, totalPrice }: Partial<Order> = request.body; - - if (request.method !== 'POST') { - response.status(404); - return; - } - - 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 }); - - if (paypalResponse.ok) { - response.status(200).json({ - orderId: paypalResponse.id, - }); - } else { - throw new Error(`PayPal returned ${paypalResponse.statusCode} ${paypalResponse.statusMessage}`); - } - } catch (err) { - response.status(500).send(`Internal server error. ${err}`); - } - } -} diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..c9a4422 --- /dev/null +++ b/api/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +}
\ No newline at end of file |
