support sanctions compliance
This commit is contained in:
parent
80e851fb59
commit
d904c8391e
5 changed files with 108 additions and 25 deletions
18
dev/ofac-match.js
Normal file
18
dev/ofac-match.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const compliance = require('../lib/compliance')
|
||||||
|
const ofac = require('../lib/ofac/index')
|
||||||
|
|
||||||
|
const [firstName, lastName, dateOfBirth] = process.argv.slice(2)
|
||||||
|
|
||||||
|
const customer = {
|
||||||
|
idCardData: {firstName, lastName, dateOfBirth}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
sanctionsVerificationActive: true
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('DEBUG100')
|
||||||
|
ofac.load()
|
||||||
|
.then(() => compliance.validateCustomer(config, customer))
|
||||||
|
.then(() => console.log('SUCCESS!'))
|
||||||
|
.catch(err => console.log(err))
|
||||||
|
|
@ -33,8 +33,9 @@ function run () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOnce () {
|
function runOnce () {
|
||||||
|
logger.info('Loading sanctions DB...')
|
||||||
return ofac.load()
|
return ofac.load()
|
||||||
.then(settingsLoader.loadLatest())
|
.then(settingsLoader.loadLatest)
|
||||||
.then(settings => {
|
.then(settings => {
|
||||||
poller.start(settings)
|
poller.start(settings)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,64 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const logger = require('./logger')
|
||||||
const ofac = require('./ofac/index')
|
const ofac = require('./ofac/index')
|
||||||
|
|
||||||
function matchOfac (customer) {
|
function matchOfac (customer) {
|
||||||
const nameParts = _.flatMap(_.split(/\s+/), [customer.firstName, customer.lastName])
|
// Probably because we haven't asked for ID yet
|
||||||
const birthDate = customer.dateOfBirth
|
if (!_.isPlainObject(customer.idCardData)) {
|
||||||
|
return true
|
||||||
const result = ofac.match(nameParts, birthDate)
|
|
||||||
console.log('DEBUG200: %s', result)
|
|
||||||
|
|
||||||
if (result > 0.8) throw new Error('Compliance error')
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateCustomer (config, customer) {
|
|
||||||
if (config.sanctionsVerificationActive) {
|
|
||||||
matchOfac(customer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return customer
|
const nameParts = {
|
||||||
|
firstName: customer.idCardData.firstName,
|
||||||
|
lastName: customer.idCardData.lastName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.some(_.isNil, _.values(nameParts))) {
|
||||||
|
logger.error(new Error(`Insufficient idCardData while matching OFAC for: ${customer.id}`))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDate = customer.idCardData.dateOfBirth
|
||||||
|
|
||||||
|
if (_.isNil(birthDate)) {
|
||||||
|
logger.error(new Error(`No birth date while matching OFAC for: ${customer.id}`))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
threshold: 0.85,
|
||||||
|
fullNameThreshold: 0.95,
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = ofac.match(nameParts, birthDate, options)
|
||||||
|
console.log('DEBUG200: %j', results)
|
||||||
|
|
||||||
|
return !_.isEmpty(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {validateCustomer}
|
function validateOfac (customer) {
|
||||||
|
if (customer.sanctionsOverride === 'blocked') return false
|
||||||
|
if (customer.sanctionsOverride === 'verified') return true
|
||||||
|
|
||||||
|
console.log('DEBUG400')
|
||||||
|
return !matchOfac(customer)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validationPatch (config, customer) {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const ofacValidation = validateOfac(customer)
|
||||||
|
|
||||||
|
console.log('DEBUG401: %s, %j', ofacValidation, customer)
|
||||||
|
|
||||||
|
if (_.isNil(customer.sanctions) || customer.sanctions !== ofacValidation) {
|
||||||
|
return {sanctions: ofacValidation}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {validationPatch}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ const NUM_RESULTS = 20
|
||||||
function add (customer) {
|
function add (customer) {
|
||||||
const sql = 'insert into customers (id, phone, phone_at) values ($1, $2, now()) returning *'
|
const sql = 'insert into customers (id, phone, phone_at) values ($1, $2, now()) returning *'
|
||||||
return db.one(sql, [uuid.v4(), customer.phone])
|
return db.one(sql, [uuid.v4(), customer.phone])
|
||||||
|
.then(populateOverrideUsernames)
|
||||||
|
.then(computeStatus)
|
||||||
|
.then(populateDailyVolume)
|
||||||
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const Tx = require('./tx')
|
||||||
const E = require('./error')
|
const E = require('./error')
|
||||||
const customers = require('./customers')
|
const customers = require('./customers')
|
||||||
const logs = require('./logs')
|
const logs = require('./logs')
|
||||||
|
const compliance = require('./compliance')
|
||||||
|
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
|
|
||||||
|
|
@ -164,17 +165,33 @@ function verifyTx (req, res, next) {
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOrUpdateCustomer (req) {
|
||||||
|
const customerData = req.body
|
||||||
|
const config = configManager.unscoped(req.settings.config)
|
||||||
|
|
||||||
|
return customers.get(customerData.phone)
|
||||||
|
.then(customer => {
|
||||||
|
if (customer) return customer
|
||||||
|
|
||||||
|
return customers.add(req.body)
|
||||||
|
})
|
||||||
|
.then(customer => {
|
||||||
|
return compliance.validationPatch(config, customer)
|
||||||
|
.then(patch => {
|
||||||
|
if (_.isEmpty(patch)) return customer
|
||||||
|
return customers.update(customer.id, patch)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getCustomerWithPhoneCode (req, res, next) {
|
function getCustomerWithPhoneCode (req, res, next) {
|
||||||
const pi = plugins(req.settings, req.deviceId)
|
const pi = plugins(req.settings, req.deviceId)
|
||||||
const phone = req.body.phone
|
const phone = req.body.phone
|
||||||
|
|
||||||
return pi.getPhoneCode(phone)
|
return pi.getPhoneCode(phone)
|
||||||
.then(code => {
|
.then(code => {
|
||||||
return customers.get(phone)
|
return addOrUpdateCustomer(req)
|
||||||
.then(customer => {
|
.then(customer => respond(req, res, {code, customer}))
|
||||||
if (customer) return respond(req, res, {code, customer})
|
|
||||||
return customers.add(req.body)
|
|
||||||
.then(customer => respond(req, res, {code, customer}))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
||||||
|
|
@ -191,9 +208,12 @@ function updateCustomer (req, res, next) {
|
||||||
customers.getById(id)
|
customers.getById(id)
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
if (!customer) { throw httpError('Not Found', 404) }
|
if (!customer) { throw httpError('Not Found', 404) }
|
||||||
return customers.update(id, patch)
|
|
||||||
|
const mergedCustomer = _.merge(customer, patch)
|
||||||
|
return compliance.validationPatch(config, mergedCustomer)
|
||||||
|
.then(_.merge(patch))
|
||||||
|
.then(newPatch => customers.update(id, newPatch))
|
||||||
})
|
})
|
||||||
.then(customer => customers.validate(config, customer))
|
|
||||||
.then(customer => respond(req, res, {customer}))
|
.then(customer => respond(req, res, {customer}))
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
@ -295,9 +315,8 @@ const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) &&
|
||||||
const configRequiredRoutes = [
|
const configRequiredRoutes = [
|
||||||
'/poll',
|
'/poll',
|
||||||
'/event',
|
'/event',
|
||||||
'/verify_user',
|
|
||||||
'/verify_transaction',
|
|
||||||
'/phone_code',
|
'/phone_code',
|
||||||
|
'/customer',
|
||||||
'/tx'
|
'/tx'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue