summaryrefslogtreecommitdiff
path: root/api-src
diff options
context:
space:
mode:
Diffstat (limited to 'api-src')
-rw-r--r--api-src/_Order.d.ts7
-rw-r--r--api-src/_env.ts48
-rw-r--r--api-src/_paypal.ts114
-rw-r--r--api-src/order.ts61
-rw-r--r--api-src/tsconfig.json15
5 files changed, 0 insertions, 245 deletions
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<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-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<Order> = 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<PatchRequestBody> = 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"]
-}