├── .env.sample ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── classes │ ├── Card.ts │ ├── CustomError.ts │ └── VirtualCard.ts ├── config │ └── config.ts ├── controllers │ ├── cardController.ts │ ├── paymentController.ts │ ├── rechargeController.ts │ └── virtualCardController.ts ├── dbStrategy │ └── postgres │ │ ├── connection.ts │ │ └── database │ │ ├── connect-database │ │ ├── create-database │ │ ├── destroy-database │ │ ├── scripts │ │ ├── 001-cria-banco.sql │ │ └── 002-popula-dados.sql │ │ └── src │ │ ├── database │ │ └── functions ├── interfaces │ ├── businessInterfaces.ts │ ├── cardInterfaces.ts │ ├── companyInterfaces.ts │ ├── customErrorInterface.ts │ ├── employeeInterfaces.ts │ ├── paymentInterfaces.ts │ └── rechargeInterfaces.ts ├── middlewares │ ├── cardMiddleware.ts │ ├── errorHandlingMiddleware.ts │ └── schemaMiddleware.ts ├── repositories │ ├── businessRepository.ts │ ├── cardRepository.ts │ ├── companyRepository.ts │ ├── employeeRepository.ts │ ├── paymentRepository.ts │ └── rechargeRepository.ts ├── routes │ ├── cardsRouter.ts │ ├── index.ts │ ├── paymentsRouter.ts │ └── virtualCardsRouter.ts ├── schemas │ ├── cardsSchemas.ts │ ├── headerSchema.ts │ └── paymentsSchemas.ts ├── server.ts ├── services │ ├── cardServices │ │ ├── activateCardService.ts │ │ ├── blockCardService.ts │ │ ├── cardsServicesValidators.ts │ │ ├── createCardService.ts │ │ ├── getCardBalanceService.ts │ │ ├── index.ts │ │ ├── rechargeCardService.ts │ │ └── unblockCardService.ts │ ├── paymentServices │ │ ├── index.ts │ │ ├── onlinePaymentService.ts │ │ └── posPaymentService.ts │ └── virtualCardServices │ │ ├── createVirtualCardService.ts │ │ ├── deleteVirtualCardService.ts │ │ └── index.ts ├── types │ ├── cardTypes.ts │ └── enviroment.d.ts └── utils │ ├── cardUtils.ts │ ├── cryptDataUtils.ts │ └── sqlUtils.ts ├── tsconfig.eslint.json ├── tsconfig.json └── valex_insomnia.json /.env.sample: -------------------------------------------------------------------------------- 1 | PORT= 2 | DATABASE_URL= 3 | CRYPTR_SECRET_KEY= -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "airbnb-base", 9 | "airbnb-typescript/base" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "project": "./tsconfig.eslint.json", 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "import", 19 | "prettier" 20 | ], 21 | "globals": { 22 | "Atomics": "readonly", 23 | "SharedArrayBuffer": "readonly" 24 | }, 25 | "root": true, 26 | "rules": { 27 | "no-console": "off", 28 | "prettier/prettier": "error", 29 | "@typescript-eslint/comma-dangle": "off", 30 | "operator-linebreak": "off", 31 | "object-curly-newline": "off", 32 | "@typescript-eslint/no-throw-literal": "off", 33 | "indent": "off", 34 | "@typescript-eslint/indent": "warn", 35 | 36 | "@typescript-eslint/quotes": "off", 37 | "camelcase": "off", 38 | "import/no-unresolved": "error", 39 | "@typescript-eslint/naming-convention": [ 40 | "error", 41 | { 42 | "selector": "interface", 43 | "format": [ 44 | "PascalCase" 45 | ], 46 | "custom": { 47 | "regex": "^I[A-Z]", 48 | "match": false 49 | } 50 | } 51 | ], 52 | "class-methods-use-this": "off", 53 | "import/prefer-default-export": "off", 54 | "no-shadow": "off", 55 | "no-useless-constructor": "off", 56 | "no-empty-function": "off", 57 | "lines-between-class-members": "off", 58 | "import/no-extraneous-dependencies": [ 59 | "error", 60 | { 61 | "devDependencies": [ 62 | "**/*.spec.js" 63 | ] 64 | } 65 | ], 66 | "no-use-before-define": "off", 67 | "@typescript-eslint/no-use-before-define": [ 68 | "error" 69 | ] 70 | }, 71 | "settings": { 72 | "import/resolver": { 73 | "typescript": {} 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist 3 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Valex API 💳 2 | 3 | ## Table of Contents 4 | - [Project Description](#project-description) 5 | - [Technologies](#technologies) 6 | - [Running the project](#running-the-project) 7 | - [Features](#features) 8 | * [Card Creation](#card-creation) 9 | * [Card Activation](#card-activation) 10 | * [Card Blocking](#card-blocking) 11 | * [Card Unblocking](#card-unblocking) 12 | * [Card Recharge](#card-recharge) 13 | * [Card Balance](#card-balance) 14 | * [POS Payment](#pos-payment) 15 | * [Online Payment](#online-payment) 16 | * [Virtual Card Creation](#virtual-card-creation) 17 | * [Virtual Card Deletion](#virtual-card-deletion) 18 | 19 | ## Project Description 20 | **Valex** is a voucher card API. 21 | The API is responsible for creating, reloading, activating, as well as processing purchases. 22 | 23 | ![status-finished](https://user-images.githubusercontent.com/97575616/152926720-d042178b-24c0-4d6b-94fb-0ccbd3c082cc.svg) 24 | 25 | ## Technologies 26 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 27 | ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) 28 | ![Express.js](https://img.shields.io/badge/express.js-%23404d59.svg?style=for-the-badge&logo=express&logoColor=%2361DAFB) 29 | ![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) 30 | 31 | ## Running the project 32 | 33 | 1. Clone the repository: 34 | 35 | ```bash 36 | git clone https://github.com/akiraTatesawa/valex-API.git 37 | ``` 38 | 2. Navigate to the project directory: 39 | 40 | ```bash 41 | cd valex-API 42 | ``` 43 | 3. Install the dependencies: 44 | 45 | ```bash 46 | npm install 47 | ``` 48 | 4. Navigate to the postgres db directory and install the database: 49 | 50 | ```bash 51 | cd src/dbStrategy/postgres/database 52 | 53 | bash ./create-database 54 | ``` 55 | 5. Set your environment variables following the .env.sample file: 56 | 57 | **Notes**: `PORT` must be a number, `DATABASE_URL` and `CRYPTR_SECRET_KEY` must be strings 58 | 59 | ```ts 60 | PORT= 61 | DATABASE_URL= 62 | CRYPTR_SECRET_KEY= 63 | ``` 64 | 6. Run the project on dev mode 65 | 66 | ```bash 67 | npm run dev 68 | ``` 69 | 70 | ## Features 71 | 72 | **Notes:** 73 | - `cardType` must only assume the following values: 'groceries', 'restaurants', 'transport', 'education', 'health'; 74 | - `employeeId` must be a valid employee id; 75 | - `x-api-key` must be a valid company key; 76 | - `password` must be a string made up of four numbers; 77 | - `CVC` must be a string made up of 3 numbers and must be a valid card CVC; 78 | - `amount` must be an integer greater than zero; 79 | 80 | ### Card Creation 81 | 82 | - **Endpoint**: 83 | ```http 84 | POST /cards/create 85 | ``` 86 | - **Request Body**: 87 | ```json 88 | { 89 | "cardType": "health", 90 | "employeeId": 2 91 | } 92 | ``` 93 | - **Request Header**: `x-api-key: "key.example.123"` 94 | - **Response Example**: 95 | ```json 96 | { 97 | "cardId": 3, 98 | "number": "7175-2620-5613-5534", 99 | "cardholderName": "CICLANA M MADEIRA", 100 | "securityCode": "074", 101 | "expirationDate": "09/27", 102 | "type": "health" 103 | } 104 | ``` 105 | 106 | ### Card Activation 107 | 108 | - **Endpoint**: 109 | ```http 110 | PATCH /cards/:cardId/activate 111 | ``` 112 | - **Request Body**: 113 | ```json 114 | { 115 | "password": "1234", 116 | "CVC": "123" 117 | } 118 | ``` 119 | 120 | ### Card Blocking 121 | 122 | - **Endpoint**: 123 | ```http 124 | PATCH /cards/:cardId/block 125 | ``` 126 | - **Request Body**: 127 | ```json 128 | { 129 | "password": "1234" 130 | } 131 | ``` 132 | 133 | ### Card Unblocking 134 | 135 | - **Endpoint**: 136 | ```http 137 | PATCH /cards/:cardId/unblock 138 | ``` 139 | - **Request Body**: 140 | ```json 141 | { 142 | "password": "1234" 143 | } 144 | ``` 145 | 146 | ### Card Recharge 147 | 148 | - **Endpoint**: 149 | ```http 150 | POST /cards/:cardId/recharge 151 | ``` 152 | - **Request Body**: 153 | ```json 154 | { 155 | "amount": 1000 156 | } 157 | ``` 158 | - **Request Header**: `x-api-key: "key.example.123"` 159 | 160 | ### Card Balance 161 | 162 | - **Endpoint**: 163 | ```http 164 | GET /cards/:cardId/balance 165 | ``` 166 | - **Response Example**: 167 | ```json 168 | { 169 | "balance": 800, 170 | "transactions": [ 171 | { 172 | "id": 2, 173 | "cardId": 3, 174 | "businessId": 5, 175 | "timestamp": "2022-09-05T04:29:35.000Z", 176 | "amount": 200, 177 | "businessName": "Unimed" 178 | } 179 | ], 180 | "recharges": [ 181 | { 182 | "id": 1, 183 | "cardId": 3, 184 | "timestamp": "2022-09-05T04:28:12.000Z", 185 | "amount": 1000 186 | } 187 | ] 188 | } 189 | ``` 190 | 191 | ### POS Payment 192 | 193 | **Notes:** The card type must be the same as the business type. 194 | 195 | - **Endpoint**: 196 | ```http 197 | POST /payments/pos 198 | ``` 199 | - **Request Body**: 200 | ```json 201 | { 202 | "cardId": 2, 203 | "password": "1234", 204 | "businessId": 5, 205 | "amount": 100 206 | } 207 | ``` 208 | 209 | ### Online Payment 210 | 211 | **Notes:** The card type must be the same as the business type. 212 | 213 | - **Endpoint**: 214 | ```http 215 | POST /payments/online 216 | ``` 217 | - **Request Body**: 218 | ```json 219 | { 220 | "cardInfo": { 221 | "cardNumber": "7175-2620-5613-5534", 222 | "cardholderName": "CICLANA M MADEIRA", 223 | "expirationDate": "09/27", 224 | "CVC": "053" 225 | }, 226 | "businessId": 5, 227 | "amount": 100 228 | } 229 | ``` 230 | ### Virtual Card Creation 231 | 232 | - **Endpoint**: 233 | ```http 234 | POST /cards/virtual/create 235 | ``` 236 | - **Request Body**: 237 | ```json 238 | { 239 | "originalCardId": 2, 240 | "password": "1234" 241 | } 242 | ``` 243 | - **Response Example**: 244 | ```json 245 | { 246 | "cardId": 3, 247 | "number": "6771-8953-1311-4172", 248 | "cardholderName": "CICLANA M MADEIRA", 249 | "securityCode": "094", 250 | "expirationDate": "09/27", 251 | "type": "health" 252 | } 253 | ``` 254 | 255 | ## Virtual Card Deletion 256 | 257 | - **Endpoint**: 258 | ```http 259 | DELETE /cards/virtual/delete 260 | ``` 261 | - **Request Body**: 262 | ```json 263 | { 264 | "virtualCardId": 3, 265 | "password": "1234" 266 | } 267 | ``` 268 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projeto18-valex", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npx tsc", 9 | "start": "node ./dist/server.js", 10 | "dev": "ts-node-dev --respawn --transpile-only --ignore-watch node_modules ./src/server.ts " 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@faker-js/faker": "^7.5.0", 17 | "@types/bcrypt": "^5.0.0", 18 | "@types/cors": "^2.8.12", 19 | "@types/cryptr": "^4.0.1", 20 | "@types/dotenv": "^8.2.0", 21 | "@types/express": "^4.17.13", 22 | "@types/joi": "^17.2.3", 23 | "@types/node": "^18.7.14", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.36.0", 26 | "eslint": "^8.23.0", 27 | "eslint-config-airbnb-base": "^15.0.0", 28 | "eslint-config-airbnb-typescript": "^17.0.0", 29 | "eslint-import-resolver-typescript": "^3.5.0", 30 | "eslint-plugin-import": "^2.26.0", 31 | "eslint-plugin-prettier": "^4.2.1", 32 | "prettier": "^2.7.1", 33 | "ts-node-dev": "^2.0.0", 34 | "typescript": "^4.8.2" 35 | }, 36 | "dependencies": { 37 | "bcrypt": "^5.0.1", 38 | "cors": "^2.8.5", 39 | "cryptr": "^6.0.3", 40 | "dayjs": "^1.11.5", 41 | "dotenv": "^16.0.1", 42 | "express": "^4.18.1", 43 | "express-async-errors": "^3.1.1", 44 | "joi": "^17.6.0", 45 | "pg": "^8.8.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/classes/Card.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | import { faker } from "@faker-js/faker"; 4 | import dayjs from "dayjs"; 5 | import { NewCard as CardInterface } from "../interfaces/cardInterfaces"; 6 | import { TransactionTypes } from "../types/cardTypes"; 7 | import { CryptDataInterface } from "../utils/cryptDataUtils"; 8 | 9 | export class Card implements CardInterface { 10 | readonly employeeId: number; 11 | 12 | readonly type: TransactionTypes; 13 | 14 | readonly number: string; 15 | 16 | readonly cardholderName: string; 17 | 18 | readonly securityCode: string; 19 | 20 | readonly expirationDate: string; 21 | 22 | readonly password: string | undefined = undefined; 23 | 24 | readonly isBlocked: boolean = false; 25 | 26 | readonly isVirtual: boolean = false; 27 | 28 | readonly originalCardId: number | undefined = undefined; 29 | 30 | constructor( 31 | employeeId: number, 32 | type: TransactionTypes, 33 | cardholderName: string, 34 | private cryptDataUtils: CryptDataInterface 35 | ) { 36 | this.employeeId = employeeId; 37 | this.type = type; 38 | this.cardholderName = cardholderName; 39 | this.cryptDataUtils = cryptDataUtils; 40 | 41 | this.securityCode = this.cryptDataUtils.encryptData( 42 | faker.finance.creditCardCVV() 43 | ); 44 | 45 | this.expirationDate = dayjs().add(5, "y").format("MM/YY"); 46 | 47 | this.number = faker.finance.creditCardNumber("####-####-####-####"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/classes/CustomError.ts: -------------------------------------------------------------------------------- 1 | import { CustomError as CustomErrorInterface } from "../interfaces/customErrorInterface"; 2 | import { ErrorType } from "../middlewares/errorHandlingMiddleware"; 3 | 4 | export class CustomError implements CustomErrorInterface { 5 | type: ErrorType; 6 | 7 | message: string; 8 | 9 | constructor(errorType: ErrorType, message: string) { 10 | this.type = errorType; 11 | this.message = message; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/classes/VirtualCard.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import { faker } from "@faker-js/faker"; 3 | import { TransactionTypes } from "../types/cardTypes"; 4 | import { CryptDataInterface } from "../utils/cryptDataUtils"; 5 | import { Card } from "./Card"; 6 | 7 | export class VirtualCard extends Card { 8 | readonly number: string; 9 | 10 | readonly originalCardId: number | undefined = undefined; 11 | 12 | readonly isVirtual: boolean = true; 13 | 14 | readonly password: string | undefined = undefined; 15 | 16 | constructor( 17 | employeeId: number, 18 | type: TransactionTypes, 19 | cardholderName: string, 20 | cryptDataUtils: CryptDataInterface, 21 | originalCardId: number, 22 | originalCardPassword: string | undefined 23 | ) { 24 | super(employeeId, type, cardholderName, cryptDataUtils); 25 | this.originalCardId = originalCardId; 26 | this.password = originalCardPassword; 27 | 28 | this.number = faker.finance.creditCardNumber("mastercard"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config(); 4 | -------------------------------------------------------------------------------- /src/controllers/cardController.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/indent */ 2 | import { Request, Response } from "express"; 3 | import { TransactionTypes } from "../types/cardTypes"; 4 | import { 5 | activateCardService, 6 | blockCardService, 7 | createCardService, 8 | getBalanceService, 9 | unblockCardService, 10 | } from "../services/cardServices"; 11 | 12 | export async function createCard( 13 | req: Request<{}, {}, { cardType: TransactionTypes; employeeId: number }>, 14 | res: Response 15 | ) { 16 | const { employeeId, cardType } = req.body; 17 | const { API_KEY } = res.locals; 18 | 19 | const card = await createCardService.create(API_KEY, employeeId, cardType); 20 | 21 | return res.status(201).send(card); 22 | } 23 | 24 | export async function activateCard( 25 | req: Request<{ cardId: string }, {}, { password: string; CVC: string }>, 26 | res: Response 27 | ) { 28 | const { cardId } = req.params; 29 | const { password, CVC } = req.body; 30 | 31 | await activateCardService.execute(parseInt(cardId, 10), password, CVC); 32 | 33 | return res.sendStatus(200); 34 | } 35 | 36 | export async function blockCard( 37 | req: Request<{ cardId: string }, {}, { password: string }>, 38 | res: Response 39 | ) { 40 | const { cardId } = req.params; 41 | const { password } = req.body; 42 | 43 | await blockCardService.execute(parseInt(cardId, 10), password); 44 | 45 | return res.sendStatus(200); 46 | } 47 | 48 | export async function unblockCard( 49 | req: Request<{ cardId: string }, {}, { password: string }>, 50 | res: Response 51 | ) { 52 | const { cardId } = req.params; 53 | const { password } = req.body; 54 | 55 | await unblockCardService.execute(parseInt(cardId, 10), password); 56 | 57 | return res.sendStatus(200); 58 | } 59 | export async function getCardBalance( 60 | req: Request<{ cardId: string }>, 61 | res: Response 62 | ) { 63 | const { cardId } = req.params; 64 | 65 | const cardBalance = await getBalanceService.execute(parseInt(cardId, 10)); 66 | 67 | return res.send(cardBalance); 68 | } 69 | -------------------------------------------------------------------------------- /src/controllers/paymentController.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/indent */ 2 | import { Request, Response } from "express"; 3 | import { OnlinePaymentData } from "../interfaces/paymentInterfaces"; 4 | import { 5 | onlinePaymentService, 6 | posPaymentService, 7 | } from "../services/paymentServices"; 8 | 9 | export async function buyFromBusinessPOS( 10 | req: Request< 11 | {}, 12 | {}, 13 | { cardId: number; password: string; businessId: number; amount: number } 14 | >, 15 | res: Response 16 | ) { 17 | const { cardId, password, businessId, amount } = req.body; 18 | 19 | await posPaymentService.execute(cardId, password, businessId, amount); 20 | 21 | return res.sendStatus(200); 22 | } 23 | 24 | export async function buyFromBusinessOnline( 25 | req: Request<{}, {}, OnlinePaymentData>, 26 | res: Response 27 | ) { 28 | await onlinePaymentService.execute(req.body); 29 | 30 | return res.sendStatus(200); 31 | } 32 | -------------------------------------------------------------------------------- /src/controllers/rechargeController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { rechargeCardService } from "../services/cardServices"; 3 | 4 | export async function rechargeCard( 5 | req: Request<{ cardId: string }, {}, { amount: number }>, 6 | res: Response 7 | ) { 8 | const { cardId } = req.params; 9 | const { amount } = req.body; 10 | const { API_KEY } = res.locals; 11 | 12 | await rechargeCardService.execute(parseInt(cardId, 10), API_KEY, amount); 13 | 14 | return res.sendStatus(201); 15 | } 16 | -------------------------------------------------------------------------------- /src/controllers/virtualCardController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { 3 | createVirtualCardService, 4 | deleteVirtualCardService, 5 | } from "../services/virtualCardServices"; 6 | 7 | export async function createVirtualCard( 8 | req: Request<{}, {}, { originalCardId: number; password: string }>, 9 | res: Response 10 | ) { 11 | const { originalCardId, password } = req.body; 12 | 13 | const virtualCard = await createVirtualCardService.create( 14 | originalCardId, 15 | password 16 | ); 17 | 18 | return res.status(201).send(virtualCard); 19 | } 20 | 21 | export async function deleteVirtualCard( 22 | req: Request<{}, {}, { virtualCardId: number; password: string }>, 23 | res: Response 24 | ) { 25 | const { virtualCardId, password } = req.body; 26 | 27 | await deleteVirtualCardService.delete(virtualCardId, password); 28 | 29 | return res.sendStatus(204); 30 | } 31 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/connection.ts: -------------------------------------------------------------------------------- 1 | import "../../config/config"; 2 | import pg from "pg"; 3 | 4 | const { Pool } = pg; 5 | 6 | export const connection = new Pool({ 7 | connectionString: process.env.DATABASE_URL, 8 | }); 9 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/connect-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checando status do postgres..." && 8 | checkPostgres && 9 | 10 | enterPostgresCli $DATABASE; 11 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/create-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checando status do postgres..." && 8 | checkPostgres && 9 | 10 | echo "Criando banco de dados..." && 11 | createDatabase $DATABASE && 12 | 13 | echo "Executando scripts..." && 14 | runScripts $DATABASE; 15 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/destroy-database: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/functions; 4 | 5 | DATABASE=$(cat src/database); 6 | 7 | echo "Checando status do postgres..." && 8 | checkPostgres && 9 | 10 | echo "Destruindo banco de dados..." && 11 | destroyDatabase $DATABASE; 12 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/scripts/001-cria-banco.sql: -------------------------------------------------------------------------------- 1 | CREATE TYPE "transactionType" AS ENUM ('groceries', 'restaurant', 'transport', 'education', 'health'); 2 | 3 | CREATE TABLE "companies"( 4 | "id" SERIAL NOT NULL PRIMARY KEY, 5 | "name" TEXT NOT NULL UNIQUE, 6 | "apiKey" TEXT NULL 7 | ); 8 | 9 | CREATE TABLE "employees"( 10 | "id" SERIAL NOT NULL PRIMARY KEY, 11 | "fullName" TEXT NOT NULL, 12 | "cpf" TEXT NOT NULL UNIQUE, 13 | "email" TEXT NOT NULL UNIQUE, 14 | "companyId" INTEGER NOT NULL REFERENCES companies(id) 15 | ); 16 | 17 | CREATE TABLE "cards"( 18 | "id" SERIAL NOT NULL PRIMARY KEY, 19 | "employeeId" INTEGER NOT NULL REFERENCES employees(id), 20 | "number" TEXT NOT NULL UNIQUE, 21 | "cardholderName" TEXT NOT NULL, 22 | "securityCode" TEXT NOT NULL, 23 | "expirationDate" TEXT NOT NULL, 24 | "password" TEXT NULL, 25 | "isVirtual" BOOLEAN NOT NULL, 26 | "originalCardId" INTEGER NULL REFERENCES cards(id), 27 | "isBlocked" BOOLEAN NOT NULL, 28 | "type" "transactionType" NOT NULL 29 | ); 30 | 31 | CREATE TABLE "businesses"( 32 | "id" SERIAL NOT NULL PRIMARY KEY, 33 | "name" TEXT NOT NULL UNIQUE, 34 | "type" "transactionType" NOT NULL 35 | ); 36 | 37 | CREATE TABLE "payments"( 38 | "id" SERIAL NOT NULL PRIMARY KEY, 39 | "cardId" INTEGER NOT NULL REFERENCES cards(id), 40 | "businessId" INTEGER NOT NULL REFERENCES businesses(id), 41 | "timestamp" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT NOW(), 42 | "amount" INTEGER NOT NULL 43 | ); 44 | 45 | CREATE TABLE "recharges"( 46 | "id" SERIAL NOT NULL PRIMARY KEY, 47 | "cardId" INTEGER NOT NULL REFERENCES cards(id), 48 | "timestamp" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT NOW(), 49 | "amount" INTEGER NOT NULL 50 | ); -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/scripts/002-popula-dados.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO companies ("id", "name", "apiKey") VALUES (1, 'Driven', 'zadKLNx.DzvOVjQH01TumGl2urPjPQSxUbf67vs0'); 2 | INSERT INTO employees ("id", "fullName", "cpf", "email", "companyId") VALUES (1, 'Fulano Rubens da Silva', '47100935741', 'fulano.silva@gmail.com', 1); 3 | INSERT INTO employees ("id", "fullName", "cpf", "email", "companyId") VALUES (2,'Ciclana Maria Madeira', '08434681895', 'ciclaninha@gmail.com', 1); 4 | INSERT INTO businesses ("id", "name", "type") VALUES (1, 'Responde Aí', 'education'); 5 | INSERT INTO businesses ("id", "name", "type") VALUES (2, 'Extra', 'groceries'); 6 | INSERT INTO businesses ("id", "name", "type") VALUES (3, 'Driven Eats', 'restaurant'); 7 | INSERT INTO businesses ("id", "name", "type") VALUES (4, 'Uber', 'transport'); 8 | INSERT INTO businesses ("id", "name", "type") VALUES (5, 'Unimed', 'health'); -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/src/database: -------------------------------------------------------------------------------- 1 | valex 2 | -------------------------------------------------------------------------------- /src/dbStrategy/postgres/database/src/functions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | checkPostgres () { 4 | if [[ $(id -u postgres 2> /dev/null) = "" ]]; then 5 | echo "Parece que o postgres não está instalado!"; 6 | echo "Execute: sudo apt update && sudo apt install postgresql postgresql-contrib"; 7 | exit; 8 | fi 9 | 10 | if [[ $(pgrep -u postgres -fa -- -D) = "" ]]; then 11 | echo "PostgreSQL não está sendo executado, tentando iniciar..."; 12 | sudo service postgresql start; 13 | fi 14 | } 15 | 16 | createDatabase () { 17 | sudo su -c "psql -c \"CREATE DATABASE $1\";" postgres; 18 | } 19 | 20 | destroyDatabase () { 21 | sudo su -c "psql -c \"DROP DATABASE $1\";" postgres; 22 | } 23 | 24 | runScripts () { 25 | for f in scripts/*; do 26 | echo "Executando script $f..."; 27 | sudo su -c "psql -d $1 -f $f" postgres; 28 | done 29 | } 30 | 31 | enterPostgresCli () { 32 | sudo su -c "psql -d $1" postgres; 33 | } 34 | -------------------------------------------------------------------------------- /src/interfaces/businessInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { TransactionTypes } from "../types/cardTypes"; 2 | 3 | export interface Business { 4 | id: number; 5 | name: string; 6 | type: TransactionTypes; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/cardInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { TransactionTypes } from "../types/cardTypes"; 2 | 3 | export interface NewCard { 4 | employeeId: number; 5 | number: string; 6 | cardholderName: string; 7 | securityCode: string; 8 | expirationDate: string; 9 | password?: string; 10 | isVirtual: boolean; 11 | originalCardId?: number; 12 | isBlocked: boolean; 13 | type: TransactionTypes; 14 | } 15 | 16 | export interface Card extends NewCard { 17 | id: number; 18 | } 19 | 20 | export interface ResponseCard { 21 | cardId: number; 22 | number: string; 23 | cardholderName: string; 24 | securityCode: string; 25 | expirationDate: string; 26 | type: TransactionTypes; 27 | } 28 | -------------------------------------------------------------------------------- /src/interfaces/companyInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Company { 2 | id: number; 3 | name: string; 4 | apiKey?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/interfaces/customErrorInterface.ts: -------------------------------------------------------------------------------- 1 | import { ErrorType } from "../middlewares/errorHandlingMiddleware"; 2 | 3 | export interface CustomError { 4 | type: ErrorType; 5 | 6 | message: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/employeeInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Employee { 2 | id: number; 3 | fullName: string; 4 | cpf: string; 5 | email: string; 6 | companyId: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/paymentInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { CardInfo } from "../types/cardTypes"; 2 | 3 | export interface Payment { 4 | id: number; 5 | cardId: number; 6 | businessId: number; 7 | timestamp: Date; 8 | amount: number; 9 | } 10 | 11 | export interface OnlinePaymentData { 12 | cardInfo: CardInfo; 13 | businessId: number; 14 | amount: number; 15 | } 16 | 17 | export interface FormattedPayment { 18 | id: number; 19 | cardId: number; 20 | businessId: number; 21 | timestamp: string; 22 | amount: number; 23 | businessName: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/interfaces/rechargeInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Recharge { 2 | id: number; 3 | cardId: number; 4 | timestamp: Date; 5 | amount: number; 6 | } 7 | 8 | export interface FormattedRecharge { 9 | id: number; 10 | cardId: number; 11 | timestamp: string; 12 | amount: number; 13 | } 14 | -------------------------------------------------------------------------------- /src/middlewares/cardMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { CustomError } from "../classes/CustomError"; 3 | import { API_KEYSchema } from "../schemas/headerSchema"; 4 | 5 | export async function validateApiKey( 6 | req: Request, 7 | res: Response, 8 | next: NextFunction 9 | ) { 10 | const { "x-api-key": API_KEY } = req.headers; 11 | 12 | const { error: headerError } = API_KEYSchema.validate( 13 | { API_KEY }, 14 | { abortEarly: false } 15 | ); 16 | 17 | if (headerError) { 18 | const message = headerError.details 19 | .map((detail) => detail.message) 20 | .join("; "); 21 | throw new CustomError("error_unprocessable_entity", message); 22 | } 23 | 24 | res.locals.API_KEY = API_KEY; 25 | 26 | return next(); 27 | } 28 | 29 | export async function validateCardActivation( 30 | req: Request<{ cardId: string }, {}, { password: string; CVC: string }>, 31 | res: Response, 32 | next: NextFunction 33 | ) { 34 | const { password, CVC } = req.body; 35 | 36 | if (!password || !CVC) { 37 | throw new CustomError("error_bad_request", "Password or CVC missing"); 38 | } 39 | 40 | return next(); 41 | } 42 | 43 | export async function validateCardBlockUnblock( 44 | req: Request<{ cardId: string }, {}, { password: string }>, 45 | res: Response, 46 | next: NextFunction 47 | ) { 48 | const { password } = req.body; 49 | 50 | if (!password) { 51 | throw new CustomError("error_bad_request", "Password missing"); 52 | } 53 | 54 | return next(); 55 | } 56 | 57 | export async function validateCardId( 58 | req: Request<{ cardId: string }>, 59 | _res: Response, 60 | next: NextFunction 61 | ) { 62 | const { cardId } = req.params; 63 | 64 | if (!cardId.match(/^\d+$/)) { 65 | throw new CustomError( 66 | "error_unprocessable_entity", 67 | "Card Id must be a number" 68 | ); 69 | } 70 | 71 | return next(); 72 | } 73 | -------------------------------------------------------------------------------- /src/middlewares/errorHandlingMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler, NextFunction, Request, Response } from "express"; 2 | 3 | const Errors = { 4 | error_bad_request: { 5 | status: 400, 6 | name: "Error: Bad Request", 7 | }, 8 | error_unauthorized: { 9 | status: 401, 10 | name: "Error: Unauthorized", 11 | }, 12 | error_forbidden: { 13 | status: 403, 14 | name: "Error: Forbidden", 15 | }, 16 | error_not_found: { 17 | status: 404, 18 | name: "Error: Not Found", 19 | }, 20 | error_conflict: { 21 | status: 409, 22 | name: "Error: Conflict", 23 | }, 24 | error_unprocessable_entity: { 25 | status: 422, 26 | name: "Error: Unprocessable Entity", 27 | }, 28 | error_internal_server_error: { 29 | status: 500, 30 | name: "Error: Internal Server Error", 31 | }, 32 | }; 33 | 34 | export type ErrorType = keyof typeof Errors; 35 | 36 | interface ErrorHandlerObject extends ErrorRequestHandler { 37 | type: ErrorType; 38 | message: string; 39 | } 40 | 41 | export async function errorHandlingMiddleware( 42 | error: ErrorHandlerObject, 43 | _req: Request, 44 | res: Response, 45 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 46 | _next: NextFunction 47 | ) { 48 | const { message, type } = error; 49 | 50 | if (Errors[type]?.status) { 51 | const { status, name } = Errors[type]; 52 | return res.status(status).json({ name, message }); 53 | } 54 | 55 | return res.sendStatus(500); 56 | } 57 | -------------------------------------------------------------------------------- /src/middlewares/schemaMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { CustomError } from "../classes/CustomError"; 3 | import { 4 | createCardSchema as create, 5 | activationCardSchema as activation, 6 | blockUnblockCardSchema as blockUnblock, 7 | rechargeCardSchema as recharge, 8 | createVirtualCardSchema as createVirtual, 9 | deleteVirtualCardSchema as deleteVirtual, 10 | } from "../schemas/cardsSchemas"; 11 | import { 12 | paymentPOSSchema as paymentPOS, 13 | paymentOnlineSchema as paymentOnline, 14 | } from "../schemas/paymentsSchemas"; 15 | 16 | const Schemas = { 17 | create, 18 | createVirtual, 19 | deleteVirtual, 20 | activation, 21 | blockUnblock, 22 | paymentPOS, 23 | paymentOnline, 24 | recharge, 25 | }; 26 | 27 | type Validator = keyof typeof Schemas; 28 | 29 | export function validateBody( 30 | validator: Validator 31 | ): (req: Request, _res: Response, next: NextFunction) => Promise { 32 | if (!Object.hasOwn(Schemas, validator)) { 33 | throw new CustomError( 34 | "error_internal_server_error", 35 | "Invalid schema validator" 36 | ); 37 | } 38 | 39 | return async (req: Request, _res: Response, next: NextFunction) => { 40 | const { error } = Schemas[validator].validate(req.body, { 41 | abortEarly: false, 42 | }); 43 | 44 | if (error) { 45 | const message = error.details.map((detail) => detail.message).join("; "); 46 | throw new CustomError("error_unprocessable_entity", message); 47 | } 48 | 49 | return next(); 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/repositories/businessRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Business } from "../interfaces/businessInterfaces"; 3 | 4 | export interface BusinessRepositoryInterface { 5 | findById: (id: number) => Promise; 6 | } 7 | 8 | export class BusinessRepository implements BusinessRepositoryInterface { 9 | async findById(id: number): Promise { 10 | const result = await connection.query( 11 | "SELECT * FROM businesses WHERE id=$1", 12 | [id] 13 | ); 14 | 15 | return result.rows[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/repositories/cardRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Card } from "../interfaces/cardInterfaces"; 3 | import { TransactionTypes } from "../types/cardTypes"; 4 | import { mapObjectToUpdateQuery } from "../utils/sqlUtils"; 5 | 6 | type CardInsertData = Omit; 7 | type CardUpdateData = Partial; 8 | 9 | type CardId = { 10 | cardId: number; 11 | }; 12 | 13 | interface InsertResult { 14 | rows: CardId[]; 15 | } 16 | 17 | export interface CardRepositoryInterface { 18 | insert: (cardData: CardInsertData) => Promise; 19 | find: () => Promise; 20 | findById: (id: number) => Promise; 21 | findByTypeAndEmployeeId: ( 22 | type: TransactionTypes, 23 | employeeId: number 24 | ) => Promise; 25 | findByCardDetails: ( 26 | number: string, 27 | cardholderName: string, 28 | expirationDate: string 29 | ) => Promise; 30 | update: (id: number, cardData: CardUpdateData) => Promise; 31 | remove: (id: number) => Promise; 32 | } 33 | 34 | export class CardRepository implements CardRepositoryInterface { 35 | async insert(cardData: CardInsertData): Promise { 36 | const { 37 | employeeId, 38 | number, 39 | cardholderName, 40 | securityCode, 41 | expirationDate, 42 | password, 43 | isVirtual, 44 | originalCardId, 45 | isBlocked, 46 | type, 47 | } = cardData; 48 | 49 | const { 50 | rows: [{ cardId }], 51 | }: InsertResult = await connection.query( 52 | ` 53 | INSERT INTO cards ("employeeId", number, "cardholderName", "securityCode", 54 | "expirationDate", password, "isVirtual", "originalCardId", "isBlocked", type) 55 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 56 | RETURNING id AS "cardId" 57 | `, 58 | [ 59 | employeeId, 60 | number, 61 | cardholderName, 62 | securityCode, 63 | expirationDate, 64 | password, 65 | isVirtual, 66 | originalCardId, 67 | isBlocked, 68 | type, 69 | ] 70 | ); 71 | 72 | return cardId; 73 | } 74 | 75 | async find(): Promise { 76 | const result = await connection.query("SELECT * FROM cards"); 77 | return result.rows; 78 | } 79 | 80 | async findById(id: number): Promise { 81 | const result = await connection.query( 82 | "SELECT * FROM cards WHERE id=$1", 83 | [id] 84 | ); 85 | return result.rows[0]; 86 | } 87 | 88 | async findByTypeAndEmployeeId( 89 | type: TransactionTypes, 90 | employeeId: number 91 | ): Promise { 92 | const result = await connection.query( 93 | `SELECT * FROM cards WHERE type=$1 AND "employeeId"=$2`, 94 | [type, employeeId] 95 | ); 96 | return result.rows[0]; 97 | } 98 | 99 | async findByCardDetails( 100 | number: string, 101 | cardholderName: string, 102 | expirationDate: string 103 | ): Promise { 104 | const result = await connection.query( 105 | ` SELECT 106 | * 107 | FROM cards 108 | WHERE number=$1 AND "cardholderName"=$2 AND "expirationDate"=$3`, 109 | [number, cardholderName, expirationDate] 110 | ); 111 | return result.rows[0]; 112 | } 113 | 114 | async update(id: number, cardData: CardUpdateData): Promise { 115 | const { objectColumns: cardColumns, objectValues: cardValues } = 116 | mapObjectToUpdateQuery({ 117 | object: cardData, 118 | offset: 2, 119 | }); 120 | 121 | connection.query( 122 | ` 123 | UPDATE cards 124 | SET ${cardColumns} 125 | WHERE $1=id 126 | `, 127 | [id, ...cardValues] 128 | ); 129 | } 130 | 131 | async remove(id: number): Promise { 132 | connection.query("DELETE FROM cards WHERE id=$1", [id]); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/repositories/companyRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Company } from "../interfaces/companyInterfaces"; 3 | 4 | export interface CompanyRepositoryInterface { 5 | findByApiKey: (apiKey: string) => Promise; 6 | } 7 | 8 | export class CompanyRepository { 9 | async findByApiKey(apiKey: string): Promise { 10 | const result = await connection.query( 11 | `SELECT * FROM companies WHERE "apiKey"=$1`, 12 | [apiKey] 13 | ); 14 | 15 | return result.rows[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/repositories/employeeRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Employee } from "../interfaces/employeeInterfaces"; 3 | 4 | export interface EmployeeRepositoryInterface { 5 | findById: (id: number) => Promise; 6 | } 7 | 8 | export class EmployeeRepository implements EmployeeRepositoryInterface { 9 | async findById(id: number): Promise { 10 | const result = await connection.query( 11 | "SELECT * FROM employees WHERE id=$1", 12 | [id] 13 | ); 14 | 15 | return result.rows[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/repositories/paymentRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Payment } from "../interfaces/paymentInterfaces"; 3 | 4 | export type PaymentWithBusinessName = Payment & { businessName: string }; 5 | export type PaymentInsertData = Omit; 6 | 7 | export interface PaymentRepositoryInterface { 8 | findByCardId: (cardId: number) => Promise; 9 | insert: (paymentData: PaymentInsertData) => Promise; 10 | } 11 | 12 | export class PaymentRepository implements PaymentRepositoryInterface { 13 | async findByCardId(cardId: number): Promise { 14 | const result = await connection.query( 15 | `SELECT 16 | payments.*, 17 | businesses.name as "businessName" 18 | FROM payments 19 | JOIN businesses ON businesses.id=payments."businessId" 20 | WHERE "cardId"=$1 21 | `, 22 | [cardId] 23 | ); 24 | 25 | return result.rows; 26 | } 27 | 28 | async insert(paymentData: PaymentInsertData): Promise { 29 | const { cardId, businessId, amount } = paymentData; 30 | 31 | connection.query( 32 | `INSERT INTO payments ("cardId", "businessId", amount) VALUES ($1, $2, $3)`, 33 | [cardId, businessId, amount] 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/repositories/rechargeRepository.ts: -------------------------------------------------------------------------------- 1 | import { connection } from "../dbStrategy/postgres/connection"; 2 | import { Recharge } from "../interfaces/rechargeInterfaces"; 3 | 4 | export type RechargeInsertData = Omit; 5 | 6 | export interface RechargeRepositoryInterface { 7 | findByCardId: (cardId: number) => Promise; 8 | insert: (rechargeData: RechargeInsertData) => Promise; 9 | } 10 | 11 | export class RechargeRepository implements RechargeRepositoryInterface { 12 | async findByCardId(cardId: number): Promise { 13 | const result = await connection.query( 14 | `SELECT * FROM recharges WHERE "cardId"=$1`, 15 | [cardId] 16 | ); 17 | 18 | return result.rows; 19 | } 20 | 21 | async insert(rechargeData: RechargeInsertData): Promise { 22 | const { cardId, amount } = rechargeData; 23 | 24 | connection.query( 25 | `INSERT INTO recharges ("cardId", amount) VALUES ($1, $2)`, 26 | [cardId, amount] 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/cardsRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as CardControllers from "../controllers/cardController"; 3 | import * as RechargeController from "../controllers/rechargeController"; 4 | import * as CardMiddlewares from "../middlewares/cardMiddleware"; 5 | import * as SchemaValidatorMiddleware from "../middlewares/schemaMiddleware"; 6 | 7 | export const cardsRouter = Router(); 8 | 9 | cardsRouter.post( 10 | "/cards/create", 11 | SchemaValidatorMiddleware.validateBody("create"), 12 | CardMiddlewares.validateApiKey, 13 | CardControllers.createCard 14 | ); 15 | 16 | cardsRouter.patch( 17 | "/cards/:cardId/activate", 18 | SchemaValidatorMiddleware.validateBody("activation"), 19 | CardMiddlewares.validateCardId, 20 | CardMiddlewares.validateCardActivation, 21 | CardControllers.activateCard 22 | ); 23 | 24 | cardsRouter.patch( 25 | "/cards/:cardId/block", 26 | SchemaValidatorMiddleware.validateBody("blockUnblock"), 27 | CardMiddlewares.validateCardId, 28 | CardMiddlewares.validateCardBlockUnblock, 29 | CardControllers.blockCard 30 | ); 31 | 32 | cardsRouter.patch( 33 | "/cards/:cardId/unblock", 34 | SchemaValidatorMiddleware.validateBody("blockUnblock"), 35 | CardMiddlewares.validateCardId, 36 | CardMiddlewares.validateCardBlockUnblock, 37 | CardControllers.unblockCard 38 | ); 39 | 40 | cardsRouter.post( 41 | "/cards/:cardId/recharge", 42 | SchemaValidatorMiddleware.validateBody("recharge"), 43 | CardMiddlewares.validateCardId, 44 | CardMiddlewares.validateApiKey, 45 | RechargeController.rechargeCard 46 | ); 47 | 48 | cardsRouter.get( 49 | "/cards/:cardId/balance", 50 | CardMiddlewares.validateCardId, 51 | CardControllers.getCardBalance 52 | ); 53 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { cardsRouter } from "./cardsRouter"; 3 | import { paymentsRouter } from "./paymentsRouter"; 4 | import { virtualCardsRouter } from "./virtualCardsRouter"; 5 | 6 | export const serverRouter = Router(); 7 | 8 | serverRouter.use(cardsRouter); 9 | serverRouter.use(paymentsRouter); 10 | serverRouter.use(virtualCardsRouter); 11 | -------------------------------------------------------------------------------- /src/routes/paymentsRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as PaymentController from "../controllers/paymentController"; 3 | import * as SchemaValidatorMiddleware from "../middlewares/schemaMiddleware"; 4 | 5 | export const paymentsRouter = Router(); 6 | 7 | paymentsRouter.post( 8 | "/payments/pos", 9 | SchemaValidatorMiddleware.validateBody("paymentPOS"), 10 | PaymentController.buyFromBusinessPOS 11 | ); 12 | 13 | paymentsRouter.post( 14 | "/payments/online", 15 | SchemaValidatorMiddleware.validateBody("paymentOnline"), 16 | PaymentController.buyFromBusinessOnline 17 | ); 18 | -------------------------------------------------------------------------------- /src/routes/virtualCardsRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as VirtualCardControllers from "../controllers/virtualCardController"; 3 | import * as SchemaValidatorMiddleware from "../middlewares/schemaMiddleware"; 4 | 5 | export const virtualCardsRouter = Router(); 6 | 7 | // Create a virtual card 8 | virtualCardsRouter.post( 9 | "/cards/virtual/create", 10 | SchemaValidatorMiddleware.validateBody("createVirtual"), 11 | VirtualCardControllers.createVirtualCard 12 | ); 13 | 14 | // Delete a virtual card 15 | virtualCardsRouter.delete( 16 | "/cards/virtual/delete", 17 | SchemaValidatorMiddleware.validateBody("deleteVirtual"), 18 | VirtualCardControllers.deleteVirtualCard 19 | ); 20 | -------------------------------------------------------------------------------- /src/schemas/cardsSchemas.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export const createCardSchema = Joi.object({ 4 | cardType: Joi.string() 5 | .valid("groceries", "restaurant", "transport", "education", "health") 6 | .required(), 7 | employeeId: Joi.number().integer().required(), 8 | }); 9 | 10 | export const createVirtualCardSchema = Joi.object({ 11 | originalCardId: Joi.number().integer().required(), 12 | password: Joi.string() 13 | .length(4) 14 | .pattern(/^\d+$/) 15 | .message(`"password" must only contain numbers`) 16 | .required(), 17 | }); 18 | 19 | export const deleteVirtualCardSchema = Joi.object({ 20 | virtualCardId: Joi.number().integer().required(), 21 | password: Joi.string() 22 | .length(4) 23 | .pattern(/^\d+$/) 24 | .message(`"password" must only contain numbers`) 25 | .required(), 26 | }); 27 | 28 | export const activationCardSchema = Joi.object({ 29 | CVC: Joi.string() 30 | .length(3) 31 | .pattern(/^\d+$/) 32 | .message(`"CVC" must only contain numbers`) 33 | .required(), 34 | password: Joi.string() 35 | .length(4) 36 | .pattern(/^\d+$/) 37 | .message(`"password" must only contain numbers`) 38 | .required(), 39 | }); 40 | 41 | export const blockUnblockCardSchema = Joi.object({ 42 | password: Joi.string() 43 | .length(4) 44 | .pattern(/^\d+$/) 45 | .message(`"password" must only contain numbers`) 46 | .required(), 47 | }); 48 | 49 | export const rechargeCardSchema = Joi.object({ 50 | amount: Joi.number().integer().greater(0).required(), 51 | }); 52 | -------------------------------------------------------------------------------- /src/schemas/headerSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export const API_KEYSchema = Joi.object({ 4 | API_KEY: Joi.string().required(), 5 | }); 6 | -------------------------------------------------------------------------------- /src/schemas/paymentsSchemas.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export const paymentPOSSchema = Joi.object({ 4 | cardId: Joi.number().integer().required(), 5 | password: Joi.string() 6 | .length(4) 7 | .pattern(/^\d+$/) 8 | .message(`"password" must only contain numbers`) 9 | .required(), 10 | businessId: Joi.number().integer().required(), 11 | amount: Joi.number().integer().greater(0).required(), 12 | }); 13 | 14 | export const paymentOnlineSchema = Joi.object({ 15 | cardInfo: Joi.object({ 16 | cardNumber: Joi.string() 17 | .length(19) 18 | .pattern(/^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}$/) 19 | .message( 20 | `"Card Number" must follow the [0-9](4)-[0-9](4)-[0-9](4)-[0-9](4) pattern` 21 | ) 22 | .required(), 23 | cardholderName: Joi.string() 24 | .pattern(/^[\p{Lu}\p{Mark}\s]+$/u) 25 | .message( 26 | `"Cardholder Name" must only contain uppercase letters and whitespace` 27 | ) 28 | .required(), 29 | expirationDate: Joi.string() 30 | .length(5) 31 | .pattern(/^[0-9]{2}\/[0-9]{2}$/) 32 | .message(`"Expiration" date must follow the MM/YY format`) 33 | .required(), 34 | CVC: Joi.string() 35 | .length(3) 36 | .pattern(/^\d+$/) 37 | .message(`"CVC" must only contain numbers`) 38 | .required(), 39 | }).required(), 40 | businessId: Joi.number().integer().required(), 41 | amount: Joi.number().integer().greater(0).required(), 42 | }); 43 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import "./config/config"; 2 | import cors from "cors"; 3 | import express from "express"; 4 | import "express-async-errors"; 5 | import { serverRouter } from "./routes"; 6 | import { errorHandlingMiddleware } from "./middlewares/errorHandlingMiddleware"; 7 | 8 | const server = express(); 9 | 10 | server.use(cors()); 11 | server.use(express.json()); 12 | 13 | server.use(serverRouter); 14 | server.use(errorHandlingMiddleware); 15 | 16 | const { PORT } = process.env; 17 | 18 | server.listen(PORT, () => { 19 | console.log(`Server running on port ${PORT}`); 20 | }); 21 | -------------------------------------------------------------------------------- /src/services/cardServices/activateCardService.ts: -------------------------------------------------------------------------------- 1 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 2 | import { CardValidatorInterface } from "./cardsServicesValidators"; 3 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 4 | import { CardUtilsInterface } from "../../utils/cardUtils"; 5 | 6 | export interface ActivateCardServiceInterface { 7 | execute: (cardId: number, password: string, CVC: string) => Promise; 8 | } 9 | 10 | export class ActivateCardService implements ActivateCardServiceInterface { 11 | constructor( 12 | private cardValidator: CardValidatorInterface, 13 | private cryptDataUtils: CryptDataInterface, 14 | private cardUtils: CardUtilsInterface, 15 | private cardRepository: CardRepositoryInterface 16 | ) { 17 | this.cardValidator = cardValidator; 18 | this.cardUtils = cardUtils; 19 | this.cryptDataUtils = cryptDataUtils; 20 | this.cardRepository = cardRepository; 21 | } 22 | 23 | async execute(cardId: number, password: string, CVC: string) { 24 | const card = await this.cardRepository.findById(cardId); 25 | this.cardValidator.ensureCardExists(card); 26 | this.cardValidator.ensureCardIsNotActivated(card?.password); 27 | 28 | this.cardValidator.ensureCardIsNotExpired( 29 | card.expirationDate, 30 | this.cardUtils 31 | ); 32 | 33 | this.cardValidator.ensureSecurityCodeIsCorrect( 34 | card.securityCode, 35 | CVC, 36 | this.cryptDataUtils 37 | ); 38 | 39 | const encryptedPassword = this.cryptDataUtils.hashDataBcrypt(password); 40 | 41 | await this.cardRepository.update(cardId, { password: encryptedPassword }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/services/cardServices/blockCardService.ts: -------------------------------------------------------------------------------- 1 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 2 | import { CardUtilsInterface } from "../../utils/cardUtils"; 3 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 4 | import { CardValidatorInterface } from "./cardsServicesValidators"; 5 | 6 | export interface BlockCardServiceInterface { 7 | execute: (cardId: number, password: string) => Promise; 8 | } 9 | 10 | export class BlockCardService implements BlockCardServiceInterface { 11 | constructor( 12 | private cardValidator: CardValidatorInterface, 13 | private cardUtils: CardUtilsInterface, 14 | private cryptDataUtils: CryptDataInterface, 15 | private cardRepository: CardRepositoryInterface 16 | ) { 17 | this.cardValidator = cardValidator; 18 | this.cardUtils = cardUtils; 19 | this.cryptDataUtils = cryptDataUtils; 20 | this.cardRepository = cardRepository; 21 | } 22 | 23 | async execute(cardId: number, password: string) { 24 | const card = await this.cardRepository.findById(cardId); 25 | 26 | this.cardValidator.ensureCardExists(card); 27 | this.cardValidator.ensureCardIsActivated(card?.password); 28 | this.cardValidator.ensureCardIsUnblocked(card.isBlocked); 29 | this.cardValidator.ensureCardIsNotExpired( 30 | card.expirationDate, 31 | this.cardUtils 32 | ); 33 | this.cardValidator.ensurePasswordIsCorrect( 34 | card?.password, 35 | password, 36 | this.cryptDataUtils 37 | ); 38 | 39 | await this.cardRepository.update(cardId, { isBlocked: true }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/services/cardServices/cardsServicesValidators.ts: -------------------------------------------------------------------------------- 1 | import { CardUtilsInterface } from "../../utils/cardUtils"; 2 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 3 | import { CustomError } from "../../classes/CustomError"; 4 | import { Card as ICard } from "../../interfaces/cardInterfaces"; 5 | import { Company } from "../../interfaces/companyInterfaces"; 6 | import { Business } from "../../interfaces/businessInterfaces"; 7 | import { Employee } from "../../interfaces/employeeInterfaces"; 8 | import { TransactionTypes } from "../../types/cardTypes"; 9 | 10 | export interface CardValidatorInterface { 11 | ensureCompanyExists: (company: Company) => void; 12 | ensureBusinessExists: (business: Business) => void; 13 | ensureEmployeeExists: (employee: Employee) => void; 14 | ensureEmployeeDoesNotHaveThisCardType: ( 15 | card: ICard, 16 | cardType: string 17 | ) => void; 18 | ensureCardExists: (card: ICard) => void; 19 | ensureCardIsNotActivated: (password: string | undefined) => void; 20 | ensureCardIsNotExpired: ( 21 | expirationDate: string, 22 | cardUtils: CardUtilsInterface 23 | ) => void; 24 | ensureSecurityCodeIsCorrect: ( 25 | cardSecurityCode: string, 26 | reqCVC: string, 27 | cryptDataUtils: CryptDataInterface 28 | ) => void; 29 | ensureCardIsActivated: (password: string | undefined) => void; 30 | ensureCardIsUnblocked: (isBlocked: boolean) => void; 31 | ensureCardIsNotBlocked: (isBlocked: boolean) => void; 32 | ensureCardIsBlocked: (isBlocked: boolean) => void; 33 | ensurePasswordIsCorrect: ( 34 | cardPassword: string | undefined, 35 | reqPassword: string, 36 | cryptDataUtils: CryptDataInterface 37 | ) => void; 38 | ensureBusinessTypeIsEqualToCardType: ( 39 | businessType: TransactionTypes, 40 | cardType: TransactionTypes 41 | ) => void; 42 | ensureSufficientCardBalance: (balance: number, amount: number) => void; 43 | ensureCardIsNotVirtual: (isVirtual: boolean, service: string) => void; 44 | ensureCardIsVirtual: (isVirtual: boolean) => void; 45 | } 46 | 47 | export class CardValidator implements CardValidatorInterface { 48 | ensureCompanyExists(company: Company) { 49 | if (!company) { 50 | throw new CustomError("error_not_found", "Company not found"); 51 | } 52 | } 53 | 54 | ensureBusinessExists(business: Business) { 55 | if (!business) { 56 | throw new CustomError("error_not_found", "Business not found"); 57 | } 58 | } 59 | 60 | ensureEmployeeExists(employee: Employee) { 61 | if (!employee) { 62 | throw new CustomError("error_not_found", "Employee not found"); 63 | } 64 | } 65 | 66 | ensureEmployeeDoesNotHaveThisCardType(card: ICard, cardType: string) { 67 | if (card) { 68 | throw new CustomError( 69 | "error_conflict", 70 | `The employee already has a ${cardType} card` 71 | ); 72 | } 73 | } 74 | 75 | ensureCardExists(card: ICard) { 76 | if (!card) { 77 | throw new CustomError("error_not_found", "Card not found"); 78 | } 79 | } 80 | 81 | ensureCardIsNotActivated(password: string | undefined) { 82 | if (password) { 83 | throw new CustomError( 84 | "error_bad_request", 85 | "This card is already activated" 86 | ); 87 | } 88 | } 89 | 90 | ensureCardIsNotExpired( 91 | expirationDate: string, 92 | cardUtils: CardUtilsInterface 93 | ) { 94 | if (cardUtils.setIsExpired(expirationDate)) { 95 | throw new CustomError("error_bad_request", "This card is expired"); 96 | } 97 | } 98 | 99 | ensureSecurityCodeIsCorrect( 100 | cardSecurityCode: string, 101 | reqCVC: string, 102 | cryptDataUtils: CryptDataInterface 103 | ) { 104 | if (cryptDataUtils.decryptData(cardSecurityCode) !== reqCVC) { 105 | throw new CustomError( 106 | "error_unauthorized", 107 | "Incorrect card security code" 108 | ); 109 | } 110 | } 111 | 112 | ensureCardIsActivated(password: string | undefined) { 113 | if (!password) { 114 | throw new CustomError("error_bad_request", "This card is not activated"); 115 | } 116 | } 117 | 118 | ensureCardIsUnblocked(isBlocked: boolean) { 119 | if (isBlocked) { 120 | throw new CustomError( 121 | "error_bad_request", 122 | "This card is already blocked" 123 | ); 124 | } 125 | } 126 | 127 | ensureCardIsNotBlocked(isBlocked: boolean) { 128 | if (isBlocked) { 129 | throw new CustomError("error_bad_request", "This card is blocked"); 130 | } 131 | } 132 | 133 | ensureCardIsBlocked(isBlocked: boolean) { 134 | if (!isBlocked) { 135 | throw new CustomError("error_bad_request", "This card is not blocked"); 136 | } 137 | } 138 | 139 | ensurePasswordIsCorrect( 140 | cardPassword: string | undefined, 141 | reqPassword: string, 142 | cryptDataUtils: CryptDataInterface 143 | ) { 144 | if ( 145 | cardPassword && 146 | !cryptDataUtils.validateEncryptedData(reqPassword, cardPassword) 147 | ) { 148 | throw new CustomError("error_unauthorized", "Incorrect password"); 149 | } 150 | } 151 | 152 | ensureBusinessTypeIsEqualToCardType( 153 | businessType: TransactionTypes, 154 | cardType: TransactionTypes 155 | ) { 156 | if (businessType !== cardType) { 157 | throw new CustomError( 158 | "error_bad_request", 159 | "The business type does not match the card type" 160 | ); 161 | } 162 | } 163 | 164 | ensureSufficientCardBalance(balance: number, amount: number) { 165 | if (balance < amount) { 166 | throw new CustomError("error_bad_request", "Insufficient card balance"); 167 | } 168 | } 169 | 170 | ensureCardIsNotVirtual(isVirtual: boolean, service: string) { 171 | if (isVirtual) { 172 | throw new CustomError( 173 | "error_bad_request", 174 | `The ${service} service is not available for virtual cards` 175 | ); 176 | } 177 | } 178 | 179 | ensureCardIsVirtual(isVirtual: boolean) { 180 | if (!isVirtual) { 181 | throw new CustomError("error_bad_request", "This is not a virtual card"); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/services/cardServices/createCardService.ts: -------------------------------------------------------------------------------- 1 | import { Card } from "../../classes/Card"; 2 | import { ResponseCard } from "../../interfaces/cardInterfaces"; 3 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 4 | import { TransactionTypes } from "../../types/cardTypes"; 5 | import { CardValidatorInterface } from "./cardsServicesValidators"; 6 | import { CompanyRepositoryInterface } from "../../repositories/companyRepository"; 7 | import { EmployeeRepositoryInterface } from "../../repositories/employeeRepository"; 8 | import { CardUtilsInterface } from "../../utils/cardUtils"; 9 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 10 | 11 | export interface CreateCardServiceInterface { 12 | create: ( 13 | API_KEY: string, 14 | employeeId: number, 15 | cardType: TransactionTypes 16 | ) => Promise; 17 | } 18 | 19 | export class CreateCardService implements CreateCardServiceInterface { 20 | constructor( 21 | private cardValidator: CardValidatorInterface, 22 | private cryptDataUtils: CryptDataInterface, 23 | private cardUtils: CardUtilsInterface, 24 | private cardRepository: CardRepositoryInterface, 25 | private companyRepository: CompanyRepositoryInterface, 26 | private employeeRepository: EmployeeRepositoryInterface 27 | ) { 28 | this.cardValidator = cardValidator; 29 | this.cardUtils = cardUtils; 30 | this.cryptDataUtils = cryptDataUtils; 31 | this.cardRepository = cardRepository; 32 | this.companyRepository = companyRepository; 33 | this.employeeRepository = employeeRepository; 34 | } 35 | 36 | async create( 37 | API_KEY: string, 38 | employeeId: number, 39 | cardType: TransactionTypes 40 | ): Promise { 41 | const company = await this.companyRepository.findByApiKey(API_KEY); 42 | this.cardValidator.ensureCompanyExists(company); 43 | 44 | const employee = await this.employeeRepository.findById(employeeId); 45 | this.cardValidator.ensureEmployeeExists(employee); 46 | 47 | const existingCard = await this.cardRepository.findByTypeAndEmployeeId( 48 | cardType, 49 | employeeId 50 | ); 51 | this.cardValidator.ensureEmployeeDoesNotHaveThisCardType( 52 | existingCard, 53 | cardType 54 | ); 55 | const cardholderName = this.cardUtils.setCardholderName(employee.fullName); 56 | 57 | const card = new Card( 58 | employeeId, 59 | cardType, 60 | cardholderName, 61 | this.cryptDataUtils 62 | ); 63 | 64 | const resultCardId = await this.cardRepository.insert(card); 65 | 66 | return { 67 | cardId: resultCardId, 68 | number: card.number, 69 | cardholderName: card.cardholderName, 70 | securityCode: this.cryptDataUtils.decryptData(card.securityCode), 71 | expirationDate: card.expirationDate, 72 | type: card.type, 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/services/cardServices/getCardBalanceService.ts: -------------------------------------------------------------------------------- 1 | import { FormattedRecharge } from "../../interfaces/rechargeInterfaces"; 2 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 3 | import { PaymentRepositoryInterface } from "../../repositories/paymentRepository"; 4 | import { RechargeRepositoryInterface } from "../../repositories/rechargeRepository"; 5 | import { CardValidatorInterface } from "./cardsServicesValidators"; 6 | import { CardUtilsInterface } from "../../utils/cardUtils"; 7 | import { FormattedPayment } from "../../interfaces/paymentInterfaces"; 8 | 9 | export interface Balance { 10 | balance: number; 11 | transactions: FormattedPayment[]; 12 | recharges: FormattedRecharge[]; 13 | } 14 | 15 | export interface GetCardBalanceServiceInterface { 16 | execute: (cardId: number) => Promise; 17 | } 18 | 19 | export class GetCardBalanceService implements GetCardBalanceServiceInterface { 20 | constructor( 21 | private cardValidator: CardValidatorInterface, 22 | private cardUtils: CardUtilsInterface, 23 | private cardRepository: CardRepositoryInterface, 24 | private rechargeRepository: RechargeRepositoryInterface, 25 | private paymentRepository: PaymentRepositoryInterface 26 | ) { 27 | this.cardValidator = cardValidator; 28 | this.cardUtils = cardUtils; 29 | this.cardRepository = cardRepository; 30 | this.rechargeRepository = rechargeRepository; 31 | this.paymentRepository = paymentRepository; 32 | } 33 | 34 | async execute(cardId: number): Promise { 35 | const card = await this.cardRepository.findById(cardId); 36 | this.cardValidator.ensureCardExists(card); 37 | 38 | const id = card.originalCardId! ? card.originalCardId : cardId; 39 | 40 | const recharges = await this.rechargeRepository.findByCardId(id); 41 | const payments = await this.paymentRepository.findByCardId(id); 42 | const formattedRecharges = this.cardUtils.formatRecharges(recharges); 43 | const formattedTransactions = this.cardUtils.formatPayments(payments); 44 | const balance = this.cardUtils.calcBalance(recharges, payments); 45 | 46 | return { 47 | balance, 48 | transactions: formattedTransactions, 49 | recharges: formattedRecharges, 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/services/cardServices/index.ts: -------------------------------------------------------------------------------- 1 | import { CardRepository } from "../../repositories/cardRepository"; 2 | import { CompanyRepository } from "../../repositories/companyRepository"; 3 | import { EmployeeRepository } from "../../repositories/employeeRepository"; 4 | import { PaymentRepository } from "../../repositories/paymentRepository"; 5 | import { RechargeRepository } from "../../repositories/rechargeRepository"; 6 | import { CardUtils } from "../../utils/cardUtils"; 7 | import { CryptDataUtils } from "../../utils/cryptDataUtils"; 8 | import { ActivateCardService } from "./activateCardService"; 9 | import { BlockCardService } from "./blockCardService"; 10 | import { CardValidator } from "./cardsServicesValidators"; 11 | import { CreateCardService } from "./createCardService"; 12 | import { GetCardBalanceService } from "./getCardBalanceService"; 13 | import { RechargeCardService } from "./rechargeCardService"; 14 | import { UnblockCardService } from "./unblockCardService"; 15 | 16 | const cardValidator = new CardValidator(); 17 | 18 | const cryptDataUtils = new CryptDataUtils(process.env.CRYPTR_SECRET_KEY); 19 | const cardUtils = new CardUtils(); 20 | 21 | const cardRepository = new CardRepository(); 22 | const companyRepository = new CompanyRepository(); 23 | const employeeRepository = new EmployeeRepository(); 24 | const rechargeRepository = new RechargeRepository(); 25 | const paymentRepository = new PaymentRepository(); 26 | 27 | export const createCardService = new CreateCardService( 28 | cardValidator, 29 | cryptDataUtils, 30 | cardUtils, 31 | cardRepository, 32 | companyRepository, 33 | employeeRepository 34 | ); 35 | 36 | export const activateCardService = new ActivateCardService( 37 | cardValidator, 38 | cryptDataUtils, 39 | cardUtils, 40 | cardRepository 41 | ); 42 | 43 | export const blockCardService = new BlockCardService( 44 | cardValidator, 45 | cardUtils, 46 | cryptDataUtils, 47 | cardRepository 48 | ); 49 | 50 | export const unblockCardService = new UnblockCardService( 51 | cardValidator, 52 | cardUtils, 53 | cryptDataUtils, 54 | cardRepository 55 | ); 56 | 57 | export const getBalanceService = new GetCardBalanceService( 58 | cardValidator, 59 | cardUtils, 60 | cardRepository, 61 | rechargeRepository, 62 | paymentRepository 63 | ); 64 | 65 | export const rechargeCardService = new RechargeCardService( 66 | cardValidator, 67 | cardUtils, 68 | cardRepository, 69 | rechargeRepository, 70 | companyRepository 71 | ); 72 | -------------------------------------------------------------------------------- /src/services/cardServices/rechargeCardService.ts: -------------------------------------------------------------------------------- 1 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 2 | import { CompanyRepositoryInterface } from "../../repositories/companyRepository"; 3 | import { RechargeRepositoryInterface } from "../../repositories/rechargeRepository"; 4 | import { CardUtilsInterface } from "../../utils/cardUtils"; 5 | import { CardValidatorInterface } from "./cardsServicesValidators"; 6 | 7 | export interface RechargeCardServiceInterface { 8 | execute: (cardId: number, API_KEY: string, amount: number) => Promise; 9 | } 10 | 11 | export class RechargeCardService implements RechargeCardServiceInterface { 12 | constructor( 13 | private cardValidator: CardValidatorInterface, 14 | private cardUtils: CardUtilsInterface, 15 | private cardRepository: CardRepositoryInterface, 16 | private rechargeRepository: RechargeRepositoryInterface, 17 | private companyRepository: CompanyRepositoryInterface 18 | ) { 19 | this.cardRepository = cardRepository; 20 | this.cardUtils = cardUtils; 21 | this.cardValidator = cardValidator; 22 | this.rechargeRepository = rechargeRepository; 23 | this.companyRepository = companyRepository; 24 | } 25 | 26 | async execute(cardId: number, API_KEY: string, amount: number) { 27 | const company = await this.companyRepository.findByApiKey(API_KEY); 28 | this.cardValidator.ensureCompanyExists(company); 29 | 30 | const card = await this.cardRepository.findById(cardId); 31 | this.cardValidator.ensureCardExists(card); 32 | this.cardValidator.ensureCardIsNotVirtual(card.isVirtual, "recharge"); 33 | this.cardValidator.ensureCardIsActivated(card?.password); 34 | this.cardValidator.ensureCardIsNotExpired( 35 | card.expirationDate, 36 | this.cardUtils 37 | ); 38 | 39 | await this.rechargeRepository.insert({ cardId, amount }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/services/cardServices/unblockCardService.ts: -------------------------------------------------------------------------------- 1 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 2 | import { CardUtilsInterface } from "../../utils/cardUtils"; 3 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 4 | import { CardValidatorInterface } from "./cardsServicesValidators"; 5 | 6 | export interface UnblockCardServiceInterface { 7 | execute: (cardId: number, password: string) => Promise; 8 | } 9 | 10 | export class UnblockCardService implements UnblockCardServiceInterface { 11 | constructor( 12 | private cardValidator: CardValidatorInterface, 13 | private cardUtils: CardUtilsInterface, 14 | private cryptDataUtils: CryptDataInterface, 15 | private cardRepository: CardRepositoryInterface 16 | ) { 17 | this.cardValidator = cardValidator; 18 | this.cardUtils = cardUtils; 19 | this.cryptDataUtils = cryptDataUtils; 20 | this.cardRepository = cardRepository; 21 | } 22 | 23 | async execute(cardId: number, password: string) { 24 | const card = await this.cardRepository.findById(cardId); 25 | this.cardValidator.ensureCardExists(card); 26 | this.cardValidator.ensureCardIsActivated(card?.password); 27 | this.cardValidator.ensureCardIsBlocked(card.isBlocked); 28 | 29 | this.cardValidator.ensureCardIsNotExpired( 30 | card.expirationDate, 31 | this.cardUtils 32 | ); 33 | 34 | this.cardValidator.ensurePasswordIsCorrect( 35 | card?.password, 36 | password, 37 | this.cryptDataUtils 38 | ); 39 | 40 | await this.cardRepository.update(cardId, { isBlocked: false }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/services/paymentServices/index.ts: -------------------------------------------------------------------------------- 1 | import { BusinessRepository } from "../../repositories/businessRepository"; 2 | import { CardRepository } from "../../repositories/cardRepository"; 3 | import { PaymentRepository } from "../../repositories/paymentRepository"; 4 | import { RechargeRepository } from "../../repositories/rechargeRepository"; 5 | import { CardUtils } from "../../utils/cardUtils"; 6 | import { CryptDataUtils } from "../../utils/cryptDataUtils"; 7 | import { CardValidator } from "../cardServices/cardsServicesValidators"; 8 | import { OnlinePaymentService } from "./onlinePaymentService"; 9 | import { POSPaymentService } from "./posPaymentService"; 10 | 11 | const cardValidator = new CardValidator(); 12 | 13 | const cardUtils = new CardUtils(); 14 | const cryptDataUtils = new CryptDataUtils(process.env.CRYPTR_SECRET_KEY); 15 | 16 | const cardRepository = new CardRepository(); 17 | const rechargeRepository = new RechargeRepository(); 18 | const businessRepository = new BusinessRepository(); 19 | const paymentRepository = new PaymentRepository(); 20 | 21 | export const onlinePaymentService = new OnlinePaymentService( 22 | cardValidator, 23 | cryptDataUtils, 24 | cardUtils, 25 | cardRepository, 26 | businessRepository, 27 | rechargeRepository, 28 | paymentRepository 29 | ); 30 | 31 | export const posPaymentService = new POSPaymentService( 32 | cardValidator, 33 | cryptDataUtils, 34 | cardUtils, 35 | cardRepository, 36 | businessRepository, 37 | rechargeRepository, 38 | paymentRepository 39 | ); 40 | -------------------------------------------------------------------------------- /src/services/paymentServices/onlinePaymentService.ts: -------------------------------------------------------------------------------- 1 | import { OnlinePaymentData } from "../../interfaces/paymentInterfaces"; 2 | import { BusinessRepositoryInterface } from "../../repositories/businessRepository"; 3 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 4 | import { PaymentRepositoryInterface } from "../../repositories/paymentRepository"; 5 | import { RechargeRepositoryInterface } from "../../repositories/rechargeRepository"; 6 | import { CardValidatorInterface } from "../cardServices/cardsServicesValidators"; 7 | import { CardUtilsInterface } from "../../utils/cardUtils"; 8 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 9 | 10 | export interface OnlinePayment { 11 | execute: (paymentData: OnlinePaymentData) => Promise; 12 | } 13 | 14 | export class OnlinePaymentService implements OnlinePayment { 15 | constructor( 16 | private cardValidator: CardValidatorInterface, 17 | private cryptDataUtils: CryptDataInterface, 18 | private cardUtils: CardUtilsInterface, 19 | private cardRepository: CardRepositoryInterface, 20 | private businessRepository: BusinessRepositoryInterface, 21 | private rechargeRepository: RechargeRepositoryInterface, 22 | private paymentRepository: PaymentRepositoryInterface 23 | ) { 24 | this.cardValidator = cardValidator; 25 | this.cardUtils = cardUtils; 26 | this.cryptDataUtils = cryptDataUtils; 27 | this.cardRepository = cardRepository; 28 | this.businessRepository = businessRepository; 29 | this.rechargeRepository = rechargeRepository; 30 | this.paymentRepository = paymentRepository; 31 | } 32 | 33 | async execute({ 34 | cardInfo, 35 | businessId, 36 | amount, 37 | }: OnlinePaymentData): Promise { 38 | const card = await this.cardRepository.findByCardDetails( 39 | cardInfo.cardNumber, 40 | cardInfo.cardholderName, 41 | cardInfo.expirationDate 42 | ); 43 | this.cardValidator.ensureCardExists(card); 44 | this.cardValidator.ensureCardIsActivated(card?.password); 45 | this.cardValidator.ensureCardIsNotBlocked(card.isBlocked); 46 | 47 | this.cardValidator.ensureCardIsNotExpired( 48 | card.expirationDate, 49 | this.cardUtils 50 | ); 51 | 52 | this.cardValidator.ensureSecurityCodeIsCorrect( 53 | card.securityCode, 54 | cardInfo.CVC, 55 | this.cryptDataUtils 56 | ); 57 | 58 | const business = await this.businessRepository.findById(businessId); 59 | this.cardValidator.ensureBusinessExists(business); 60 | this.cardValidator.ensureBusinessTypeIsEqualToCardType( 61 | business.type, 62 | card.type 63 | ); 64 | 65 | const cardId = 66 | card.isVirtual && card.originalCardId ? card.originalCardId : card.id; 67 | 68 | const recharges = await this.rechargeRepository.findByCardId(cardId); 69 | const transactions = await this.paymentRepository.findByCardId(cardId); 70 | const balance = this.cardUtils.calcBalance(recharges, transactions); 71 | this.cardValidator.ensureSufficientCardBalance(balance, amount); 72 | 73 | await this.paymentRepository.insert({ cardId, amount, businessId }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/services/paymentServices/posPaymentService.ts: -------------------------------------------------------------------------------- 1 | import { BusinessRepositoryInterface } from "../../repositories/businessRepository"; 2 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 3 | import { PaymentRepositoryInterface } from "../../repositories/paymentRepository"; 4 | import { RechargeRepositoryInterface } from "../../repositories/rechargeRepository"; 5 | import { CardValidatorInterface } from "../cardServices/cardsServicesValidators"; 6 | import { CardUtilsInterface } from "../../utils/cardUtils"; 7 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 8 | 9 | export interface POSPaymentInterface { 10 | execute: ( 11 | cardId: number, 12 | password: string, 13 | businessId: number, 14 | amount: number 15 | ) => Promise; 16 | } 17 | 18 | export class POSPaymentService implements POSPaymentInterface { 19 | constructor( 20 | private cardValidator: CardValidatorInterface, 21 | private cryptDataUtils: CryptDataInterface, 22 | private cardUtils: CardUtilsInterface, 23 | private cardRepository: CardRepositoryInterface, 24 | private businessRepository: BusinessRepositoryInterface, 25 | private rechargeRepository: RechargeRepositoryInterface, 26 | private paymentRepository: PaymentRepositoryInterface 27 | ) { 28 | this.cardValidator = cardValidator; 29 | this.cardUtils = cardUtils; 30 | this.cryptDataUtils = cryptDataUtils; 31 | this.cardRepository = cardRepository; 32 | this.businessRepository = businessRepository; 33 | this.rechargeRepository = rechargeRepository; 34 | this.paymentRepository = paymentRepository; 35 | } 36 | 37 | async execute( 38 | cardId: number, 39 | password: string, 40 | businessId: number, 41 | amount: number 42 | ): Promise { 43 | const card = await this.cardRepository.findById(cardId); 44 | this.cardValidator.ensureCardExists(card); 45 | this.cardValidator.ensureCardIsNotVirtual(card.isVirtual, "POS shopping"); 46 | this.cardValidator.ensureCardIsActivated(card?.password); 47 | this.cardValidator.ensureCardIsNotBlocked(card.isBlocked); 48 | 49 | this.cardValidator.ensureCardIsNotExpired( 50 | card.expirationDate, 51 | this.cardUtils 52 | ); 53 | 54 | this.cardValidator.ensurePasswordIsCorrect( 55 | card?.password, 56 | password, 57 | this.cryptDataUtils 58 | ); 59 | 60 | const business = await this.businessRepository.findById(businessId); 61 | this.cardValidator.ensureBusinessExists(business); 62 | this.cardValidator.ensureBusinessTypeIsEqualToCardType( 63 | business.type, 64 | card.type 65 | ); 66 | 67 | const recharges = await this.rechargeRepository.findByCardId(cardId); 68 | const transactions = await this.paymentRepository.findByCardId(cardId); 69 | const balance = this.cardUtils.calcBalance(recharges, transactions); 70 | this.cardValidator.ensureSufficientCardBalance(balance, amount); 71 | 72 | await this.paymentRepository.insert({ cardId, amount, businessId }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/services/virtualCardServices/createVirtualCardService.ts: -------------------------------------------------------------------------------- 1 | import { VirtualCard } from "../../classes/VirtualCard"; 2 | import { ResponseCard } from "../../interfaces/cardInterfaces"; 3 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 4 | import { CardValidatorInterface } from "../cardServices/cardsServicesValidators"; 5 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 6 | 7 | export interface CreateVirtualCard { 8 | create: (originalCardId: number, password: string) => Promise; 9 | } 10 | 11 | export class CreateVirtualCardService implements CreateVirtualCard { 12 | constructor( 13 | private cardValidator: CardValidatorInterface, 14 | private cardRepository: CardRepositoryInterface, 15 | private cryptDataUtils: CryptDataInterface 16 | ) { 17 | this.cardValidator = cardValidator; 18 | this.cardRepository = cardRepository; 19 | this.cryptDataUtils = cryptDataUtils; 20 | } 21 | 22 | async create( 23 | originalCardId: number, 24 | password: string 25 | ): Promise { 26 | const card = await this.cardRepository.findById(originalCardId); 27 | this.cardValidator.ensureCardExists(card); 28 | this.cardValidator.ensureCardIsActivated(card?.password); 29 | this.cardValidator.ensureCardIsNotVirtual( 30 | card.isVirtual, 31 | "virtual card creation" 32 | ); 33 | 34 | this.cardValidator.ensurePasswordIsCorrect( 35 | card?.password, 36 | password, 37 | this.cryptDataUtils 38 | ); 39 | 40 | const virtualCard = new VirtualCard( 41 | card.employeeId, 42 | card.type, 43 | card.cardholderName, 44 | this.cryptDataUtils, 45 | originalCardId, 46 | card?.password 47 | ); 48 | 49 | const resultCardId = await this.cardRepository.insert(virtualCard); 50 | 51 | return { 52 | cardId: resultCardId, 53 | number: virtualCard.number, 54 | cardholderName: virtualCard.cardholderName, 55 | securityCode: this.cryptDataUtils.decryptData(virtualCard.securityCode), 56 | expirationDate: virtualCard.expirationDate, 57 | type: virtualCard.type, 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/services/virtualCardServices/deleteVirtualCardService.ts: -------------------------------------------------------------------------------- 1 | import { CardRepositoryInterface } from "../../repositories/cardRepository"; 2 | import { CryptDataInterface } from "../../utils/cryptDataUtils"; 3 | import { CardValidatorInterface } from "../cardServices/cardsServicesValidators"; 4 | 5 | export interface DeleteVirtualCard { 6 | delete: (virtualCardId: number, password: string) => Promise; 7 | } 8 | 9 | export class DeleteVirtualCardService implements DeleteVirtualCard { 10 | constructor( 11 | private cardValidator: CardValidatorInterface, 12 | private cardRepository: CardRepositoryInterface, 13 | private cryptDataUtils: CryptDataInterface 14 | ) { 15 | this.cardValidator = cardValidator; 16 | this.cardRepository = cardRepository; 17 | this.cryptDataUtils = cryptDataUtils; 18 | } 19 | 20 | async delete(virtualCardId: number, password: string): Promise { 21 | const card = await this.cardRepository.findById(virtualCardId); 22 | this.cardValidator.ensureCardExists(card); 23 | this.cardValidator.ensureCardIsVirtual(card.isVirtual); 24 | 25 | this.cardValidator.ensurePasswordIsCorrect( 26 | card?.password, 27 | password, 28 | this.cryptDataUtils 29 | ); 30 | 31 | await this.cardRepository.remove(virtualCardId); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/services/virtualCardServices/index.ts: -------------------------------------------------------------------------------- 1 | import { CardRepository } from "../../repositories/cardRepository"; 2 | import { CryptDataUtils } from "../../utils/cryptDataUtils"; 3 | import { CardValidator } from "../cardServices/cardsServicesValidators"; 4 | import { CreateVirtualCardService } from "./createVirtualCardService"; 5 | import { DeleteVirtualCardService } from "./deleteVirtualCardService"; 6 | 7 | const cardValidator = new CardValidator(); 8 | const cardRepository = new CardRepository(); 9 | const cryptDataUtils = new CryptDataUtils(process.env.CRYPTR_SECRET_KEY); 10 | 11 | export const createVirtualCardService = new CreateVirtualCardService( 12 | cardValidator, 13 | cardRepository, 14 | cryptDataUtils 15 | ); 16 | 17 | export const deleteVirtualCardService = new DeleteVirtualCardService( 18 | cardValidator, 19 | cardRepository, 20 | cryptDataUtils 21 | ); 22 | -------------------------------------------------------------------------------- /src/types/cardTypes.ts: -------------------------------------------------------------------------------- 1 | export type TransactionTypes = 2 | | "groceries" 3 | | "restaurant" 4 | | "transport" 5 | | "education" 6 | | "health"; 7 | 8 | export type CardInfo = { 9 | cardNumber: string; 10 | cardholderName: string; 11 | expirationDate: string; 12 | CVC: string; 13 | }; 14 | -------------------------------------------------------------------------------- /src/types/enviroment.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | namespace NodeJS { 5 | interface ProcessEnv { 6 | PORT: number; 7 | DATABASE_URL: string; 8 | CRYPTR_SECRET_KEY: string; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/cardUtils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable function-paren-newline */ 2 | /* eslint-disable implicit-arrow-linebreak */ 3 | import dayjs from "dayjs"; 4 | import { FormattedPayment } from "../interfaces/paymentInterfaces"; 5 | import { FormattedRecharge, Recharge } from "../interfaces/rechargeInterfaces"; 6 | import { PaymentWithBusinessName } from "../repositories/paymentRepository"; 7 | 8 | export interface CardUtilsInterface { 9 | setCardholderName: (employeeName: string) => string; 10 | setIsExpired: (expirationDate: string) => boolean; 11 | calcBalance: ( 12 | recharges: Recharge[], 13 | transactions: PaymentWithBusinessName[] 14 | ) => number; 15 | formatPayments: ( 16 | transactions: PaymentWithBusinessName[] 17 | ) => FormattedPayment[]; 18 | formatRecharges: (transactions: Recharge[]) => FormattedRecharge[]; 19 | } 20 | 21 | export class CardUtils implements CardUtilsInterface { 22 | setCardholderName(employeeName: string): string { 23 | const employeeNameArray: string[] = employeeName.split(" "); 24 | const cardholderNameArray: string[] = []; 25 | 26 | employeeNameArray.forEach((value, index, array) => { 27 | if (index === 0 || index === array.length - 1) { 28 | return cardholderNameArray.push(value.toUpperCase()); 29 | } 30 | if (value.length > 3) { 31 | return cardholderNameArray.push(value.charAt(0).toUpperCase()); 32 | } 33 | return null; 34 | }); 35 | 36 | return cardholderNameArray.join(" "); 37 | } 38 | 39 | setIsExpired(expirationDate: string): boolean { 40 | const formattedExpirationDate = expirationDate.replace("/", "-01-"); 41 | 42 | if (dayjs().isAfter(dayjs(formattedExpirationDate))) { 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | calcBalance( 49 | recharges: Recharge[], 50 | transactions: PaymentWithBusinessName[] 51 | ): number { 52 | const balance = 53 | recharges.reduce((prev, curr) => prev + curr.amount, 0) - 54 | transactions.reduce((prev, curr) => prev + curr.amount, 0); 55 | 56 | return balance; 57 | } 58 | 59 | formatPayments(transactions: PaymentWithBusinessName[]): FormattedPayment[] { 60 | const formattedTransactions = transactions.map((transaction) => ({ 61 | ...transaction, 62 | timestamp: dayjs(transaction.timestamp).format("DD/MM/YYYY"), 63 | })); 64 | 65 | return formattedTransactions; 66 | } 67 | 68 | formatRecharges(transactions: Recharge[]): FormattedRecharge[] { 69 | const formattedTransactions = transactions.map((transaction) => ({ 70 | ...transaction, 71 | timestamp: dayjs(transaction.timestamp).format("DD/MM/YYYY"), 72 | })); 73 | 74 | return formattedTransactions; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/cryptDataUtils.ts: -------------------------------------------------------------------------------- 1 | import Cryptr from "cryptr"; 2 | import bcrypt from "bcrypt"; 3 | 4 | export interface CryptDataInterface { 5 | encryptData: (data: string) => string; 6 | decryptData: (data: string) => string; 7 | hashDataBcrypt: (data: string) => string; 8 | validateEncryptedData: (data: string, hashData: string) => boolean; 9 | } 10 | 11 | export class CryptDataUtils implements CryptDataInterface { 12 | private cryptr: Cryptr; 13 | 14 | constructor(private secret: string) { 15 | this.secret = secret; 16 | this.cryptr = new Cryptr(this.secret); 17 | } 18 | 19 | encryptData(data: string): string { 20 | return this.cryptr.encrypt(data); 21 | } 22 | 23 | decryptData(data: string): string { 24 | return this.cryptr.decrypt(data); 25 | } 26 | 27 | hashDataBcrypt(data: string): string { 28 | return bcrypt.hashSync(data, 10); 29 | } 30 | 31 | validateEncryptedData(data: string, hashData: string): boolean { 32 | return bcrypt.compareSync(data, hashData); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/sqlUtils.ts: -------------------------------------------------------------------------------- 1 | import { NewCard as ICard } from "../interfaces/cardInterfaces"; 2 | 3 | type UpdatedCard = Partial; 4 | 5 | type UpdateObject = { 6 | object: UpdatedCard; 7 | offset: number; 8 | }; 9 | 10 | export function mapObjectToUpdateQuery({ object, offset = 1 }: UpdateObject) { 11 | const objectColumns = Object.keys(object) 12 | .map((key, index) => `"${key}"=$${index + offset}`) 13 | .join(","); 14 | const objectValues = Object.values(object); 15 | 16 | return { objectColumns, objectValues }; 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./**/*.ts", 5 | "./**/*.js", 6 | "./.*.js" 7 | ] 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 14 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 15 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 16 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 17 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 18 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 19 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 20 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 21 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 22 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 23 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 24 | /* Modules */ 25 | "module": "commonjs", /* Specify what module code is generated. */ 26 | "rootDir": "./src", /* Specify the root folder within your source files. */ 27 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 28 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 29 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 30 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 31 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 32 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 33 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 34 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 35 | // "resolveJsonModule": true, /* Enable importing .json files. */ 36 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 37 | /* JavaScript Support */ 38 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 39 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 40 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 41 | /* Emit */ 42 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 43 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 44 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 45 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 46 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 47 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 48 | // "removeComments": true, /* Disable emitting comments. */ 49 | // "noEmit": true, /* Disable emitting files from a compilation. */ 50 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 51 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 52 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 53 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 56 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 57 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 58 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 59 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 60 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 61 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 62 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 63 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 64 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 65 | /* Interop Constraints */ 66 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 67 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 68 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 69 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 70 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 71 | /* Type Checking */ 72 | "strict": true, /* Enable all strict type-checking options. */ 73 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 74 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 75 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 76 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 77 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 78 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 79 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 80 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 81 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 82 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 83 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 84 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 85 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 86 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 87 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 88 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 89 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 90 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 91 | /* Completeness */ 92 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 93 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 94 | }, 95 | "include": [ 96 | "src" 97 | ], 98 | "exclude": [ 99 | "node_modules", 100 | "dist" 101 | ] 102 | } -------------------------------------------------------------------------------- /valex_insomnia.json: -------------------------------------------------------------------------------- 1 | {"_type":"export","__export_format":4,"__export_date":"2022-09-06T00:34:41.642Z","__export_source":"insomnia.desktop.app:v2022.4.0","resources":[{"_id":"req_d1a8eb83b5e04fd18a9c94c7e395c450","parentId":"fld_da8e4d04b45e49529cfc0c8d6b6bc565","modified":1662416449293,"created":1662212368402,"url":"{{ _.URL }}/{{ _.resource }}/{{ _.typeOfCard }}/delete","name":"Delete Virtual Card","description":"","method":"DELETE","body":{"mimeType":"application/json","text":"{\n\t\"virtualCardId\": 3,\n\t\"password\": \"1234\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_a81ec841f05c43f18b6098b0bec99cb7"}],"authentication":{},"metaSortKey":-1662212368402,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_da8e4d04b45e49529cfc0c8d6b6bc565","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1662165683393,"created":1662165575350,"name":"Virtual Cards","description":"","environment":{"resource":"cards","typeOfCard":"virtual"},"environmentPropertyOrder":{"&":["resource","typeOfCard"]},"metaSortKey":-1662165575350,"_type":"request_group"},{"_id":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","parentId":null,"modified":1661871760788,"created":1661871760788,"name":"Valex","description":"","scope":"collection","_type":"workspace"},{"_id":"req_dd0e525753764148b4710836c7837e2f","parentId":"fld_da8e4d04b45e49529cfc0c8d6b6bc565","modified":1662404686548,"created":1662165577876,"url":"{{ _.URL }}/{{ _.resource }}/{{ _.typeOfCard }}/create","name":"Create Virtual Card","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"originalCardId\": 2,\n\t\"password\": \"1234\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_50020a4d320c4315ac4b89cb8bfd644f"}],"authentication":{},"metaSortKey":-1662165577876,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_9e54ec77831b4d049315baf60bd42406","parentId":"fld_1093214e10a74384b0174612dccc6e9e","modified":1662404708263,"created":1662126285040,"url":"{{ _.URL }}/{{ _.resource }}/online","name":"Payment Online","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"cardInfo\": {\n\t\t\"cardNumber\": \"5172-8801-1965-1681\",\n\t\t\"cardholderName\": \"FULANO R SILVA\",\n\t\t\"expirationDate\": \"09/27\",\n\t\t\"CVC\": \"766\"\n\t},\n\t\"businessId\": 2,\n\t\"amount\": 12\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_7c756be2aa364668b0c3c6ec65b07bc4"}],"authentication":{},"metaSortKey":-1662126285040,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_1093214e10a74384b0174612dccc6e9e","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1662125597158,"created":1662125570353,"name":"Payments","description":"","environment":{"resource":"payments"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1661876914920,"_type":"request_group"},{"_id":"req_643af48a31844f7aabc385210beba458","parentId":"fld_1093214e10a74384b0174612dccc6e9e","modified":1662404649959,"created":1662125575499,"url":"{{ _.URL }}/{{ _.resource }}/pos","name":"Payment POS","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"cardId\": 3,\n\t\"password\": \"1234\",\n\t\"businessId\": 2,\n\t\"amount\": 1\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_c2fb4f729cae46bbb2953b982c372b65"}],"authentication":{},"metaSortKey":-1662125575499,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_50f1c372c0ec4a0480140f311e6d6aef","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662404666150,"created":1661970799200,"url":"{{ _.URL }}/{{ _.resource }}/3/balance","name":"Get card balance","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1661970799200,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_56369090f3fc45dab4ce15a2f6041791","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1661876944359,"created":1661876914870,"name":"Cards","description":"","environment":{"resource":"cards"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1661876914870,"_type":"request_group"},{"_id":"req_c259adb664e8430d8504ab62926ece37","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662404658736,"created":1661965651645,"url":"{{ _.URL }}/cards/3/recharge","name":"Recharge Card","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"amount\": 100\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_87c6cf15af964d009985233e3df99544"},{"id":"pair_ed6313dd458641eb8a8f5342431904d4","name":"x-api-key","value":"zadKLNx.DzvOVjQH01TumGl2urPjPQSxUbf67vs0","description":""}],"authentication":{},"metaSortKey":-1661965651645,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_383fee5cfc5a4bd5bb50f0c6d351d743","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662404417387,"created":1661963737901,"url":"{{ _.URL }}/{{ _.resource }}/2/unblock","name":"Unblock Card","description":"","method":"PATCH","body":{"mimeType":"application/json","text":"{\n\t\"password\": \"1234\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_d32f9fb6c8cb44a4b8bec9a1f6a5bc92"}],"authentication":{},"metaSortKey":-1661963737901,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_48a24592b41141d4ad977a80e702c717","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662416453855,"created":1661957506493,"url":"{{ _.URL }}/{{ _.resource }}/2/block","name":"Block Card","description":"","method":"PATCH","body":{"mimeType":"application/json","text":"{\n\t\"password\": \"1234\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_9ca67de79182458a952ca1bc6d64d198"}],"authentication":{},"metaSortKey":-1661957506493,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_486f3b0a57364ffaa808fca9e2442e11","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662416455016,"created":1661951174597,"url":"{{ _.URL }}/{{ _.resource }}/2/activate","name":"Activate Card","description":"","method":"PATCH","body":{"mimeType":"application/json","text":"{\n\t\"password\": \"1234\",\n\t\"CVC\": \"483\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_665846e038914a92978da2c25af41f58"}],"authentication":{},"metaSortKey":-1661951174597,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0f751307b45841d7ab4aa76cc8a52ff8","parentId":"fld_56369090f3fc45dab4ce15a2f6041791","modified":1662424452859,"created":1661876917208,"url":"{{ _.URL }}/{{ _.resource }}/create","name":"Create Card","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"cardType\": \"groceries\",\n\t\"employeeId\": \"1\"\n}"},"parameters":[],"headers":[{"id":"pair_054a8a9d2a974b4e95f1e5510648a71c","name":"x-api-key","value":"zadKLNx.DzvOVjQH01TumGl2urPjPQSxUbf67vs0","description":""},{"name":"Content-Type","value":"application/json","id":"pair_70ec17fa13044be8954a4f4f1ffe5dd4"}],"authentication":{},"metaSortKey":-1661876917208,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_d4e841a130d42ca6244f63bff76280dd4cac4c03","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1661871766993,"created":1661871760800,"name":"Base Environment","data":{},"dataPropertyOrder":{},"color":null,"isPrivate":false,"metaSortKey":1661871760800,"_type":"environment"},{"_id":"jar_d4e841a130d42ca6244f63bff76280dd4cac4c03","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1661871760801,"created":1661871760801,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_f7b7acb95ca04689847804de1703d544","parentId":"wrk_8f4f83193b4c4cfdaad52b88c92b74b9","modified":1661871760797,"created":1661871760797,"fileName":"Valex","contents":"","contentType":"yaml","_type":"api_spec"},{"_id":"env_770f5829e3fc41bdb7c2d7fb535d23bb","parentId":"env_d4e841a130d42ca6244f63bff76280dd4cac4c03","modified":1661871801363,"created":1661871772114,"name":"development","data":{"URL":"http://localhost:4000"},"dataPropertyOrder":{"&":["URL"]},"color":"#7d69cb","isPrivate":false,"metaSortKey":1661871772114,"_type":"environment"}]} --------------------------------------------------------------------------------