From b5371faf11d34b4afa253d92bf909e4c2eab7b69 Mon Sep 17 00:00:00 2001 From: Javier Toledo Date: Wed, 19 Apr 2023 20:35:57 +0100 Subject: [PATCH] [NestJS] Milestone 4: Background check --- kyc-nest/.env | 4 + kyc-nest/package-lock.json | 153 +++++++++--------- kyc-nest/package.json | 1 + kyc-nest/src/app.module.ts | 2 + ...interface.ts => api-messages.interface.ts} | 7 + kyc-nest/src/kyc/kyc.controller.ts | 10 +- kyc-nest/src/kyc/kyc.module.ts | 3 +- kyc-nest/src/kyc/kyc.service.ts | 98 ++++++++++- kyc-nest/src/profile/profile.entity.ts | 23 ++- kyc-nest/src/profile/profile.service.ts | 17 +- mock-server.js | 22 +++ 11 files changed, 252 insertions(+), 88 deletions(-) create mode 100644 kyc-nest/.env rename kyc-nest/src/kyc/{webhook-message.interface.ts => api-messages.interface.ts} (92%) create mode 100755 mock-server.js diff --git a/kyc-nest/.env b/kyc-nest/.env new file mode 100644 index 0000000..f2e044c --- /dev/null +++ b/kyc-nest/.env @@ -0,0 +1,4 @@ +OFAC_PROXY_URL="http://localhost:8000" +OFAC_PROXY_API_KEY="1234567890" +PEP_PROXY_URL="http://localhost:8000" +PEP_PROXY_API_KEY="1234567890" diff --git a/kyc-nest/package-lock.json b/kyc-nest/package-lock.json index 2a90f61..497f517 100644 --- a/kyc-nest/package-lock.json +++ b/kyc-nest/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^9.0.0", + "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", @@ -1568,6 +1569,30 @@ } } }, + "node_modules/@nestjs/config": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.3.1.tgz", + "integrity": "sha512-Ckzel0NZ9CWhNsLfE1hxfDuxJuEbhQvGxSlmZ1/X8awjRmAA/g3kT6M1+MO1SHj1wMtPyUfd9WpwkiqFbiwQgA==", + "dependencies": { + "dotenv": "16.0.3", + "dotenv-expand": "10.0.0", + "lodash": "4.17.21", + "uuid": "9.0.0" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, + "node_modules/@nestjs/config/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nestjs/core": { "version": "9.3.12", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.12.tgz", @@ -2795,7 +2820,8 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/array-flatten": { "version": "1.1.1", @@ -3680,18 +3706,6 @@ "node": ">= 8" } }, - "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3849,6 +3863,14 @@ "node": ">=12" } }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6028,6 +6050,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -6159,8 +6182,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -7789,11 +7811,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, "node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -8832,26 +8849,23 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typeorm": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.12.tgz", - "integrity": "sha512-sYSxBmCf1nJLLTcYtwqZ+lQIRtLPyUoO93rHTOKk9vJCyT4UfRtU7oRsJvfvKP3nnZTD1hzz2SEy2zwPEN6OyA==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz", + "integrity": "sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==", "dependencies": { "@sqltools/formatter": "^1.2.5", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "chalk": "^4.1.2", "cli-highlight": "^2.1.11", - "date-fns": "^2.29.3", "debug": "^4.3.4", "dotenv": "^16.0.3", "glob": "^8.1.0", - "js-yaml": "^4.1.0", "mkdirp": "^2.1.3", "reflect-metadata": "^0.1.13", "sha.js": "^2.4.11", "tslib": "^2.5.0", "uuid": "^9.0.0", - "xml2js": "^0.4.23", "yargs": "^17.6.2" }, "bin": { @@ -8871,8 +8885,8 @@ "better-sqlite3": "^7.1.2 || ^8.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", - "mongodb": "^3.6.0", - "mssql": "^7.3.0", + "mongodb": "^5.2.0", + "mssql": "^9.1.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^5.1.0", "pg": "^8.5.1", @@ -9408,26 +9422,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10673,6 +10667,24 @@ "uid": "2.0.1" } }, + "@nestjs/config": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.3.1.tgz", + "integrity": "sha512-Ckzel0NZ9CWhNsLfE1hxfDuxJuEbhQvGxSlmZ1/X8awjRmAA/g3kT6M1+MO1SHj1wMtPyUfd9WpwkiqFbiwQgA==", + "requires": { + "dotenv": "16.0.3", + "dotenv-expand": "10.0.0", + "lodash": "4.17.21", + "uuid": "9.0.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + } + } + }, "@nestjs/core": { "version": "9.3.12", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.12.tgz", @@ -11623,7 +11635,8 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "array-flatten": { "version": "1.1.1", @@ -12275,11 +12288,6 @@ "which": "^2.0.1" } }, - "date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -12392,6 +12400,11 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -14067,6 +14080,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "requires": { "argparse": "^2.0.1" } @@ -14163,8 +14177,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -15378,11 +15391,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -16149,26 +16157,23 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typeorm": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.12.tgz", - "integrity": "sha512-sYSxBmCf1nJLLTcYtwqZ+lQIRtLPyUoO93rHTOKk9vJCyT4UfRtU7oRsJvfvKP3nnZTD1hzz2SEy2zwPEN6OyA==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz", + "integrity": "sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==", "requires": { "@sqltools/formatter": "^1.2.5", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "chalk": "^4.1.2", "cli-highlight": "^2.1.11", - "date-fns": "^2.29.3", "debug": "^4.3.4", "dotenv": "^16.0.3", "glob": "^8.1.0", - "js-yaml": "^4.1.0", "mkdirp": "^2.1.3", "reflect-metadata": "^0.1.13", "sha.js": "^2.4.11", "tslib": "^2.5.0", "uuid": "^9.0.0", - "xml2js": "^0.4.23", "yargs": "^17.6.2" }, "dependencies": { @@ -16506,20 +16511,6 @@ "signal-exit": "^3.0.7" } }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/kyc-nest/package.json b/kyc-nest/package.json index 9b3b80e..1948090 100644 --- a/kyc-nest/package.json +++ b/kyc-nest/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@nestjs/common": "^9.0.0", + "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", diff --git a/kyc-nest/src/app.module.ts b/kyc-nest/src/app.module.ts index 4563b82..84ea3dc 100644 --- a/kyc-nest/src/app.module.ts +++ b/kyc-nest/src/app.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ProfileModule } from './profile/profile.module'; @@ -7,6 +8,7 @@ import { KycModule } from './kyc/kyc.module'; @Module({ imports: [ + ConfigModule.forRoot(), TypeOrmModule.forRoot({ type: 'sqlite', database: 'kyc-nest.sqlite', diff --git a/kyc-nest/src/kyc/webhook-message.interface.ts b/kyc-nest/src/kyc/api-messages.interface.ts similarity index 92% rename from kyc-nest/src/kyc/webhook-message.interface.ts rename to kyc-nest/src/kyc/api-messages.interface.ts index 2d5f685..73f8caa 100644 --- a/kyc-nest/src/kyc/webhook-message.interface.ts +++ b/kyc-nest/src/kyc/api-messages.interface.ts @@ -60,3 +60,10 @@ export interface AddressVerificationWebhookMessage { result: 'success' | 'rejected'; timestamp: string; } + +export interface ManualBackgroundCheckMessage { + userId: string; + validatorId: string; + resolution: 'passed' | 'rejected'; + timestamp: string; +} diff --git a/kyc-nest/src/kyc/kyc.controller.ts b/kyc-nest/src/kyc/kyc.controller.ts index 89184a9..9c0f7dc 100644 --- a/kyc-nest/src/kyc/kyc.controller.ts +++ b/kyc-nest/src/kyc/kyc.controller.ts @@ -3,7 +3,8 @@ import { KYCService } from './kyc.service'; import { IDVerificationWebhookMessage, AddressVerificationWebhookMessage, -} from './webhook-message.interface'; + ManualBackgroundCheckMessage, +} from './api-messages.interface'; @Controller('kyc') export class KYCController { @@ -22,4 +23,11 @@ export class KYCController { ): Promise { await this.kycService.handleAddressVerificationWebhook(message); } + + @Post('submit-manual-background-check') + async submitManualBackgroundCheck( + @Body() payload: ManualBackgroundCheckMessage, + ): Promise { + await this.kycService.submitManualBackgroundCheck(payload); + } } diff --git a/kyc-nest/src/kyc/kyc.module.ts b/kyc-nest/src/kyc/kyc.module.ts index 03e4140..3638019 100644 --- a/kyc-nest/src/kyc/kyc.module.ts +++ b/kyc-nest/src/kyc/kyc.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { KYCService } from './kyc.service'; import { KYCController } from './kyc.controller'; import { ProfileModule } from 'src/profile/profile.module'; +import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [ProfileModule], + imports: [ProfileModule, ConfigModule.forRoot()], providers: [KYCService], controllers: [KYCController], }) diff --git a/kyc-nest/src/kyc/kyc.service.ts b/kyc-nest/src/kyc/kyc.service.ts index 8b5bb67..c8044d3 100644 --- a/kyc-nest/src/kyc/kyc.service.ts +++ b/kyc-nest/src/kyc/kyc.service.ts @@ -2,12 +2,18 @@ import { Injectable } from '@nestjs/common'; import { IDVerificationWebhookMessage, AddressVerificationWebhookMessage, -} from './webhook-message.interface'; + ManualBackgroundCheckMessage, +} from './api-messages.interface'; import { ProfileService } from '../profile/profile.service'; +import { Profile } from 'src/profile/profile.entity'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class KYCService { - constructor(private readonly profileService: ProfileService) {} + constructor( + private readonly profileService: ProfileService, + private configService: ConfigService, + ) {} async handleIDVerificationWebhook( message: IDVerificationWebhookMessage, @@ -48,6 +54,8 @@ export class KYCService { addressVerificationId: message.verificationId, addressVerifiedAt: message.timestamp, }); + + await this.performBackgroundCheck(userId); } else if (message.result === 'rejected') { await this.profileService.update(userId, { kycStatus: 'KYCAddressRejected', @@ -58,4 +66,90 @@ export class KYCService { console.error('Unknown address verification result:', message.result); } } + + async submitManualBackgroundCheck( + payload: ManualBackgroundCheckMessage, + ): Promise { + const { userId, validatorId, resolution, timestamp } = payload; + if (resolution === 'passed') { + await this.profileService.update(userId, { + kycStatus: 'KYCBackgroundCheckPassed', + backgroundCheckManualValidatorId: validatorId, + backgroundCheckPassedAt: timestamp, + }); + } else { + await this.profileService.update(userId, { + kycStatus: 'KYCBackgroundCheckRejected', + backgroundCheckManualValidatorId: validatorId, + backgroundCheckRejectedAt: timestamp, + }); + } + } + + private async performBackgroundCheck(profileId: string): Promise { + const profile = await this.profileService.findById(profileId); + + const passedOFACTest = await this.checkOFACListInclusion(profile); + const passedPEPTest = await this.checkPEPListInclusion(profile); + + if (passedOFACTest && passedPEPTest) { + await this.profileService.update(profileId, { + kycStatus: 'KYCBackgroundCheckPassed', + backgroundCheckPassedAt: new Date().toISOString(), + }); + } else { + await this.profileService.update(profileId, { + kycStatus: 'KYCBackgroundCheckRequiresManualReview', + backgroundCheckTriedAt: new Date().toISOString(), + }); + } + } + + private async checkOFACListInclusion(profile: Profile): Promise { + const ofacProxyURL = this.configService.get('OFAC_PROXY_URL'); + const ofacProxyAPIKey = + this.configService.get('OFAC_PROXY_API_KEY'); + + const ofacResponse = await fetch(ofacProxyURL, { + method: 'POST', + headers: { Authorization: `Bearer ${ofacProxyAPIKey}` }, + body: JSON.stringify({ + origin: 'kycService', + profile_id: profile.id, + type: 'individual', + program: 'all', + first_name: profile.firstName, + last_name: profile.lastName, + date_of_birth: profile.dateOfBirth, + address: profile.address, + city: profile.city, + state: profile.state, + zip_code: profile.zipCode, + country: profile.country, + nationality: profile.nationality, + }), + }); + const ofacRes = await ofacResponse.json(); + return ofacRes.result === 'clear'; + } + + private async checkPEPListInclusion(profile: Profile): Promise { + const pepProxyURL = this.configService.get('PEP_PROXY_URL'); + const pepProxyAPIKey = this.configService.get('PEP_PROXY_API_KEY'); + + const pepResponse = await fetch(pepProxyURL, { + method: 'POST', + headers: { Authorization: `Bearer ${pepProxyAPIKey}` }, + body: JSON.stringify({ + origin: 'kycService', + profile_id: profile.id, + first_name: profile.firstName, + last_name: profile.lastName, + date_of_birth: profile.dateOfBirth, + nationality: profile.nationality, + }), + }); + const pepData = await pepResponse.json(); + return pepData.result === 'clear'; + } } diff --git a/kyc-nest/src/profile/profile.entity.ts b/kyc-nest/src/profile/profile.entity.ts index 3381df2..3cb1c15 100644 --- a/kyc-nest/src/profile/profile.entity.ts +++ b/kyc-nest/src/profile/profile.entity.ts @@ -5,7 +5,10 @@ export type KYCStatus = | 'KYCIDVerified' | 'KYCIDRejected' | 'KYCAddressVerified' - | 'KYCAddressRejected'; + | 'KYCAddressRejected' + | 'KYCBackgroundCheckPassed' + | 'KYCBackgroundCheckRequiresManualReview' + | 'KYCBackgroundCheckRejected'; @Entity() export class Profile { @@ -30,9 +33,15 @@ export class Profile { @Column() zipCode: string; + @Column({ default: 'unknown' }) + country: string; + @Column() dateOfBirth: Date; + @Column({ default: 'unknown' }) + nationality: string; + @Column() phoneNumber: string; @@ -65,4 +74,16 @@ export class Profile { @Column({ nullable: true }) addressRejectedAt?: string; + + @Column({ nullable: true }) + backgroundCheckPassedAt?: string; + + @Column({ nullable: true }) + backgroundCheckTriedAt?: string; + + @Column({ nullable: true }) + backgroundCheckManualValidatorId?: string; + + @Column({ nullable: true }) + backgroundCheckRejectedAt?: string; } diff --git a/kyc-nest/src/profile/profile.service.ts b/kyc-nest/src/profile/profile.service.ts index 310f89c..01a40ed 100644 --- a/kyc-nest/src/profile/profile.service.ts +++ b/kyc-nest/src/profile/profile.service.ts @@ -60,15 +60,28 @@ export class ProfileService { private allowedTransitions(currentState: KYCStatus): KYCStatus[] { switch (currentState) { + // Initial state case 'KYCPending': return ['KYCIDVerified', 'KYCIDRejected']; + // Step 1: ID Verified, waiting for address verification case 'KYCIDVerified': return ['KYCAddressVerified', 'KYCAddressRejected']; - case 'KYCIDRejected': - return []; + // Step 2: Address verified, waiting for background check case 'KYCAddressVerified': + return [ + 'KYCBackgroundCheckPassed', + 'KYCBackgroundCheckRequiresManualReview', + ]; + // Step 3: Background check suspicious, waiting for manual review + case 'KYCBackgroundCheckRequiresManualReview': + return ['KYCBackgroundCheckPassed', 'KYCBackgroundCheckRejected']; + // Step 4: Background check passed, waiting for risk assessment + case 'KYCBackgroundCheckPassed': return []; + // Final states + case 'KYCIDRejected': case 'KYCAddressRejected': + case 'KYCBackgroundCheckRejected': return []; } } diff --git a/mock-server.js b/mock-server.js new file mode 100755 index 0000000..66eb8bd --- /dev/null +++ b/mock-server.js @@ -0,0 +1,22 @@ +const http = require('http'); + +const PORT = 8000; + +const server = http.createServer((req, res) => { + console.log(`Incoming request: ${req.method} ${req.url}`) + req.on('data', chunk => { + console.log(chunk.toString()); + }); + let response = { + result: 'clear' + }; + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + const responseStr = JSON.stringify(response) + res.end(responseStr); + console.log(`Response: ${responseStr}`) +}); + +server.listen(PORT, () => { + console.log(`Server listening on port ${PORT}`); +}); \ No newline at end of file