From 04eea85a0dd8890098bb9045f01167d1c6722592 Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Mon, 17 Jun 2024 22:11:24 +0100 Subject: [PATCH] refactor: yagni and flow of external compliance --- lib/compliance-external.js | 103 ++-- lib/customers.js | 28 +- lib/graphql/types.js | 1 - lib/new-admin/config/accounts.js | 5 +- .../resolvers/externalCompliance.resolver.js | 11 - lib/new-admin/graphql/resolvers/index.js | 2 - .../graphql/types/externalCompliance.type.js | 9 - lib/new-admin/graphql/types/index.js | 2 - lib/plugins/compliance/consts.js | 6 + .../mock-compliance/mock-compliance.js | 14 + lib/plugins/compliance/sumsub/request.js | 43 +- lib/plugins/compliance/sumsub/sumsub.api.js | 99 ++++ lib/plugins/compliance/sumsub/sumsub.js | 477 ++---------------- lib/plugins/compliance/sumsub/utils.js | 455 ----------------- lib/routes/customerRoutes.js | 12 +- migrations/1667945906700-integrate-sumsub.js | 13 - migrations/1718464437502-integrate-sumsub.js | 21 + .../src/pages/Compliance/Sumsub.js | 52 -- .../src/pages/Customers/CustomerData.js | 79 +-- .../Customers/components/EditableCard.js | 84 +-- .../src/pages/Customers/components/index.js | 3 +- .../src/pages/Services/schemas/sumsub.js | 42 +- .../src/pages/Triggers/TriggerView.js | 11 +- .../src/pages/Triggers/Triggers.js | 86 +--- .../src/pages/Triggers/Wizard.js | 17 +- .../src/pages/Triggers/helper.js | 64 +-- new-lamassu-admin/src/utils/constants.js | 5 +- package-lock.json | 60 +-- package.json | 2 - 29 files changed, 389 insertions(+), 1417 deletions(-) delete mode 100644 lib/new-admin/graphql/resolvers/externalCompliance.resolver.js delete mode 100644 lib/new-admin/graphql/types/externalCompliance.type.js create mode 100644 lib/plugins/compliance/consts.js create mode 100644 lib/plugins/compliance/mock-compliance/mock-compliance.js create mode 100644 lib/plugins/compliance/sumsub/sumsub.api.js delete mode 100644 lib/plugins/compliance/sumsub/utils.js delete mode 100644 migrations/1667945906700-integrate-sumsub.js create mode 100644 migrations/1718464437502-integrate-sumsub.js delete mode 100644 new-lamassu-admin/src/pages/Compliance/Sumsub.js diff --git a/lib/compliance-external.js b/lib/compliance-external.js index 87436f8f..e8e817c9 100644 --- a/lib/compliance-external.js +++ b/lib/compliance-external.js @@ -1,55 +1,88 @@ const _ = require('lodash/fp') +const logger = require('./logger') const configManager = require('./new-config-manager') const ph = require('./plugin-helper') -const getPlugin = settings => { - const pluginCodes = ['sumsub'] - const enabledAccounts = _.filter(_plugin => _plugin.enabled, _.map(code => ph.getAccountInstance(settings.accounts[code], code), pluginCodes)) - if (_.isEmpty(enabledAccounts)) { - throw new Error('No external compliance services are active. Please check your 3rd party service configuration') - } - - if (_.size(enabledAccounts) > 1) { - throw new Error('Multiple external compliance services are active. Please check your 3rd party service configuration') - } - const account = _.head(enabledAccounts) - const plugin = ph.load(ph.COMPLIANCE, account.code, account.enabled) +const getPlugin = (settings, pluginCode) => { + const account = settings.accounts[pluginCode] + const plugin = ph.load(ph.COMPLIANCE, pluginCode) return ({ plugin, account }) } -const createApplicant = (settings, customer, applicantLevel) => { - const { plugin } = getPlugin(settings) - const { id } = customer - return plugin.createApplicant({ levelName: applicantLevel, externalUserId: id }) -} - -const getApplicant = (settings, customer) => { +const getStatus = (settings, service, customerId) => { try { - const { plugin } = getPlugin(settings) - const { id } = customer - return plugin.getApplicant({ externalUserId: id }, false) - .then(res => ({ - provider: plugin.CODE, - ...res.data + const { plugin, account } = getPlugin(settings, service) + + return plugin.getApplicantStatus(account, customerId) + .then((status) => ({ + service, + status })) - .catch(() => ({})) - } catch (e) { - return {} + .catch((error) => { + logger.error(`Error getting applicant for service ${service}:`, error) + return { + service: service, + status: null, + } + }) + } catch (error) { + logger.error(`Error loading plugin for service ${service}:`, error) + return Promise.resolve({ + service: service, + status: null, + }) } + } -const createApplicantExternalLink = (settings, customerId, triggerId) => { +const getStatusMap = (settings, customerExternalCompliance) => { const triggers = configManager.getTriggers(settings.config) - const trigger = _.find(it => it.id === triggerId)(triggers) - const { plugin } = getPlugin(settings) - return plugin.createApplicantExternalLink({ levelName: trigger.externalServiceApplicantLevel, userId: customerId }) - .then(r => r.data.url) + const services = _.flow( + _.map('externalService'), + _.compact, + _.uniq + )(triggers) + + const applicantPromises = _.map(service => { + return getStatus(settings, service, customerExternalCompliance) + })(services) + + return Promise.all(applicantPromises) + .then((applicantResults) => { + return _.reduce((map, result) => { + map[result.service] = result.status + return map + }, {})(applicantResults) + }) +} + +const createApplicant = (settings, externalService, customerId) => { + const account = settings.accounts[externalService] + const { plugin } = getPlugin(settings, externalService) + + return plugin.createApplicant(account, customerId, account.applicantLevel) +} + +const createLink = (settings, externalService, customerId) => { + const account = settings.accounts[externalService] + const { plugin } = getPlugin(settings, externalService) + + return plugin.createLink(account, customerId, account.applicantLevel) +} + +const getApplicantByExternalId = (settings, externalService, customerId) => { + const account = settings.accounts[externalService] + const { plugin } = getPlugin(settings, externalService) + + return plugin.getApplicantByExternalId(account, customerId) } module.exports = { + getStatusMap, + getStatus, createApplicant, - getApplicant, - createApplicantExternalLink + getApplicantByExternalId, + createLink } diff --git a/lib/customers.js b/lib/customers.js index f605c269..164bf807 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -932,13 +932,30 @@ function updateLastAuthAttempt (customerId) { function getExternalCustomerData (customer) { return settingsLoader.loadLatest() - .then(settings => externalCompliance.getApplicant(settings, customer)) - .then(externalCompliance => { - customer.externalCompliance = externalCompliance - return customer + .then(settings => externalCompliance.getStatusMap(settings, customer.id)) + .then(statusMap => { + return updateExternalCompliance(customer.id, statusMap) + .then(() => customer.externalCompliance = statusMap) + .then(() => customer) }) } +function updateExternalCompliance(customerId, serviceMap) { + const sql = ` + UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now() + WHERE customer_id=$2 AND service=$3 + ` + const pairs = _.toPairs(serviceMap) + const promises = _.map(([service, status]) => db.none(sql, [status.answer, customerId, service]))(pairs) + return Promise.all(promises) +} + +function addExternalCompliance(customerId, service, id) { + const sql = `INSERT INTO customer_external_compliance (customer_id, external_id, service) VALUES ($1, $2, $3)` + return db.none(sql, [customerId, id, service]) +} + + module.exports = { add, addWithEmail, @@ -962,5 +979,6 @@ module.exports = { updateTxCustomerPhoto, enableTestCustomer, disableTestCustomer, - updateLastAuthAttempt + updateLastAuthAttempt, + addExternalCompliance } diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 8d086151..7977e522 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -82,7 +82,6 @@ type CustomInput { constraintType: String! label1: String label2: String - label3: String choiceList: [String] } diff --git a/lib/new-admin/config/accounts.js b/lib/new-admin/config/accounts.js index 0041ba93..8bbb15e8 100644 --- a/lib/new-admin/config/accounts.js +++ b/lib/new-admin/config/accounts.js @@ -15,6 +15,7 @@ const ID_VERIFIER = 'idVerifier' const EMAIL = 'email' const ZERO_CONF = 'zeroConf' const WALLET_SCORING = 'wallet_scoring' +const COMPLIANCE = 'compliance' const ALL_ACCOUNTS = [ { code: 'bitfinex', display: 'Bitfinex', class: TICKER, cryptos: bitfinex.CRYPTO }, @@ -60,7 +61,9 @@ const ALL_ACCOUNTS = [ { code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] }, { code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true }, { code: 'scorechain', display: 'Scorechain', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, DASH, USDT, USDT_TRON, TRX] }, - { code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true } + { code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true }, + { code: 'sumsub', display: 'Sumsub', class: COMPLIANCE }, + { code: 'mock-compliance', display: 'Mock Compliance', class: COMPLIANCE, dev: true }, ] const devMode = require('minimist')(process.argv.slice(2)).dev diff --git a/lib/new-admin/graphql/resolvers/externalCompliance.resolver.js b/lib/new-admin/graphql/resolvers/externalCompliance.resolver.js deleted file mode 100644 index c25afdc7..00000000 --- a/lib/new-admin/graphql/resolvers/externalCompliance.resolver.js +++ /dev/null @@ -1,11 +0,0 @@ -const externalCompliance = require('../../../compliance-external') -const { loadLatest } = require('../../../new-settings-loader') - -const resolvers = { - Query: { - getApplicantExternalLink: (...[, { customerId, triggerId }]) => loadLatest() - .then(settings => externalCompliance.createApplicantExternalLink(settings, customerId, triggerId)) - } -} - -module.exports = resolvers diff --git a/lib/new-admin/graphql/resolvers/index.js b/lib/new-admin/graphql/resolvers/index.js index f10e10cc..81e79614 100644 --- a/lib/new-admin/graphql/resolvers/index.js +++ b/lib/new-admin/graphql/resolvers/index.js @@ -7,7 +7,6 @@ const config = require('./config.resolver') const currency = require('./currency.resolver') const customer = require('./customer.resolver') const customInfoRequests = require('./customInfoRequests.resolver') -const externalCompliance = require('./externalCompliance.resolver') const funding = require('./funding.resolver') const log = require('./log.resolver') const loyalty = require('./loyalty.resolver') @@ -31,7 +30,6 @@ const resolvers = [ currency, customer, customInfoRequests, - externalCompliance, funding, log, loyalty, diff --git a/lib/new-admin/graphql/types/externalCompliance.type.js b/lib/new-admin/graphql/types/externalCompliance.type.js deleted file mode 100644 index f05f3153..00000000 --- a/lib/new-admin/graphql/types/externalCompliance.type.js +++ /dev/null @@ -1,9 +0,0 @@ -const { gql } = require('apollo-server-express') - -const typeDef = gql` - type Query { - getApplicantExternalLink(customerId: ID, triggerId: ID): String - } -` - -module.exports = typeDef diff --git a/lib/new-admin/graphql/types/index.js b/lib/new-admin/graphql/types/index.js index 11bfee20..a1886a28 100644 --- a/lib/new-admin/graphql/types/index.js +++ b/lib/new-admin/graphql/types/index.js @@ -7,7 +7,6 @@ const config = require('./config.type') const currency = require('./currency.type') const customer = require('./customer.type') const customInfoRequests = require('./customInfoRequests.type') -const externalCompliance = require('./externalCompliance.type') const funding = require('./funding.type') const log = require('./log.type') const loyalty = require('./loyalty.type') @@ -31,7 +30,6 @@ const types = [ currency, customer, customInfoRequests, - externalCompliance, funding, log, loyalty, diff --git a/lib/plugins/compliance/consts.js b/lib/plugins/compliance/consts.js new file mode 100644 index 00000000..3700c1d3 --- /dev/null +++ b/lib/plugins/compliance/consts.js @@ -0,0 +1,6 @@ +module.exports = { + WAIT: 'WAIT', + RETRY: 'RETRY', + APPROVED: 'APPROVED', + REJECTED: 'REJECTED' +} \ No newline at end of file diff --git a/lib/plugins/compliance/mock-compliance/mock-compliance.js b/lib/plugins/compliance/mock-compliance/mock-compliance.js new file mode 100644 index 00000000..366b1861 --- /dev/null +++ b/lib/plugins/compliance/mock-compliance/mock-compliance.js @@ -0,0 +1,14 @@ +const CODE = 'mock-compliance' + +const createLink = (settings, userId, level) => { + return `this is a mock external link, ${userId}, ${level}` +} + +const getApplicantStatus = (settings, userId) => { +} + +module.exports = { + CODE, + createLink, + getApplicantStatus +} diff --git a/lib/plugins/compliance/sumsub/request.js b/lib/plugins/compliance/sumsub/request.js index 588acc23..f102f996 100644 --- a/lib/plugins/compliance/sumsub/request.js +++ b/lib/plugins/compliance/sumsub/request.js @@ -2,40 +2,33 @@ const axios = require('axios') const crypto = require('crypto') const _ = require('lodash/fp') const FormData = require('form-data') -const settingsLoader = require('../../../new-settings-loader') - -const ph = require('../../../plugin-helper') const axiosConfig = { baseURL: 'https://api.sumsub.com' } -const axiosInstance = axios.create(axiosConfig) +const getSigBuilder = (apiToken, secretKey) => config => { + const timestamp = Math.floor(Date.now() / 1000) + const signature = crypto.createHmac('sha256', secretKey) -const buildSignature = config => { - return settingsLoader.loadLatest() - .then(({ accounts }) => ph.getAccountInstance(accounts.sumsub, 'sumsub')) - .then(({ secretKey, apiToken }) => { - const timestamp = Math.floor(Date.now() / 1000) - const signature = crypto.createHmac('sha256', secretKey) - - signature.update(`${timestamp}${_.toUpper(config.method)}${config.url}`) - if (config.data instanceof FormData) { - signature.update(config.data.getBuffer()) - } else if (config.data) { - signature.update(config.data) - } + signature.update(`${timestamp}${_.toUpper(config.method)}${config.url}`) + if (config.data instanceof FormData) { + signature.update(config.data.getBuffer()) + } else if (config.data) { + signature.update(JSON.stringify(config.data)) + } - config.headers['X-App-Token'] = apiToken - config.headers['X-App-Access-Sig'] = signature.digest('hex') - config.headers['X-App-Access-Ts'] = timestamp + config.headers['X-App-Token'] = apiToken + config.headers['X-App-Access-Sig'] = signature.digest('hex') + config.headers['X-App-Access-Ts'] = timestamp - return config - }) + return config } -axiosInstance.interceptors.request.use(buildSignature, Promise.reject) - -const request = config => axiosInstance(config) +const request = ((account, config) => { + const instance = axios.create(axiosConfig) + instance.interceptors.request.use(getSigBuilder(account.apiToken, account.secretKey), Promise.reject) + return instance(config) +}) module.exports = request diff --git a/lib/plugins/compliance/sumsub/sumsub.api.js b/lib/plugins/compliance/sumsub/sumsub.api.js new file mode 100644 index 00000000..f611678f --- /dev/null +++ b/lib/plugins/compliance/sumsub/sumsub.api.js @@ -0,0 +1,99 @@ +const request = require('./request') + +const createApplicant = (account, userId, level) => { + if (!userId || !level) { + return Promise.reject(`Missing required fields: userId: ${userId}, level: ${level}`) + } + + const config = { + method: 'POST', + url: `/resources/applicants?levelName=${level}`, + data: { + externalUserId: userId, + sourceKey: 'lamassu' + }, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + } + + return request(account, config) +} + +const createLink = (account, userId, level) => { + if (!userId || !level) { + return Promise.reject(`Missing required fields: userId: ${userId}, level: ${level}`) + } + + const config = { + method: 'POST', + url: `/resources/sdkIntegrations/levels/${level}/websdkLink?ttlInSecs=${600}&externalUserId=${userId}`, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + } + + return request(account, config) +} + +const getApplicantByExternalId = (account, id) => { + console.log('id', id) + if (!id) { + return Promise.reject('Missing required fields: id') + } + + const config = { + method: 'GET', + url: `/resources/applicants/-;externalUserId=${id}/one`, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } + + return request(account, config) +} + +const getApplicantStatus = (account, id) => { + if (!id) { + return Promise.reject(`Missing required fields: id`) + } + + const config = { + method: 'GET', + url: `/resources/applicants/${id}/status`, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } + + return request(account, config) +} + +const getApplicantById = (account, id) => { + if (!id) { + return Promise.reject(`Missing required fields: id`) + } + + const config = { + method: 'GET', + url: `/resources/applicants/${id}/one`, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } + + return request(account, config) +} + +module.exports = { + createLink, + createApplicant, + getApplicantByExternalId, + getApplicantById, + getApplicantStatus +} diff --git a/lib/plugins/compliance/sumsub/sumsub.js b/lib/plugins/compliance/sumsub/sumsub.js index 0fdd9838..8f036f97 100644 --- a/lib/plugins/compliance/sumsub/sumsub.js +++ b/lib/plugins/compliance/sumsub/sumsub.js @@ -1,462 +1,55 @@ const _ = require('lodash/fp') -const request = require('./request') +const sumsubApi = require('./sumsub.api') +const { WAIT, RETRY, APPROVED, REJECTED } = require('../consts') const CODE = 'sumsub' -const hasRequiredFields = fields => obj => _.every(_.partial(_.has, [_, obj]), fields) -const getMissingRequiredFields = (fields, obj) => - _.reduce( - (acc, value) => { - if (!_.has(value, obj)) { - acc.push(value) - } - return acc - }, - [], - fields - ) - -const createApplicantExternalLink = opts => { - const REQUIRED_FIELDS = ['userId', 'levelName'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/sdkIntegrations/levels/${opts.levelName}/websdkLink?ttlInSecs=${600}&externalUserId=${opts.userId}`, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - }) +const getApplicantByExternalId = (account, userId) => { + return sumsubApi.getApplicantByExternalId(account, userId) + .then(r => r.data) } -const createApplicant = opts => { - const REQUIRED_FIELDS = ['levelName', 'externalUserId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants?levelName=${opts.levelName}`, - headers: { - 'Content-Type': 'application/json' - }, - data: { - externalUserId: opts.externalUserId - } - }) +const createApplicant = (account, userId, level) => { + return sumsubApi.createApplicant(account, userId, level) + .then(r => r.data) + .catch(err => { + if (err.response.status === 409) return getApplicantByExternalId(account, userId) + throw err + }) } -const changeRequiredLevel = opts => { - const REQUIRED_FIELDS = ['applicantId', 'levelName'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/moveToLevel?name=${opts.levelName}`, - headers: { - 'Content-Type': 'application/json' - } - }) +const createLink = (account, userId, level) => { + return sumsubApi.createLink(account, userId, level) + .then(r => r.data.url) } -const getApplicant = (opts, knowsApplicantId = true) => { - const REQUIRED_FIELDS = knowsApplicantId - ? ['applicantId'] - : ['externalUserId'] +const getApplicantStatus = (account, userId) => { + return sumsubApi.getApplicantByExternalId(account, userId) + .then(r => { + const levelName = _.get('data.review.levelName', r) + const reviewStatus = _.get('data.review.reviewStatus', r) + const reviewAnswer = _.get('data.review.reviewResult.reviewAnswer', r) + const reviewRejectType = _.get('data.review.reviewResult.reviewRejectType', r) - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } + console.log('levelName', levelName) + console.log('reviewStatus', reviewStatus) + console.log('reviewAnswer', reviewAnswer) + console.log('reviewRejectType', reviewRejectType) - return request({ - method: 'GET', - url: knowsApplicantId ? `/resources/applicants/${opts.applicantId}/one` : `/resources/applicants/-;externalUserId=${opts.externalUserId}/one`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} + let answer = WAIT + if (reviewAnswer === 'GREEN') answer = APPROVED + if (reviewAnswer === 'RED' && reviewRejectType === 'RETRY') answer = RETRY + if (reviewAnswer === 'RED' && reviewRejectType === 'FINAL') answer = REJECTED -const getApplicantStatus = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'GET', - url: `/resources/applicants/${opts.applicantId}/status`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const getApplicantIdDocsStatus = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'GET', - url: `/resources/applicants/${opts.applicantId}/requiredIdDocsStatus`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const addIdDocument = opts => { - const REQUIRED_FIELDS = ['applicantId', 'metadata', 'metadata.idDocType', 'metadata.country'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - const form = new FormData() - form.append('metadata', opts.metadata) - form.append('content', opts.content) - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/info/idDoc`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'multipart/form-data', - 'X-Return-Doc-Warnings': 'true' - }, - data: form - }) -} - -const changeApplicantFixedInfo = opts => { - const REQUIRED_FIELDS = ['applicantId', 'newData'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'PATCH', - url: `/resources/applicants/${opts.applicantId}/fixedInfo`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - data: opts.newData - }) -} - -const getApplicantRejectReasons = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'GET', - url: `/resources/moderationStates/-;applicantId=${opts.applicantId}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const requestApplicantCheck = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/status/pending${!_.isNil(opts.reason) ? `?reason=${opts.reason}` : ``}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const requestApplicantCheckDiffVerificationType = opts => { - const REQUIRED_FIELDS = ['applicantId', 'reasonCode'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/status/pending${!_.isNil(opts.reasonCode) ? `?reasonCode=${opts.reasonCode}` : ``}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const getDocumentImages = opts => { - const REQUIRED_FIELDS = ['inspectionId', 'imageId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'GET', - url: `/resources/inspections/${opts.inspectionId}/resources/${opts.imageId}`, - headers: { - 'Accept': 'image/jpeg, image/png, application/pdf, video/mp4, video/webm, video/quicktime', - 'Content-Type': 'application/json' - } - }) -} - -const blockApplicant = opts => { - const REQUIRED_FIELDS = ['applicantId', 'note'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/blacklist?note=${opts.note}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const generateShareToken = opts => { - const REQUIRED_FIELDS = ['applicantId', 'clientId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/accessTokens/-/shareToken?applicantId=${opts.applicantId}&forClientId=${opts.clientId}${!_.isNil(opts.ttlInSecs) ? `&ttlInSecs=${opts.ttlInSecs}` : ``}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const importRawApplicant = opts => { - const REQUIRED_FIELDS = ['applicantObj'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/-/ingestCompleted`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - data: opts.applicantObj - }) -} - -const importApplicantFromPartnerService = opts => { - const REQUIRED_FIELDS = ['shareToken'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/-/import?shareToken=${opts.shareToken}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const resetVerificationStep = opts => { - const REQUIRED_FIELDS = ['applicantId', 'idDocSetType'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/resetStep/${opts.idDocSetType}`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const resetApplicant = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/reset`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) -} - -const patchApplicantTopLevelInfo = opts => { - const REQUIRED_FIELDS = ['applicantId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/`, - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - data: { - id: opts.applicantId, - externalUserId: opts.externalUserId, - email: opts.email, - phone: opts.phone, - sourceKey: opts.sourceKey, - type: opts.type, - lang: opts.lang, - questionnaires: opts.questionnaires, - metadata: opts.metadata, - deleted: opts.deleted - } - }) -} - -const setApplicantRiskLevel = opts => { - const REQUIRED_FIELDS = ['applicantId', 'comment', 'riskLevel'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/riskLevel/entries`, - headers: { - 'Content-Type': 'application/json' - }, - data: { - comment: opts.comment, - riskLevel: opts.riskLevel - } - }) -} - -const addApplicantTags = opts => { - const REQUIRED_FIELDS = ['applicantId', 'tags'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'POST', - url: `/resources/applicants/${opts.applicantId}/tags`, - headers: { - 'Content-Type': 'application/json' - }, - data: opts.tags - }) -} - -const markImageAsInactive = opts => { - const REQUIRED_FIELDS = ['inspectionId', 'imageId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'DELETE', - url: `/resources/inspections/${opts.inspectionId}/resources/${opts.imageId}?revert=false` - }) -} - -const markImageAsActive = opts => { - const REQUIRED_FIELDS = ['inspectionId', 'imageId'] - - if (_.isEmpty(opts) || !hasRequiredFields(REQUIRED_FIELDS, opts)) { - return Promise.reject(`Missing required fields: ${getMissingRequiredFields(REQUIRED_FIELDS, opts)}`) - } - - return request({ - method: 'DELETE', - url: `/resources/inspections/${opts.inspectionId}/resources/${opts.imageId}?revert=true` - }) -} - -const getApiHealth = () => { - return request({ - method: 'GET', - url: `/resources/status/api` - }) + return { level: levelName, answer } + }) } module.exports = { CODE, - createApplicantExternalLink, createApplicant, - getApplicant, - addIdDocument, - changeApplicantFixedInfo, getApplicantStatus, - getApplicantIdDocsStatus, - getApplicantRejectReasons, - requestApplicantCheck, - requestApplicantCheckDiffVerificationType, - getDocumentImages, - blockApplicant, - generateShareToken, - importRawApplicant, - importApplicantFromPartnerService, - resetVerificationStep, - resetApplicant, - patchApplicantTopLevelInfo, - setApplicantRiskLevel, - addApplicantTags, - markImageAsInactive, - markImageAsActive, - getApiHealth, - changeRequiredLevel -} + getApplicantByExternalId, + createLink +} \ No newline at end of file diff --git a/lib/plugins/compliance/sumsub/utils.js b/lib/plugins/compliance/sumsub/utils.js deleted file mode 100644 index b81bfe48..00000000 --- a/lib/plugins/compliance/sumsub/utils.js +++ /dev/null @@ -1,455 +0,0 @@ -const ADD_ID_DOCUMENT_WARNINGS = { - badSelfie: 'Make sure that your face and the photo in the document are clearly visible', - dataReadability: 'Please make sure that the information in the document is easy to read', - inconsistentDocument: 'Please ensure that all uploaded photos are of the same document', - maybeExpiredDoc: 'Your document appears to be expired', - documentTooMuchOutside: 'Please ensure that the document completely fits the photo' -} - -const ADD_ID_DOCUMENT_ERRORS = { - forbiddenDocument: 'Unsupported or unacceptable type/country of document', - differentDocTypeOrCountry: 'Document type or country mismatches ones that was sent with metadata', - missingImportantInfo: 'Not all required document data can be recognized', - dataNotReadable: 'There is no available data to recognize from image', - expiredDoc: 'Document validity date is expired', - documentWayTooMuchOutside: 'Not all parts of the documents are visible', - grayscale: 'Black and white image', - noIdDocFacePhoto: 'Face is not clearly visible on the document', - selfieFaceBadQuality: 'Face is not clearly visible on the selfie', - screenRecapture: 'Image might be a photo of screen', - screenshot: 'Image is a screenshot', - sameSides: 'Image of the same side of document was uploaded as front and back sides', - shouldBeMrzDocument: 'Sent document type should have an MRZ, but there is no readable MRZ on the image', - shouldBeDoubleSided: 'Two sides of the sent document should be presented', - shouldBeDoublePaged: 'The full double-page of the document are required', - documentDeclinedBefore: 'The same image was uploaded and declined earlier' -} - -const SUPPORTED_DOCUMENT_TYPES = { - ID_CARD: { - code: 'ID_CARD', - description: 'An ID card' - }, - PASSPORT: { - code: 'PASSPORT', - description: 'A passport' - }, - DRIVERS: { - code: 'DRIVERS', - description: 'A driving license' - }, - RESIDENCE_PERMIT: { - code: 'RESIDENCE_PERMIT', - description: 'Residence permit or registration document in the foreign city/country' - }, - UTILITY_BILL: { - code: 'UTILITY_BILL', - description: 'Proof of address document' - }, - SELFIE: { - code: 'SELFIE', - description: 'A selfie with a document' - }, - VIDEO_SELFIE: { - code: 'VIDEO_SELFIE', - description: 'A selfie video' - }, - PROFILE_IMAGE: { - code: 'PROFILE_IMAGE', - description: 'A profile image, i.e. avatar' - }, - ID_DOC_PHOTO: { - code: 'ID_DOC_PHOTO', - description: 'Photo from an ID doc (like a photo from a passport)' - }, - AGREEMENT: { - code: 'AGREEMENT', - description: 'Agreement of some sort, e.g. for processing personal info' - }, - CONTRACT: { - code: 'CONTRACT', - description: 'Some sort of contract' - }, - DRIVERS_TRANSLATION: { - code: 'DRIVERS_TRANSLATION', - description: 'Translation of the driving license required in the target country' - }, - INVESTOR_DOC: { - code: 'INVESTOR_DOC', - description: 'A document from an investor, e.g. documents which disclose assets of the investor' - }, - VEHICLE_REGISTRATION_CERTIFICATE: { - code: 'VEHICLE_REGISTRATION_CERTIFICATE', - description: 'Certificate of vehicle registration' - }, - INCOME_SOURCE: { - code: 'INCOME_SOURCE', - description: 'A proof of income' - }, - PAYMENT_METHOD: { - code: 'PAYMENT_METHOD', - description: 'Entity confirming payment (like bank card, crypto wallet, etc)' - }, - BANK_CARD: { - code: 'BANK_CARD', - description: 'A bank card, like Visa or Maestro' - }, - COVID_VACCINATION_FORM: { - code: 'COVID_VACCINATION_FORM', - description: 'COVID vaccination document' - }, - OTHER: { - code: 'OTHER', - description: 'Should be used only when nothing else applies' - }, -} - -const VERIFICATION_RESULTS = { - GREEN: { - code: 'GREEN', - description: 'Everything is fine' - }, - RED: { - code: 'RED', - description: 'Some violations found' - } -} - -const REVIEW_REJECT_TYPES = { - FINAL: { - code: 'FINAL', - description: 'Final reject, e.g. when a person is a fraudster, or a client does not want to accept such kinds of clients in their system' - }, - RETRY: { - code: 'RETRY', - description: 'Decline that can be fixed, e.g. by uploading an image of better quality' - } -} - -const REVIEW_REJECT_LABELS = { - FORGERY: { - code: 'FORGERY', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Forgery attempt has been made' - }, - DOCUMENT_TEMPLATE: { - code: 'DOCUMENT_TEMPLATE', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Documents supplied are templates, downloaded from internet' - }, - LOW_QUALITY: { - code: 'LOW_QUALITY', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Documents have low-quality that does not allow definitive conclusions to be made' - }, - SPAM: { - code: 'SPAM', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'An applicant has been created by mistake or is just a spam user (irrelevant images were supplied)' - }, - NOT_DOCUMENT: { - code: 'NOT_DOCUMENT', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Documents supplied are not relevant for the verification procedure' - }, - SELFIE_MISMATCH: { - code: 'SELFIE_MISMATCH', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'A user photo (profile image) does not match a photo on the provided documents' - }, - ID_INVALID: { - code: 'ID_INVALID', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'A document that identifies a person (like a passport or an ID card) is not valid' - }, - FOREIGNER: { - code: 'FOREIGNER', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'When a client does not accept applicants from a different country or e.g. without a residence permit' - }, - DUPLICATE: { - code: 'DUPLICATE', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'This applicant was already created for this client, and duplicates are not allowed by the regulations' - }, - BAD_AVATAR: { - code: 'BAD_AVATAR', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'When avatar does not meet the client\'s requirements' - }, - WRONG_USER_REGION: { - code: 'WRONG_USER_REGION', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'When applicants from certain regions/countries are not allowed to be registered' - }, - INCOMPLETE_DOCUMENT: { - code: 'INCOMPLETE_DOCUMENT', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Some information is missing from the document, or it\'s partially visible' - }, - BLACKLIST: { - code: 'BLACKLIST', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'User is blocklisted' - }, - UNSATISFACTORY_PHOTOS: { - code: 'UNSATISFACTORY_PHOTOS', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'There were problems with the photos, like poor quality or masked information' - }, - DOCUMENT_PAGE_MISSING: { - code: 'DOCUMENT_PAGE_MISSING', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Some pages of a document are missing (if applicable)' - }, - DOCUMENT_DAMAGED: { - code: 'DOCUMENT_DAMAGED', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Document is damaged' - }, - REGULATIONS_VIOLATIONS: { - code: 'REGULATIONS_VIOLATIONS', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Regulations violations' - }, - INCONSISTENT_PROFILE: { - code: 'INCONSISTENT_PROFILE', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Data or documents of different persons were uploaded to one applicant' - }, - PROBLEMATIC_APPLICANT_DATA: { - code: 'PROBLEMATIC_APPLICANT_DATA', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Applicant data does not match the data in the documents' - }, - ADDITIONAL_DOCUMENT_REQUIRED: { - code: 'ADDITIONAL_DOCUMENT_REQUIRED', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Additional documents required to pass the check' - }, - AGE_REQUIREMENT_MISMATCH: { - code: 'AGE_REQUIREMENT_MISMATCH', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Age requirement is not met (e.g. cannot rent a car to a person below 25yo)' - }, - EXPERIENCE_REQUIREMENT_MISMATCH: { - code: 'EXPERIENCE_REQUIREMENT_MISMATCH', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Not enough experience (e.g. driving experience is not enough)' - }, - CRIMINAL: { - code: 'CRIMINAL', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'The user is involved in illegal actions' - }, - WRONG_ADDRESS: { - code: 'WRONG_ADDRESS', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The address from the documents doesn\'t match the address that the user entered' - }, - GRAPHIC_EDITOR: { - code: 'GRAPHIC_EDITOR', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The document has been edited by a graphical editor' - }, - DOCUMENT_DEPRIVED: { - code: 'DOCUMENT_DEPRIVED', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user has been deprived of the document' - }, - COMPROMISED_PERSONS: { - code: 'COMPROMISED_PERSONS', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'The user does not correspond to Compromised Person Politics' - }, - PEP: { - code: 'PEP', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'The user belongs to the PEP category' - }, - ADVERSE_MEDIA: { - code: 'ADVERSE_MEDIA', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'The user was found in the adverse media' - }, - FRAUDULENT_PATTERNS: { - code: 'FRAUDULENT_PATTERNS', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'Fraudulent behavior was detected' - }, - SANCTIONS: { - code: 'SANCTIONS', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'The user was found on sanction lists' - }, - NOT_ALL_CHECKS_COMPLETED: { - code: 'NOT_ALL_CHECKS_COMPLETED', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'All checks were not completed' - }, - FRONT_SIDE_MISSING: { - code: 'FRONT_SIDE_MISSING', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Front side of the document is missing' - }, - BACK_SIDE_MISSING: { - code: 'BACK_SIDE_MISSING', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Back side of the document is missing' - }, - SCREENSHOTS: { - code: 'SCREENSHOTS', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded screenshots' - }, - BLACK_AND_WHITE: { - code: 'BLACK_AND_WHITE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded black and white photos of documents' - }, - INCOMPATIBLE_LANGUAGE: { - code: 'INCOMPATIBLE_LANGUAGE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user should upload translation of his document' - }, - EXPIRATION_DATE: { - code: 'EXPIRATION_DATE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded expired document' - }, - UNFILLED_ID: { - code: 'UNFILLED_ID', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded the document without signatures and stamps' - }, - BAD_SELFIE: { - code: 'BAD_SELFIE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded a bad selfie' - }, - BAD_VIDEO_SELFIE: { - code: 'BAD_VIDEO_SELFIE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded a bad video selfie' - }, - BAD_FACE_MATCHING: { - code: 'BAD_FACE_MATCHING', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Face check between document and selfie failed' - }, - BAD_PROOF_OF_IDENTITY: { - code: 'BAD_PROOF_OF_IDENTITY', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded a bad ID document' - }, - BAD_PROOF_OF_ADDRESS: { - code: 'BAD_PROOF_OF_ADDRESS', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded a bad proof of address' - }, - BAD_PROOF_OF_PAYMENT: { - code: 'BAD_PROOF_OF_PAYMENT', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user uploaded a bad proof of payment' - }, - SELFIE_WITH_PAPER: { - code: 'SELFIE_WITH_PAPER', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'The user should upload a special selfie (e.g. selfie with paper and date on it)' - }, - FRAUDULENT_LIVENESS: { - code: 'FRAUDULENT_LIVENESS', - rejectType: REVIEW_REJECT_TYPES.FINAL, - description: 'There was an attempt to bypass liveness check' - }, - OTHER: { - code: 'OTHER', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Some unclassified reason' - }, - REQUESTED_DATA_MISMATCH: { - code: 'REQUESTED_DATA_MISMATCH', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Provided info doesn\'t match with recognized from document data' - }, - OK: { - code: 'OK', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Custom reject label' - }, - COMPANY_NOT_DEFINED_STRUCTURE: { - code: 'COMPANY_NOT_DEFINED_STRUCTURE', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Could not establish the entity\'s control structure' - }, - COMPANY_NOT_DEFINED_BENEFICIARIES: { - code: 'COMPANY_NOT_DEFINED_BENEFICIARIES', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Could not identify and duly verify the entity\'s beneficial owners' - }, - COMPANY_NOT_VALIDATED_BENEFICIARIES: { - code: 'COMPANY_NOT_VALIDATED_BENEFICIARIES', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Beneficiaries are not validated' - }, - COMPANY_NOT_DEFINED_REPRESENTATIVES: { - code: 'COMPANY_NOT_DEFINED_REPRESENTATIVES', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Representatives are not defined' - }, - COMPANY_NOT_VALIDATED_REPRESENTATIVES: { - code: 'COMPANY_NOT_VALIDATED_REPRESENTATIVES', - rejectType: REVIEW_REJECT_TYPES.RETRY, - description: 'Representatives are not validated' - }, -} - -const REVIEW_STATUS = { - init: 'Initial registration has started. A client is still in the process of filling out the applicant profile. Not all required documents are currently uploaded', - pending: 'An applicant is ready to be processed', - prechecked: 'The check is in a half way of being finished', - queued: 'The checks have been started for the applicant', - completed: 'The check has been completed', - onHold: 'Applicant waits for a final decision from compliance officer (manual check was initiated) or waits for all beneficiaries to pass KYC in case of company verification', -} - -const RESETTABLE_VERIFICATION_STEPS = { - PHONE_VERIFICATION: { - code: 'PHONE_VERIFICATION', - description: 'Phone verification step' - }, - EMAIL_VERIFICATION: { - code: 'EMAIL_VERIFICATION', - description: 'Email verification step' - }, - QUESTIONNAIRE: { - code: 'QUESTIONNAIRE', - description: 'Questionnaire' - }, - APPLICANT_DATA: { - code: 'APPLICANT_DATA', - description: 'Applicant data' - }, - IDENTITY: { - code: 'IDENTITY', - description: 'Identity step' - }, - PROOF_OF_RESIDENCE: { - code: 'PROOF_OF_RESIDENCE', - description: 'Proof of residence' - }, - SELFIE: { - code: 'SELFIE', - description: 'Selfie step' - }, -} - -module.exports = { - ADD_ID_DOCUMENT_WARNINGS, - ADD_ID_DOCUMENT_ERRORS, - SUPPORTED_DOCUMENT_TYPES, - VERIFICATION_RESULTS, - REVIEW_REJECT_LABELS, - REVIEW_STATUS, - RESETTABLE_VERIFICATION_STEPS -} diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index ea9ed016..75fe6641 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -18,7 +18,7 @@ const notifier = require('../notifier') const respond = require('../respond') const { getTx } = require('../new-admin/services/transactions.js') const machineLoader = require('../machine-loader') -const { loadLatest, loadLatestConfig } = require('../new-settings-loader') +const { loadLatestConfig } = require('../new-settings-loader') const customInfoRequestQueries = require('../new-admin/services/customInfoRequests') const T = require('../time') const plugins = require('../plugins') @@ -243,12 +243,12 @@ function getExternalComplianceLink (req, res, next) { const settings = req.settings const triggers = configManager.getTriggers(settings.config) const trigger = _.find(it => it.id === triggerId)(triggers) + const externalService = trigger.externalService - return externalCompliance.createApplicantExternalLink(settings, customerId, trigger.id) - .then(url => { - process.env.NODE_ENV === 'development' && console.log(url) - return respond(req, res, { url: url }) - }) + return externalCompliance.createApplicant(settings, externalService, customerId) + .then(applicant => customers.addExternalCompliance(customerId, externalService, applicant.id)) + .then(() => externalCompliance.createLink(settings, externalService, customerId)) + .then(url => respond(req, res, { url })) } function addOrUpdateCustomer (customerData, config, isEmailAuth) { diff --git a/migrations/1667945906700-integrate-sumsub.js b/migrations/1667945906700-integrate-sumsub.js deleted file mode 100644 index 0080faa6..00000000 --- a/migrations/1667945906700-integrate-sumsub.js +++ /dev/null @@ -1,13 +0,0 @@ -var db = require('./db') - -exports.up = function (next) { - var sql = [ - `ALTER TABLE customers ADD COLUMN applicant_id TEXT` - ] - - db.multi(sql, next) -} - -exports.down = function (next) { - next() -} diff --git a/migrations/1718464437502-integrate-sumsub.js b/migrations/1718464437502-integrate-sumsub.js new file mode 100644 index 00000000..0e49de6e --- /dev/null +++ b/migrations/1718464437502-integrate-sumsub.js @@ -0,0 +1,21 @@ +const db = require('./db') + +exports.up = function (next) { + let sql = [ + `CREATE TYPE EXTERNAL_COMPLIANCE_STATUS AS ENUM('WAIT', 'APPROVED', 'REJECTED', 'RETRY')`, + `CREATE TABLE CUSTOMER_EXTERNAL_COMPLIANCE ( + customer_id UUID NOT NULL REFERENCES customers(id), + service TEXT NOT NULL, + external_id TEXT NOT NULL, + last_known_status EXTERNAL_COMPLIANCE_STATUS, + last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (customer_id, service) + )` + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/pages/Compliance/Sumsub.js b/new-lamassu-admin/src/pages/Compliance/Sumsub.js deleted file mode 100644 index 2e38cd66..00000000 --- a/new-lamassu-admin/src/pages/Compliance/Sumsub.js +++ /dev/null @@ -1,52 +0,0 @@ -import { useQuery } from '@apollo/react-hooks' -import SumsubWebSdk from '@sumsub/websdk-react' -import gql from 'graphql-tag' -import React from 'react' -import { useLocation } from 'react-router-dom' - -const QueryParams = () => new URLSearchParams(useLocation().search) - -const CREATE_NEW_TOKEN = gql` - query getApplicantAccessToken($customerId: ID, $triggerId: ID) { - getApplicantAccessToken(customerId: $customerId, triggerId: $triggerId) - } -` - -const Sumsub = () => { - const token = QueryParams().get('t') - const customerId = QueryParams().get('customer') - const triggerId = QueryParams().get('trigger') - - const { refetch: getNewToken } = useQuery(CREATE_NEW_TOKEN, { - skip: true, - variables: { customerId: customerId, triggerId: triggerId } - }) - - const config = { - lang: 'en' - } - - const options = { - addViewportTag: true, - adaptIframeHeight: true - } - - const updateAccessToken = () => - getNewToken().then(res => { - const { getApplicantAccessToken: _token } = res.data - return _token - }) - - return ( - - ) -} - -export default Sumsub diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index cc159380..8c6d5bc0 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -11,8 +11,7 @@ import { TextInput } from 'src/components/inputs/formik' import { H3, Info3 } from 'src/components/typography' import { OVERRIDE_AUTHORIZED, - OVERRIDE_REJECTED, - OVERRIDE_PENDING + OVERRIDE_REJECTED } from 'src/pages/Customers/components/propertyCard' import { ReactComponent as CardIcon } from 'src/styling/icons/ID/card/comet.svg' import { ReactComponent as PhoneIcon } from 'src/styling/icons/ID/phone/comet.svg' @@ -26,7 +25,7 @@ import { URI } from 'src/utils/apollo' import { onlyFirstToUpper } from 'src/utils/string' import styles from './CustomerData.styles.js' -import { EditableCard, NonEditableCard } from './components' +import { EditableCard } from './components' import { customerDataElements, customerDataSchemas, @@ -401,59 +400,22 @@ const CustomerData = ({ }) }, R.keys(smsData) ?? []) - const externalComplianceProvider = - R.path([`externalCompliance`, `provider`])(customer) ?? undefined - - const externalComplianceData = { - sumsub: { - getApplicantInfo: data => { - return R.path(['fixedInfo'])(data) ?? {} - }, - getVerificationState: data => { - const reviewStatus = R.path(['review', 'reviewStatus'])(data) - const reviewResult = R.path(['review', 'reviewResult', 'reviewAnswer'])( - data - ) - - const state = - reviewStatus === 'completed' - ? reviewResult === 'GREEN' - ? OVERRIDE_AUTHORIZED - : OVERRIDE_REJECTED - : OVERRIDE_PENDING - - const comment = R.path(['review', 'reviewResult', 'clientComment'])( - data - ) - - const labels = R.path(['review', 'reviewResult', 'rejectLabels'])(data) - - return { state, comment, labels } - } - } - } - - const externalComplianceValues = R.path(['externalCompliance'])(customer) - - if ( - !R.isNil(externalComplianceValues) && - !R.isEmpty(externalComplianceValues) - ) { - externalCompliance.push({ - fields: R.map(it => ({ name: it[0], label: it[0], value: it[1] }))( - R.toPairs( - externalComplianceData[externalComplianceProvider]?.getApplicantInfo( - externalComplianceValues - ) - ) - ), - titleIcon: , - state: externalComplianceData[ - externalComplianceProvider - ]?.getVerificationState(externalComplianceValues), - title: 'External Info' - }) - } + // TODO - add external compliance data + // R.forEach(outer => { + // initialValues. + // externalCompliance.push({ + // fields: [ + // { + // name: 'lastKnownStatus', + // label: 'Last Known Status', + // component: TextInput + // } + // ], + // titleIcon: , + // state: outer.state, + // title: 'External Info' + // }) + // })(R.keys(customer.externalCompliance)) const editableCard = ( { @@ -501,13 +463,14 @@ const CustomerData = ({ idx ) => { return ( - + fields={fields}> ) } diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 6783b648..1dc71165 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -8,7 +8,6 @@ import { useState, React } from 'react' import ErrorMessage from 'src/components/ErrorMessage' import PromptWhenDirty from 'src/components/PromptWhenDirty' import { MainStatus } from 'src/components/Status' -import { HoverableTooltip } from 'src/components/Tooltip' import { ActionButton } from 'src/components/buttons' import { Label1, P, H3 } from 'src/components/typography' import { @@ -402,85 +401,4 @@ const EditableCard = ({ ) } -const NonEditableCard = ({ - fields, - hasImage, - state: _state, - title, - titleIcon -}) => { - const classes = useStyles() - - const { state, comment, labels } = _state - - const label1ClassNames = { - [classes.label1]: true, - [classes.label1Pending]: state === OVERRIDE_PENDING, - [classes.label1Rejected]: state === OVERRIDE_REJECTED, - [classes.label1Accepted]: state === OVERRIDE_AUTHORIZED - } - const authorized = - state === OVERRIDE_PENDING - ? { label: 'Pending', type: 'neutral' } - : state === OVERRIDE_REJECTED - ? { label: 'Rejected', type: 'error' } - : { label: 'Accepted', type: 'success' } - - return ( -
- - -
-
- {titleIcon} -

{title}

- { - // TODO: Enable for next release - /* */ - } -
- {state && ( -
- - {comment && labels && ( - -

Comments about this decision:

- {R.map( - it => ( -

{it}

- ), - R.split('\n', comment) - )} -

Relevant labels: {R.join(',', labels)}

-
- )} -
- )} -
-
- - - {!hasImage && - fields?.map((field, idx) => { - return idx >= 0 && idx < 4 ? ( - - ) : null - })} - - - {!hasImage && - fields?.map((field, idx) => { - return idx >= 4 ? ( - - ) : null - })} - - -
-
-
-
- ) -} - -export { EditableCard, NonEditableCard } +export default EditableCard diff --git a/new-lamassu-admin/src/pages/Customers/components/index.js b/new-lamassu-admin/src/pages/Customers/components/index.js index bc009220..7e3c3e19 100644 --- a/new-lamassu-admin/src/pages/Customers/components/index.js +++ b/new-lamassu-admin/src/pages/Customers/components/index.js @@ -2,7 +2,7 @@ import Wizard from '../Wizard' import CustomerDetails from './CustomerDetails' import CustomerSidebar from './CustomerSidebar' -import { EditableCard, NonEditableCard } from './EditableCard' +import EditableCard from './EditableCard' import Field from './Field' import IdDataCard from './IdDataCard' import PhotosCarousel from './PhotosCarousel' @@ -17,7 +17,6 @@ export { CustomerSidebar, Field, EditableCard, - NonEditableCard, Wizard, Upload } diff --git a/new-lamassu-admin/src/pages/Services/schemas/sumsub.js b/new-lamassu-admin/src/pages/Services/schemas/sumsub.js index 20eb6bc8..ee156a9e 100644 --- a/new-lamassu-admin/src/pages/Services/schemas/sumsub.js +++ b/new-lamassu-admin/src/pages/Services/schemas/sumsub.js @@ -1,53 +1,13 @@ -import React, { useState } from 'react' import * as Yup from 'yup' -import { Button } from 'src/components/buttons' -import { Checkbox } from 'src/components/inputs' import { SecretInput, TextInput } from 'src/components/inputs/formik' -import { P } from 'src/components/typography' import { secretTest } from './helper' -const SumsubSplash = ({ classes, onContinue }) => { - const [canContinue, setCanContinue] = useState(false) - - return ( -
-

- Before linking the Sumsub 3rd party service to the Lamassu Admin, make - sure you have configured the required parameters in your personal Sumsub - Dashboard. -

-

- These parameters include the Sumsub Global Settings, Applicant Levels, - Twilio and Webhooks. -

- setCanContinue(!canContinue)} - settings={{ - enabled: true, - label: 'I have completed the steps needed to configure Sumsub', - rightSideLabel: true - }} - /> -
-
- -
-
-
- ) -} - const schema = { code: 'sumsub', name: 'Sumsub', - category: 'Compliance', - allowMultiInstances: false, - SplashScreenComponent: SumsubSplash, + title: 'Sumsub (Compliance)', elements: [ { code: 'apiToken', diff --git a/new-lamassu-admin/src/pages/Triggers/TriggerView.js b/new-lamassu-admin/src/pages/Triggers/TriggerView.js index 5c049f36..58688227 100644 --- a/new-lamassu-admin/src/pages/Triggers/TriggerView.js +++ b/new-lamassu-admin/src/pages/Triggers/TriggerView.js @@ -28,9 +28,8 @@ const TriggerView = ({ config, toggleWizard, addNewTriger, - customInfoRequests, emailAuth, - additionalInfo + customInfoRequests }) => { const currency = R.path(['fiatCurrency'])( fromNamespace(namespaces.LOCALE)(config) @@ -70,12 +69,7 @@ const TriggerView = ({ error={error?.message} save={save} validationSchema={Schema} - elements={getElements( - currency, - classes, - customInfoRequests, - additionalInfo - )} + elements={getElements(currency, classes, customInfoRequests)} /> {showWizard && ( )} {R.isEmpty(triggers) && ( diff --git a/new-lamassu-admin/src/pages/Triggers/Triggers.js b/new-lamassu-admin/src/pages/Triggers/Triggers.js index ade21956..9d70edfe 100644 --- a/new-lamassu-admin/src/pages/Triggers/Triggers.js +++ b/new-lamassu-admin/src/pages/Triggers/Triggers.js @@ -4,7 +4,6 @@ import classnames from 'classnames' import gql from 'graphql-tag' import * as R from 'ramda' import React, { useState } from 'react' -import { getAccountInstance } from 'src/utils/accounts' import Modal from 'src/components/Modal' import { HoverableTooltip } from 'src/components/Tooltip' @@ -19,7 +18,6 @@ import { ReactComponent as CustomInfoIcon } from 'src/styling/icons/circle butto import { ReactComponent as ReverseSettingsIcon } from 'src/styling/icons/circle buttons/settings/white.svg' import { ReactComponent as SettingsIcon } from 'src/styling/icons/circle buttons/settings/zodiac.svg' import { fromNamespace, toNamespace } from 'src/utils/config' -import { COMPLIANCE_SERVICES } from 'src/utils/constants' import CustomInfoRequests from './CustomInfoRequests' import TriggerView from './TriggerView' @@ -134,67 +132,32 @@ const Triggers = () => { else toggleWizard('newTrigger')() } - const accounts = data?.accounts ?? {} - const isAnyExternalValidationAccountEnabled = () => { - try { - return R.any( - it => it === true, - R.map( - ite => getAccountInstance(accounts[ite], ite)?.enabled, - COMPLIANCE_SERVICES - ) - ) - } catch (e) { - return false - } - } - - const buttons = [] - const externalValidationLevels = !R.isEmpty(accounts) - ? R.reduce( - (acc, value) => { - const instances = accounts[value]?.instances ?? {} - return { - ...acc, - [value]: R.map( - it => ({ value: it, display: it }), - R.uniq(R.map(ite => ite.applicantLevel, instances) ?? []) - ) - } - }, - {}, - COMPLIANCE_SERVICES - ) - : [] - - !isAnyExternalValidationAccountEnabled() && - buttons.push({ - text: 'Advanced settings', - icon: SettingsIcon, - inverseIcon: ReverseSettingsIcon, - forceDisable: !(subMenu === 'advancedSettings'), - toggle: show => { - refetch() - setSubMenu(show ? 'advancedSettings' : false) - } - }) - - buttons.push({ - text: 'Custom info requests', - icon: CustomInfoIcon, - inverseIcon: ReverseCustomInfoIcon, - forceDisable: !(subMenu === 'customInfoRequests'), - toggle: show => { - refetch() - setSubMenu(show ? 'customInfoRequests' : false) - } - }) - return ( <> { + refetch() + setSubMenu(show ? 'advancedSettings' : false) + } + }, + { + text: 'Custom info requests', + icon: CustomInfoIcon, + inverseIcon: ReverseCustomInfoIcon, + forceDisable: !(subMenu === 'customInfoRequests'), + toggle: show => { + refetch() + setSubMenu(show ? 'customInfoRequests' : false) + } + } + ]} className={classnames(titleSectionWidth)}> {!subMenu && ( @@ -257,10 +220,7 @@ const Triggers = () => { toggleWizard={toggleWizard('newTrigger')} addNewTriger={addNewTriger} emailAuth={emailAuth} - additionalInfo={{ - customInfoRequests: enabledCustomInfoRequests, - externalValidationLevels: externalValidationLevels - }} + customInfoRequests={enabledCustomInfoRequests} /> )} {!loading && subMenu === 'advancedSettings' && ( diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js index 0d966d6c..1d83c3ea 100644 --- a/new-lamassu-admin/src/pages/Triggers/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js @@ -53,8 +53,7 @@ const getStep = ( currency, customInfoRequests, emailAuth, - triggers, - additionalInfo + triggers ) => { switch (step) { // case 1: @@ -62,13 +61,7 @@ const getStep = ( case 1: return type(currency) case 2: - return requirements( - customInfoRequests, - emailAuth, - config, - triggers, - additionalInfo - ) + return requirements(config, triggers, customInfoRequests, emailAuth) default: return Fragment } @@ -226,8 +219,7 @@ const Wizard = ({ currency, customInfoRequests, emailAuth, - triggers, - additionalInfo + triggers }) => { const classes = useStyles() @@ -242,8 +234,7 @@ const Wizard = ({ currency, customInfoRequests, emailAuth, - triggers, - additionalInfo + triggers ) const onContinue = async it => { diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js index 9e03abb7..5bd3d0f8 100644 --- a/new-lamassu-admin/src/pages/Triggers/helper.js +++ b/new-lamassu-admin/src/pages/Triggers/helper.js @@ -504,13 +504,6 @@ const requirementSchema = Yup.object() otherwise: Yup.string() .nullable() .transform(() => '') - }), - externalServiceApplicantLevel: Yup.string().when('requirement', { - is: value => value === 'external', - then: Yup.string(), - otherwise: Yup.string() - .nullable() - .transform(() => '') }) }).required() }) @@ -527,8 +520,7 @@ const requirementSchema = Yup.object() : true case 'external': return requirement.requirement === type - ? !R.isNil(requirement.externalService) && - !R.isNil(requirement.externalServiceApplicantLevel) + ? !R.isNil(requirement.externalService) : true default: return true @@ -582,20 +574,13 @@ const hasCustomRequirementError = (errors, touched, values) => const hasExternalRequirementError = (errors, touched, values) => !!errors.requirement && !!touched.requirement?.externalService && - !!touched.requirement?.externalServiceApplicantLevel && - (!values.requirement?.externalService || - !R.isNil(values.requirement?.externalService)) && - (!values.requirement?.externalServiceApplicantLevel || - !R.isNil(values.requirement?.externalServiceApplicantLevel)) + !values.requirement?.externalService const Requirement = ({ config = {}, triggers, - additionalInfo: { - emailAuth, - customInfoRequests = [], - externalValidationLevels = {} - } + emailAuth, + customInfoRequests = [] }) => { const classes = useStyles() const { @@ -725,30 +710,17 @@ const Requirement = ({ name="requirement.externalService" options={externalServices} /> - {!R.isNil( - externalValidationLevels[values.requirement.externalService] - ) && ( - - )} )} ) } -const requirements = (config, triggers, additionalInfo) => ({ +const requirements = (config, triggers, customInfoRequests, emailAuth) => ({ schema: requirementSchema, options: requirementOptions, Component: Requirement, - props: { config, triggers, additionalInfo }, + props: { config, triggers, customInfoRequests, emailAuth }, hasRequirementError: hasRequirementError, hasCustomRequirementError: hasCustomRequirementError, hasExternalRequirementError: hasExternalRequirementError, @@ -757,8 +729,7 @@ const requirements = (config, triggers, additionalInfo) => ({ requirement: '', suspensionDays: '', customInfoRequestId: '', - externalService: '', - externalServiceApplicantLevel: '' + externalService: '' } } }) @@ -788,9 +759,7 @@ const customReqIdMatches = customReqId => it => { return it.id === customReqId } -const RequirementInput = ({ - additionalInfo: { customInfoRequests = [], externalValidationLevels = {} } -}) => { +const RequirementInput = ({ customInfoRequests = [] }) => { const { values } = useFormikContext() const classes = useStyles() @@ -826,7 +795,7 @@ const RequirementView = ({ suspensionDays, customInfoRequestId, externalService, - additionalInfo: { customInfoRequests = [], externalValidationLevels = {} } + customInfoRequests = [] }) => { const classes = useStyles() const display = @@ -949,7 +918,7 @@ const ThresholdView = ({ config, currency }) => { return } -const getElements = (currency, classes, additionalInfo) => [ +const getElements = (currency, classes, customInfoRequests) => [ { name: 'triggerType', size: 'sm', @@ -970,8 +939,10 @@ const getElements = (currency, classes, additionalInfo) => [ size: 'sm', width: 260, bypassField: true, - input: () => , - view: it => + input: () => , + view: it => ( + + ) }, { name: 'threshold', @@ -1003,7 +974,7 @@ const sortBy = [ ) ] -const fromServer = (triggers, customInfoRequests) => { +const fromServer = triggers => { return R.map( ({ requirement, @@ -1012,15 +983,13 @@ const fromServer = (triggers, customInfoRequests) => { thresholdDays, customInfoRequestId, externalService, - externalServiceApplicantLevel, ...rest }) => ({ requirement: { requirement, suspensionDays, customInfoRequestId, - externalService, - externalServiceApplicantLevel + externalService }, threshold: { threshold, @@ -1039,7 +1008,6 @@ const toServer = triggers => thresholdDays: threshold.thresholdDays, customInfoRequestId: requirement.customInfoRequestId, externalService: requirement.externalService, - externalServiceApplicantLevel: requirement.externalServiceApplicantLevel, ...rest }))(triggers) diff --git a/new-lamassu-admin/src/utils/constants.js b/new-lamassu-admin/src/utils/constants.js index 43f4d4f2..9d829d3c 100644 --- a/new-lamassu-admin/src/utils/constants.js +++ b/new-lamassu-admin/src/utils/constants.js @@ -10,8 +10,6 @@ const IP_CHECK_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[ const SWEEPABLE_CRYPTOS = ['ETH'] -const COMPLIANCE_SERVICES = ['sumsub'] - export { CURRENCY_MAX, MIN_NUMBER_OF_CASSETTES, @@ -20,6 +18,5 @@ export { MANUAL, WALLET_SCORING_DEFAULT_THRESHOLD, IP_CHECK_REGEX, - SWEEPABLE_CRYPTOS, - COMPLIANCE_SERVICES + SWEEPABLE_CRYPTOS } diff --git a/package-lock.json b/package-lock.json index 67ebc670..e2d8f8f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1342,7 +1342,7 @@ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, "bitcoinjs-message": { - "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", + "version": "npm:bitcoinjs-message@1.0.0-master.2", "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", "requires": { @@ -4714,36 +4714,6 @@ "wif": "^2.0.1" } }, - "bitcoinjs-message": { - "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", - "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", - "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", - "requires": { - "bech32": "^1.1.3", - "bs58check": "^2.1.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "5.0.0", - "varuint-bitcoin": "^1.0.1" - }, - "dependencies": { - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "secp256k1": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", - "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^5.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - }, "bitcore-lib": { "version": "8.25.47", "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz", @@ -8644,12 +8614,12 @@ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -12203,6 +12173,16 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "get-uri": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", @@ -16423,6 +16403,16 @@ "readable-stream": "^2.3.5" }, "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", diff --git a/package.json b/package.json index b9f3a450..2f432c93 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "@haensl/subset-sum": "^3.0.5", "@lamassu/coins": "v1.4.10", "@simplewebauthn/server": "^3.0.0", - "@sumsub/websdk-react": "^1.3.6", "@vonage/auth": "1.5.0", "@vonage/sms": "1.7.0", "@vonage/server-client": "1.7.0", @@ -48,7 +47,6 @@ "ethereumjs-wallet": "^0.6.3", "express": "4.17.1", "express-session": "^1.17.1", - "express-ws": "^3.0.0", "form-data": "^4.0.0", "futoin-hkdf": "^1.0.2", "got": "^7.1.0",