feat: add button to check against OFAC sanction list
This commit is contained in:
parent
a29f3fc13c
commit
c77fda2623
11 changed files with 114 additions and 5 deletions
|
|
@ -114,6 +114,7 @@ function update (id, data, userToken) {
|
||||||
async function updateCustomer (id, data, userToken) {
|
async function updateCustomer (id, data, userToken) {
|
||||||
const formattedData = _.pick(
|
const formattedData = _.pick(
|
||||||
[
|
[
|
||||||
|
'sanctions',
|
||||||
'authorized_override',
|
'authorized_override',
|
||||||
'id_card_photo_override',
|
'id_card_photo_override',
|
||||||
'id_card_data_override',
|
'id_card_data_override',
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const { typeDefs, resolvers } = require('./graphql/schema')
|
||||||
const findOperatorId = require('../middlewares/operatorId')
|
const findOperatorId = require('../middlewares/operatorId')
|
||||||
const computeSchema = require('../compute-schema')
|
const computeSchema = require('../compute-schema')
|
||||||
const { USER_SESSIONS_CLEAR_INTERVAL } = require('../constants')
|
const { USER_SESSIONS_CLEAR_INTERVAL } = require('../constants')
|
||||||
const { session, cleanUserSessions, buildApolloContext } = require('./middlewares')
|
const { session, cleanUserSessions, buildApolloContext, loadSanctionLists } = require('./middlewares')
|
||||||
|
|
||||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||||
|
|
||||||
|
|
@ -48,6 +48,7 @@ app.use(express.static(path.resolve(__dirname, '..', '..', 'public')))
|
||||||
app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL))
|
app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL))
|
||||||
app.use(computeSchema)
|
app.use(computeSchema)
|
||||||
app.use(findOperatorId)
|
app.use(findOperatorId)
|
||||||
|
app.use(loadSanctionLists)
|
||||||
app.use(session)
|
app.use(session)
|
||||||
app.use(graphqlUploadExpress())
|
app.use(graphqlUploadExpress())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const machine = require('./machine.resolver')
|
||||||
const notification = require('./notification.resolver')
|
const notification = require('./notification.resolver')
|
||||||
const pairing = require('./pairing.resolver')
|
const pairing = require('./pairing.resolver')
|
||||||
const rates = require('./rates.resolver')
|
const rates = require('./rates.resolver')
|
||||||
|
const sanctions = require('./sanctions.resolver')
|
||||||
const scalar = require('./scalar.resolver')
|
const scalar = require('./scalar.resolver')
|
||||||
const settings = require('./settings.resolver')
|
const settings = require('./settings.resolver')
|
||||||
const sms = require('./sms.resolver')
|
const sms = require('./sms.resolver')
|
||||||
|
|
@ -37,6 +38,7 @@ const resolvers = [
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
sanctions,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
sms,
|
sms,
|
||||||
|
|
|
||||||
14
lib/new-admin/graphql/resolvers/sanctions.resolver.js
Normal file
14
lib/new-admin/graphql/resolvers/sanctions.resolver.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const ofac = require('../../../ofac')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
checkAgainstSanctions: (...[, { firstName, lastName, birthdate }]) => {
|
||||||
|
const ofacMatches = ofac.match({ firstName, lastName }, birthdate, { threshold: 0.85, fullNameThreshold: 0.95, debug: false })
|
||||||
|
|
||||||
|
return { ofacSanctioned: _.size(ofacMatches) > 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
|
|
@ -14,6 +14,7 @@ const machine = require('./machine.type')
|
||||||
const notification = require('./notification.type')
|
const notification = require('./notification.type')
|
||||||
const pairing = require('./pairing.type')
|
const pairing = require('./pairing.type')
|
||||||
const rates = require('./rates.type')
|
const rates = require('./rates.type')
|
||||||
|
const sanctions = require('./sanctions.type')
|
||||||
const scalar = require('./scalar.type')
|
const scalar = require('./scalar.type')
|
||||||
const settings = require('./settings.type')
|
const settings = require('./settings.type')
|
||||||
const sms = require('./sms.type')
|
const sms = require('./sms.type')
|
||||||
|
|
@ -37,6 +38,7 @@ const types = [
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
sanctions,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
sms,
|
sms,
|
||||||
|
|
|
||||||
13
lib/new-admin/graphql/types/sanctions.type.js
Normal file
13
lib/new-admin/graphql/types/sanctions.type.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type SanctionMatches {
|
||||||
|
ofacSanctioned: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
checkAgainstSanctions(firstName: String, lastName: String, birthdate: String): SanctionMatches @auth
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
const cleanUserSessions = require('./cleanUserSessions')
|
const cleanUserSessions = require('./cleanUserSessions')
|
||||||
const buildApolloContext = require('./context')
|
const buildApolloContext = require('./context')
|
||||||
|
const loadSanctionLists = require('./loadSanctionLists')
|
||||||
const session = require('./session')
|
const session = require('./session')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
cleanUserSessions,
|
cleanUserSessions,
|
||||||
buildApolloContext,
|
buildApolloContext,
|
||||||
|
loadSanctionLists,
|
||||||
session
|
session
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
lib/new-admin/middlewares/loadSanctionLists.js
Normal file
28
lib/new-admin/middlewares/loadSanctionLists.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
const logger = require('../../logger')
|
||||||
|
const sanctions = require('../../ofac')
|
||||||
|
|
||||||
|
const sanctionStatus = {
|
||||||
|
loaded: false,
|
||||||
|
timestamp: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSanctionLists = (req, res, next) => {
|
||||||
|
if (!sanctionStatus.loaded) {
|
||||||
|
logger.info('No sanction lists loaded. Loading sanctions...')
|
||||||
|
return sanctions.load()
|
||||||
|
.then(() => {
|
||||||
|
logger.info('OFAC sanction list loaded!')
|
||||||
|
sanctionStatus.loaded = true
|
||||||
|
sanctionStatus.timestamp = Date.now()
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
logger.error('Couldn\'t load OFAC sanction list!')
|
||||||
|
return next(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = loadSanctionLists
|
||||||
|
|
@ -73,7 +73,8 @@ const CustomerData = ({
|
||||||
authorizeCustomRequest,
|
authorizeCustomRequest,
|
||||||
updateCustomEntry,
|
updateCustomEntry,
|
||||||
retrieveAdditionalDataDialog,
|
retrieveAdditionalDataDialog,
|
||||||
setRetrieve
|
setRetrieve,
|
||||||
|
checkAgainstSanctions
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [listView, setListView] = useState(false)
|
const [listView, setListView] = useState(false)
|
||||||
|
|
@ -172,6 +173,14 @@ const CustomerData = ({
|
||||||
idCardData: R.merge(idData, formatDates(values))
|
idCardData: R.merge(idData, formatDates(values))
|
||||||
}),
|
}),
|
||||||
validationSchema: customerDataSchemas.idCardData,
|
validationSchema: customerDataSchemas.idCardData,
|
||||||
|
checkAgainstSanctions: () =>
|
||||||
|
checkAgainstSanctions({
|
||||||
|
variables: {
|
||||||
|
firstName: initialValues.idCardData.firstName,
|
||||||
|
lastName: initialValues.idCardData.lastName,
|
||||||
|
birthdate: R.replace(/-/g, '')(initialValues.idCardData.dateOfBirth)
|
||||||
|
}
|
||||||
|
}),
|
||||||
initialValues: initialValues.idCardData,
|
initialValues: initialValues.idCardData,
|
||||||
isAvailable: !R.isNil(idData),
|
isAvailable: !R.isNil(idData),
|
||||||
editable: true
|
editable: true
|
||||||
|
|
@ -434,7 +443,8 @@ const CustomerData = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
hasImage,
|
hasImage,
|
||||||
hasAdditionalData,
|
hasAdditionalData,
|
||||||
editable
|
editable,
|
||||||
|
checkAgainstSanctions
|
||||||
},
|
},
|
||||||
idx
|
idx
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -455,6 +465,7 @@ const CustomerData = ({
|
||||||
save={save}
|
save={save}
|
||||||
deleteEditedData={deleteEditedData}
|
deleteEditedData={deleteEditedData}
|
||||||
retrieveAdditionalData={retrieveAdditionalData}
|
retrieveAdditionalData={retrieveAdditionalData}
|
||||||
|
checkAgainstSanctions={checkAgainstSanctions}
|
||||||
editable={editable}></EditableCard>
|
editable={editable}></EditableCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery, useMutation, useLazyQuery } from '@apollo/react-hooks'
|
||||||
import {
|
import {
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
|
@ -292,6 +292,22 @@ const GET_ACTIVE_CUSTOM_REQUESTS = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const CHECK_AGAINST_SANCTIONS = gql`
|
||||||
|
query checkAgainstSanctions(
|
||||||
|
$firstName: String
|
||||||
|
$lastName: String
|
||||||
|
$birthdate: String
|
||||||
|
) {
|
||||||
|
checkAgainstSanctions(
|
||||||
|
firstName: $firstName
|
||||||
|
lastName: $lastName
|
||||||
|
birthdate: $birthdate
|
||||||
|
) {
|
||||||
|
ofacSanctioned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const CustomerProfile = memo(() => {
|
const CustomerProfile = memo(() => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
|
||||||
|
|
@ -400,6 +416,13 @@ const CustomerProfile = memo(() => {
|
||||||
onCompleted: () => getCustomer()
|
onCompleted: () => getCustomer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [checkAgainstSanctions] = useLazyQuery(CHECK_AGAINST_SANCTIONS, {
|
||||||
|
onCompleted: ({ checkAgainstSanctions: { ofacSanctioned } }) =>
|
||||||
|
updateCustomer({
|
||||||
|
sanctions: !ofacSanctioned
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const updateCustomer = it =>
|
const updateCustomer = it =>
|
||||||
setCustomer({
|
setCustomer({
|
||||||
variables: {
|
variables: {
|
||||||
|
|
@ -662,6 +685,7 @@ const CustomerProfile = memo(() => {
|
||||||
authorizeCustomRequest={authorizeCustomRequest}
|
authorizeCustomRequest={authorizeCustomRequest}
|
||||||
updateCustomEntry={updateCustomEntry}
|
updateCustomEntry={updateCustomEntry}
|
||||||
setRetrieve={setRetrieve}
|
setRetrieve={setRetrieve}
|
||||||
|
checkAgainstSanctions={checkAgainstSanctions}
|
||||||
retrieveAdditionalDataDialog={
|
retrieveAdditionalDataDialog={
|
||||||
<RetrieveDataDialog
|
<RetrieveDataDialog
|
||||||
onDismissed={() => {
|
onDismissed={() => {
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,8 @@ const EditableCard = ({
|
||||||
deleteEditedData,
|
deleteEditedData,
|
||||||
retrieveAdditionalData,
|
retrieveAdditionalData,
|
||||||
hasAdditionalData = true,
|
hasAdditionalData = true,
|
||||||
editable
|
editable,
|
||||||
|
checkAgainstSanctions
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -273,6 +274,16 @@ const EditableCard = ({
|
||||||
Retrieve API data
|
Retrieve API data
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
|
{checkAgainstSanctions && (
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
Icon={DataIcon}
|
||||||
|
InverseIcon={DataReversedIcon}
|
||||||
|
onClick={() => checkAgainstSanctions()}>
|
||||||
|
Check against OFAC sanction list
|
||||||
|
</ActionButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{editable && (
|
{editable && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue