support sanctions compliance

This commit is contained in:
Josh Harvey 2018-05-03 20:18:13 +03:00
parent 80e851fb59
commit d904c8391e
5 changed files with 108 additions and 25 deletions

18
dev/ofac-match.js Normal file
View 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))

View file

@ -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)

View file

@ -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}

View file

@ -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)
} }
/** /**

View file

@ -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'
] ]