feat: notifications and flow fixes
This commit is contained in:
parent
423cfd4bbb
commit
8f8e95c292
9 changed files with 103 additions and 26 deletions
|
|
@ -21,7 +21,7 @@ const getStatus = (settings, service, customerId) => {
|
||||||
status
|
status
|
||||||
}))
|
}))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error(`Error getting applicant for service ${service}:`, error)
|
if (error.response.status !== 404) logger.error(`Error getting applicant for service ${service}:`, error.message)
|
||||||
return {
|
return {
|
||||||
service: service,
|
service: service,
|
||||||
status: null,
|
status: null,
|
||||||
|
|
@ -45,7 +45,6 @@ const getStatusMap = (settings, customerExternalCompliance) => {
|
||||||
_.uniq
|
_.uniq
|
||||||
)(triggers)
|
)(triggers)
|
||||||
|
|
||||||
const meta = {}
|
|
||||||
const applicantPromises = _.map(service => {
|
const applicantPromises = _.map(service => {
|
||||||
return getStatus(settings, service, customerExternalCompliance)
|
return getStatus(settings, service, customerExternalCompliance)
|
||||||
})(services)
|
})(services)
|
||||||
|
|
@ -53,7 +52,7 @@ const getStatusMap = (settings, customerExternalCompliance) => {
|
||||||
return Promise.all(applicantPromises)
|
return Promise.all(applicantPromises)
|
||||||
.then((applicantResults) => {
|
.then((applicantResults) => {
|
||||||
return _.reduce((map, result) => {
|
return _.reduce((map, result) => {
|
||||||
map[result.service] = result.status
|
if (result.status) map[result.service] = result.status
|
||||||
return map
|
return map
|
||||||
}, {})(applicantResults)
|
}, {})(applicantResults)
|
||||||
})
|
})
|
||||||
|
|
@ -73,16 +72,9 @@ const createLink = (settings, externalService, customerId) => {
|
||||||
return plugin.createLink(account, customerId, account.applicantLevel)
|
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 = {
|
module.exports = {
|
||||||
getStatusMap,
|
getStatusMap,
|
||||||
|
getStatus,
|
||||||
createApplicant,
|
createApplicant,
|
||||||
getApplicantByExternalId,
|
|
||||||
createLink
|
createLink
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ const settingsLoader = require('./new-settings-loader')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const externalCompliance = require('./compliance-external')
|
const externalCompliance = require('./compliance-external')
|
||||||
|
|
||||||
|
const { APPROVED, RETRY } = require('./plugins/compliance/consts')
|
||||||
|
|
||||||
const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel', 'scoreThresholdReached', 'walletScoringError']
|
const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel', 'scoreThresholdReached', 'walletScoringError']
|
||||||
|
|
||||||
const ID_PHOTO_CARD_DIR = process.env.ID_PHOTO_CARD_DIR
|
const ID_PHOTO_CARD_DIR = process.env.ID_PHOTO_CARD_DIR
|
||||||
|
|
@ -938,13 +940,21 @@ function getExternalComplianceMachine (customer) {
|
||||||
return settingsLoader.loadLatest()
|
return settingsLoader.loadLatest()
|
||||||
.then(settings => externalCompliance.getStatusMap(settings, customer.id))
|
.then(settings => externalCompliance.getStatusMap(settings, customer.id))
|
||||||
.then(statusMap => {
|
.then(statusMap => {
|
||||||
return updateExternalCompliance(customer.id, statusMap)
|
return updateExternalComplianceByMap(customer.id, statusMap)
|
||||||
.then(() => customer.externalCompliance = statusMap)
|
.then(() => customer.externalCompliance = statusMap)
|
||||||
.then(() => customer)
|
.then(() => customer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateExternalCompliance(customerId, serviceMap) {
|
function updateExternalCompliance(customerId, service, status) {
|
||||||
|
const sql = `
|
||||||
|
UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now()
|
||||||
|
WHERE customer_id=$2 AND service=$3
|
||||||
|
`
|
||||||
|
return db.none(sql, [status, customerId, service])
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExternalComplianceByMap(customerId, serviceMap) {
|
||||||
const sql = `
|
const sql = `
|
||||||
UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now()
|
UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now()
|
||||||
WHERE customer_id=$2 AND service=$3
|
WHERE customer_id=$2 AND service=$3
|
||||||
|
|
@ -959,12 +969,56 @@ function getExternalCompliance(customer) {
|
||||||
FROM customer_external_compliance where customer_id=$1`
|
FROM customer_external_compliance where customer_id=$1`
|
||||||
return db.manyOrNone(sql, [customer.id])
|
return db.manyOrNone(sql, [customer.id])
|
||||||
.then(compliance => {
|
.then(compliance => {
|
||||||
console.log(compliance)
|
|
||||||
customer.externalCompliance = compliance
|
customer.externalCompliance = compliance
|
||||||
})
|
})
|
||||||
.then(() => customer)
|
.then(() => customer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOpenExternalCompliance() {
|
||||||
|
const sql = `SELECT customer_id, service, last_known_status FROM customer_external_compliance where last_known_status in ('PENDING', 'RETRY') or last_known_status is null`
|
||||||
|
return db.manyOrNone(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyRetryExternalCompliance(settings, customerId, service) {
|
||||||
|
const sql = 'SELECT phone FROM customers WHERE id=$1'
|
||||||
|
const promises = [db.one(sql, [customerId]), externalCompliance.createLink(settings, service, customerId)]
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(([toNumber, link]) => {
|
||||||
|
const body = `Your external compliance verification has failed. Please try again. Link for retry: ${link}`
|
||||||
|
|
||||||
|
return sms.sendMessage(settings, { toNumber, body })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyApprovedExternalCompliance(settings, customerId) {
|
||||||
|
const sql = 'SELECT phone FROM customers WHERE id=$1'
|
||||||
|
return db.one(sql, [customerId])
|
||||||
|
.then((toNumber) => {
|
||||||
|
const body = 'Your external compliance verification has been approved.'
|
||||||
|
|
||||||
|
return sms.sendMessage(settings, { toNumber, body })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkExternalCompliance(settings) {
|
||||||
|
return getOpenExternalCompliance()
|
||||||
|
.then(externals => {
|
||||||
|
console.log(externals)
|
||||||
|
const promises = _.map(external => {
|
||||||
|
return externalCompliance.getStatus(settings, external.service, external.customer_id)
|
||||||
|
.then(status => {
|
||||||
|
console.log('status', status, external.customer_id, external.service)
|
||||||
|
if (status.status.answer === RETRY) notifyRetryExternalCompliance(settings, external.customer_id, status.service)
|
||||||
|
if (status.status.answer === APPROVED) notifyApprovedExternalCompliance(settings, external.customer_id)
|
||||||
|
|
||||||
|
return updateExternalCompliance(external.customer_id, external.service, status.status.answer)
|
||||||
|
})
|
||||||
|
}, externals)
|
||||||
|
return Promise.all(promises)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function addExternalCompliance(customerId, service, id) {
|
function addExternalCompliance(customerId, service, id) {
|
||||||
const sql = `INSERT INTO customer_external_compliance (customer_id, external_id, service) VALUES ($1, $2, $3)`
|
const sql = `INSERT INTO customer_external_compliance (customer_id, external_id, service) VALUES ($1, $2, $3)`
|
||||||
return db.none(sql, [customerId, id, service])
|
return db.none(sql, [customerId, id, service])
|
||||||
|
|
@ -995,5 +1049,6 @@ module.exports = {
|
||||||
enableTestCustomer,
|
enableTestCustomer,
|
||||||
disableTestCustomer,
|
disableTestCustomer,
|
||||||
updateLastAuthAttempt,
|
updateLastAuthAttempt,
|
||||||
addExternalCompliance
|
addExternalCompliance,
|
||||||
|
checkExternalCompliance
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ type Trigger {
|
||||||
thresholdDays: Int
|
thresholdDays: Int
|
||||||
customInfoRequestId: String
|
customInfoRequestId: String
|
||||||
customInfoRequest: CustomInfoRequest
|
customInfoRequest: CustomInfoRequest
|
||||||
|
externalService: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type TermsDetails {
|
type TermsDetails {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
WAIT: 'WAIT',
|
PENDING: 'PENDING',
|
||||||
RETRY: 'RETRY',
|
RETRY: 'RETRY',
|
||||||
APPROVED: 'APPROVED',
|
APPROVED: 'APPROVED',
|
||||||
REJECTED: 'REJECTED'
|
REJECTED: 'REJECTED'
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,31 @@
|
||||||
|
const uuid = require('uuid')
|
||||||
|
|
||||||
|
const {APPROVED} = require('../consts')
|
||||||
|
|
||||||
const CODE = 'mock-compliance'
|
const CODE = 'mock-compliance'
|
||||||
|
|
||||||
const createLink = (settings, userId, level) => {
|
const createLink = (settings, userId, level) => {
|
||||||
return `this is a mock external link, ${userId}, ${level}`
|
return `this is a mock external link, ${userId}, ${level}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApplicantStatus = (settings, userId) => {
|
const getApplicantStatus = (account, userId) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
service: CODE,
|
||||||
|
status: {
|
||||||
|
level: account.applicantLevel, answer: APPROVED
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createApplicant = () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: uuid.v4()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CODE,
|
CODE,
|
||||||
createLink,
|
createApplicant,
|
||||||
getApplicantStatus
|
getApplicantStatus,
|
||||||
|
createLink
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const sumsubApi = require('./sumsub.api')
|
const sumsubApi = require('./sumsub.api')
|
||||||
const { WAIT, RETRY, APPROVED, REJECTED } = require('../consts')
|
const { PENDING, RETRY, APPROVED, REJECTED } = require('../consts')
|
||||||
|
|
||||||
const CODE = 'sumsub'
|
const CODE = 'sumsub'
|
||||||
|
|
||||||
|
|
@ -31,17 +31,16 @@ const getApplicantStatus = (account, userId) => {
|
||||||
const reviewStatus = _.get('data.review.reviewStatus', r)
|
const reviewStatus = _.get('data.review.reviewStatus', r)
|
||||||
const reviewAnswer = _.get('data.review.reviewResult.reviewAnswer', r)
|
const reviewAnswer = _.get('data.review.reviewResult.reviewAnswer', r)
|
||||||
const reviewRejectType = _.get('data.review.reviewResult.reviewRejectType', r)
|
const reviewRejectType = _.get('data.review.reviewResult.reviewRejectType', r)
|
||||||
const sumsubUserId = _.get('data.review.reviewResult.reviewRejectType', r)
|
|
||||||
|
|
||||||
// if last review was from a different level, return the current level and RETRY
|
// if last review was from a different level, return the current level and RETRY
|
||||||
if (levelName !== account.applicantLevel) return { thirdPartyId: sumsubUserId, level: account.applicantLevel, answer: RETRY }
|
if (levelName !== account.applicantLevel) return { level: account.applicantLevel, answer: RETRY }
|
||||||
|
|
||||||
let answer = WAIT
|
let answer = PENDING
|
||||||
if (reviewAnswer === 'GREEN' && reviewStatus === 'completed') answer = APPROVED
|
if (reviewAnswer === 'GREEN' && reviewStatus === 'completed') answer = APPROVED
|
||||||
if (reviewAnswer === 'RED' && reviewRejectType === 'RETRY') answer = RETRY
|
if (reviewAnswer === 'RED' && reviewRejectType === 'RETRY') answer = RETRY
|
||||||
if (reviewAnswer === 'RED' && reviewRejectType === 'FINAL') answer = REJECTED
|
if (reviewAnswer === 'RED' && reviewRejectType === 'FINAL') answer = REJECTED
|
||||||
|
|
||||||
return { thirdPartyId: sumsubUserId, level: levelName, answer }
|
return { level: levelName, answer }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +48,5 @@ module.exports = {
|
||||||
CODE,
|
CODE,
|
||||||
createApplicant,
|
createApplicant,
|
||||||
getApplicantStatus,
|
getApplicantStatus,
|
||||||
getApplicantByExternalId,
|
|
||||||
createLink
|
createLink
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ const T = require('./time')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const cashOutTx = require('./cash-out/cash-out-tx')
|
const cashOutTx = require('./cash-out/cash-out-tx')
|
||||||
const cashInTx = require('./cash-in/cash-in-tx')
|
const cashInTx = require('./cash-in/cash-in-tx')
|
||||||
|
const customers = require('./customers')
|
||||||
const sanctionsUpdater = require('./ofac/update')
|
const sanctionsUpdater = require('./ofac/update')
|
||||||
const sanctions = require('./ofac/index')
|
const sanctions = require('./ofac/index')
|
||||||
const coinAtmRadar = require('./coinatmradar/coinatmradar')
|
const coinAtmRadar = require('./coinatmradar/coinatmradar')
|
||||||
|
|
@ -31,6 +32,7 @@ const RADAR_UPDATE_INTERVAL = 5 * T.minutes
|
||||||
const PRUNE_MACHINES_HEARTBEAT = 1 * T.day
|
const PRUNE_MACHINES_HEARTBEAT = 1 * T.day
|
||||||
const TRANSACTION_BATCH_LIFECYCLE = 20 * T.minutes
|
const TRANSACTION_BATCH_LIFECYCLE = 20 * T.minutes
|
||||||
const TICKER_RATES_INTERVAL = 59 * T.seconds
|
const TICKER_RATES_INTERVAL = 59 * T.seconds
|
||||||
|
const EXTERNAL_COMPLIANCE_INTERVAL = 1 * T.minutes
|
||||||
|
|
||||||
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
||||||
const PENDING_INTERVAL = 10 * T.seconds
|
const PENDING_INTERVAL = 10 * T.seconds
|
||||||
|
|
@ -127,6 +129,10 @@ function updateCoinAtmRadar () {
|
||||||
.then(rates => coinAtmRadar.update(rates, settings()))
|
.then(rates => coinAtmRadar.update(rates, settings()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function checkExternalCompliance (settings) {
|
||||||
|
// return customers.checkExternalCompliance(settings)
|
||||||
|
// }
|
||||||
|
|
||||||
function initializeEachSchema (schemas = ['public']) {
|
function initializeEachSchema (schemas = ['public']) {
|
||||||
// for each schema set "thread variables" and do polling
|
// for each schema set "thread variables" and do polling
|
||||||
return _.forEach(schema => {
|
return _.forEach(schema => {
|
||||||
|
|
@ -190,6 +196,7 @@ function doPolling (schema) {
|
||||||
pi().sweepHd()
|
pi().sweepHd()
|
||||||
notifier.checkNotification(pi())
|
notifier.checkNotification(pi())
|
||||||
updateCoinAtmRadar()
|
updateCoinAtmRadar()
|
||||||
|
// checkExternalCompliance(settings())
|
||||||
|
|
||||||
addToQueue(pi().getRawRates, TICKER_RATES_INTERVAL, schema, QUEUE.FAST)
|
addToQueue(pi().getRawRates, TICKER_RATES_INTERVAL, schema, QUEUE.FAST)
|
||||||
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
||||||
|
|
@ -206,6 +213,7 @@ function doPolling (schema) {
|
||||||
addToQueue(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
addToQueue(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
||||||
addToQueue(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
addToQueue(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL, schema, QUEUE.SLOW)
|
||||||
addToQueue(pi().pruneMachinesHeartbeat, PRUNE_MACHINES_HEARTBEAT, schema, QUEUE.SLOW, settings)
|
addToQueue(pi().pruneMachinesHeartbeat, PRUNE_MACHINES_HEARTBEAT, schema, QUEUE.SLOW, settings)
|
||||||
|
// addToQueue(checkExternalCompliance, EXTERNAL_COMPLIANCE_INTERVAL, schema, QUEUE.SLOW, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup (schemasToAdd = [], schemasToRemove = []) {
|
function setup (schemasToAdd = [], schemasToRemove = []) {
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ function sendSmsReceipt (req, res, next) {
|
||||||
function getExternalComplianceLink (req, res, next) {
|
function getExternalComplianceLink (req, res, next) {
|
||||||
const customerId = req.query.customer
|
const customerId = req.query.customer
|
||||||
const triggerId = req.query.trigger
|
const triggerId = req.query.trigger
|
||||||
|
const isRetry = req.query.isRetry
|
||||||
if (_.isNil(customerId) || _.isNil(triggerId)) return next(httpError('Not Found', 404))
|
if (_.isNil(customerId) || _.isNil(triggerId)) return next(httpError('Not Found', 404))
|
||||||
|
|
||||||
const settings = req.settings
|
const settings = req.settings
|
||||||
|
|
@ -245,6 +246,11 @@ function getExternalComplianceLink (req, res, next) {
|
||||||
const trigger = _.find(it => it.id === triggerId)(triggers)
|
const trigger = _.find(it => it.id === triggerId)(triggers)
|
||||||
const externalService = trigger.externalService
|
const externalService = trigger.externalService
|
||||||
|
|
||||||
|
if (isRetry) {
|
||||||
|
return externalCompliance.createLink(settings, externalService, customerId)
|
||||||
|
.then(url => respond(req, res, { url }))
|
||||||
|
}
|
||||||
|
|
||||||
return externalCompliance.createApplicant(settings, externalService, customerId)
|
return externalCompliance.createApplicant(settings, externalService, customerId)
|
||||||
.then(applicant => customers.addExternalCompliance(customerId, externalService, applicant.id))
|
.then(applicant => customers.addExternalCompliance(customerId, externalService, applicant.id))
|
||||||
.then(() => externalCompliance.createLink(settings, externalService, customerId))
|
.then(() => externalCompliance.createLink(settings, externalService, customerId))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const db = require('./db')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
let sql = [
|
let sql = [
|
||||||
`CREATE TYPE EXTERNAL_COMPLIANCE_STATUS AS ENUM('WAIT', 'APPROVED', 'REJECTED', 'RETRY')`,
|
`CREATE TYPE EXTERNAL_COMPLIANCE_STATUS AS ENUM('PENDING', 'APPROVED', 'REJECTED', 'RETRY')`,
|
||||||
`CREATE TABLE CUSTOMER_EXTERNAL_COMPLIANCE (
|
`CREATE TABLE CUSTOMER_EXTERNAL_COMPLIANCE (
|
||||||
customer_id UUID NOT NULL REFERENCES customers(id),
|
customer_id UUID NOT NULL REFERENCES customers(id),
|
||||||
service TEXT NOT NULL,
|
service TEXT NOT NULL,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue