feat: new compliance options
This commit is contained in:
parent
ccf7eacfad
commit
f2080c32e9
23 changed files with 161 additions and 121 deletions
10
dev/get-compat-trigger.js
Normal file
10
dev/get-compat-trigger.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const complianceTriggers = require('../lib/compliance-triggers')
|
||||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const configManager = require('../lib/new-config-manager')
|
||||
|
||||
settingsLoader.loadLatest().then(settings => {
|
||||
const triggers = configManager.getTriggers(settings.config)
|
||||
const response = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
||||
console.log(response)
|
||||
})
|
||||
|
||||
|
|
@ -8,13 +8,9 @@ const customer = {
|
|||
idCardData: {firstName, lastName, dateOfBirth}
|
||||
}
|
||||
|
||||
const config = {
|
||||
sanctionsVerificationActive: true
|
||||
}
|
||||
|
||||
const deviceId = 'test-device'
|
||||
|
||||
ofac.load()
|
||||
.then(() => compliance.validationPatch(deviceId, config, customer))
|
||||
.then(() => compliance.validationPatch(deviceId, true, customer))
|
||||
.then(console.log)
|
||||
.catch(err => console.log(err))
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ function postProcess (r, pi, isBlacklisted, addressReuse) {
|
|||
settingsLoader.loadLatest().then(it => {
|
||||
// TODO new-admin: addressReuse doesnt exist
|
||||
// const config = configManager.unscoped(it.config)
|
||||
if (true) {
|
||||
if (false) {
|
||||
blacklist.addToUsedAddresses(r.tx.toAddress, r.tx.cryptoCode)
|
||||
.catch(err => logger.error('Failure adding to addressReuse', err))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const _ = require('lodash/fp')
|
||||
|
||||
function getBackwardsCompatibleTriggers (triggers) {
|
||||
const filtered = _.filter(_.matches({ triggerType: 'txAmount', cashDirection: 'both' }))(triggers)
|
||||
const filtered = _.filter(_.matches({ triggerType: 'txVolume', direction: 'both', thresholdDays: 1 }))(triggers)
|
||||
const grouped = _.groupBy(_.prop('requirement'))(filtered)
|
||||
return _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,10 @@ function matchOfac (deviceId, customer) {
|
|||
})
|
||||
}
|
||||
|
||||
function validateOfac (deviceId, config, customer) {
|
||||
if (!config.sanctionsVerificationActive) return Promise.resolve(true)
|
||||
// BACKWARDS_COMPATIBILITY 7.5
|
||||
// machines before 7.5 need to test sanctionsActive here
|
||||
function validateOfac (deviceId, sanctionsActive, customer) {
|
||||
if (!sanctionsActive) return Promise.resolve(true)
|
||||
if (customer.sanctionsOverride === 'blocked') return Promise.resolve(false)
|
||||
if (customer.sanctionsOverride === 'verified') return Promise.resolve(true)
|
||||
|
||||
|
|
@ -68,8 +70,8 @@ function validateOfac (deviceId, config, customer) {
|
|||
.then(didMatch => !didMatch)
|
||||
}
|
||||
|
||||
function validationPatch (deviceId, config, customer) {
|
||||
return validateOfac(deviceId, config, customer)
|
||||
function validationPatch (deviceId, sanctionsActive, customer) {
|
||||
return validateOfac(deviceId, sanctionsActive, customer)
|
||||
.then(ofacValidation => {
|
||||
if (_.isNil(customer.sanctions) || customer.sanctions !== ofacValidation) {
|
||||
return {sanctions: ofacValidation}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ const ph = require('./plugin-helper')
|
|||
function sendMessage (settings, rec) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// TODO new-admin
|
||||
// const pluginCode = configManager.unscoped(settings.config).email
|
||||
const pluginCode = 'mailgun'
|
||||
const plugin = ph.load(ph.EMAIL, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ const uuid = require('uuid')
|
|||
|
||||
const db = require('../db')
|
||||
|
||||
const NUM_RESULTS = 500
|
||||
|
||||
function getServerLogs (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
|
||||
const sql = `select id, log_level, timestamp, message from server_logs
|
||||
where timestamp >= $1 and timestamp <= $2
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ function plugins (settings, deviceId) {
|
|||
const notifications = configManager.getGlobalNotifications(settings.config)
|
||||
|
||||
const notificationsEnabled = notifications.sms.transactions || notifications.email.transactions
|
||||
const highValueTx = tx.fiat.gt(notifications.highValueTransaction)
|
||||
const highValueTx = tx.fiat.gt(notifications.highValueTransaction || Infinity)
|
||||
|
||||
if (!notificationsEnabled || !highValueTx) return Promise.resolve()
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ function poll (req, res, next) {
|
|||
const hasLightning = checkHasLightning(settings)
|
||||
|
||||
const triggers = configManager.getTriggers(settings.config)
|
||||
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
||||
|
||||
const operatorInfo = configManager.getOperatorInfo(settings.config)
|
||||
const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
|
||||
|
|
@ -91,18 +90,6 @@ function poll (req, res, next) {
|
|||
error: null,
|
||||
locale,
|
||||
version,
|
||||
smsVerificationActive: !!compatTriggers.sms,
|
||||
smsVerificationThreshold: compatTriggers.sms,
|
||||
hardLimitVerificationActive: !!compatTriggers.block,
|
||||
hardLimitVerificationThreshold: compatTriggers.block,
|
||||
idCardDataVerificationActive: !!compatTriggers.idData,
|
||||
idCardDataVerificationThreshold: compatTriggers.idData,
|
||||
idCardPhotoVerificationActive: !!compatTriggers.idPhoto,
|
||||
idCardPhotoVerificationThreshold: compatTriggers.idPhoto,
|
||||
sanctionsVerificationActive: !!compatTriggers.sancations,
|
||||
sanctionsVerificationThreshold: compatTriggers.sancations,
|
||||
frontCameraVerificationActive: !!compatTriggers.facephoto,
|
||||
frontCameraVerificationThreshold: compatTriggers.facephoto,
|
||||
receiptPrintingActive: receipt.active === "on",
|
||||
cassettes,
|
||||
twoWayMode: cashOutConfig.active,
|
||||
|
|
@ -111,7 +98,24 @@ function poll (req, res, next) {
|
|||
restartServices,
|
||||
hasLightning,
|
||||
receipt,
|
||||
operatorInfo
|
||||
operatorInfo,
|
||||
triggers
|
||||
}
|
||||
|
||||
// BACKWARDS_COMPATIBILITY 7.5
|
||||
// machines before 7.5 expect old compliance
|
||||
if (!machineVersion || semver.lt(machineVersion, '7.5.0-beta.0')) {
|
||||
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
||||
response.smsVerificationActive = !!compatTriggers.sms
|
||||
response.smsVerificationThreshold = compatTriggers.sms
|
||||
response.idCardDataVerificationActive = !!compatTriggers.idData
|
||||
response.idCardDataVerificationThreshold = compatTriggers.idData
|
||||
response.idCardPhotoVerificationActive = !!compatTriggers.idPhoto
|
||||
response.idCardPhotoVerificationThreshold = compatTriggers.idPhoto
|
||||
response.sanctionsVerificationActive = !!compatTriggers.sancations
|
||||
response.sanctionsVerificationThreshold = compatTriggers.sancations
|
||||
response.frontCameraVerificationActive = !!compatTriggers.facephoto
|
||||
response.frontCameraVerificationThreshold = compatTriggers.facephoto
|
||||
}
|
||||
|
||||
// BACKWARDS_COMPATIBILITY 7.4.9
|
||||
|
|
@ -208,6 +212,7 @@ function verifyTx (req, res, next) {
|
|||
|
||||
function addOrUpdateCustomer (req) {
|
||||
const customerData = req.body
|
||||
const machineVersion = req.query.version
|
||||
const triggers = configManager.getTriggers(req.settings.config)
|
||||
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
||||
|
||||
|
|
@ -218,11 +223,24 @@ function addOrUpdateCustomer (req) {
|
|||
return customers.add(req.body)
|
||||
})
|
||||
.then(customer => {
|
||||
// BACKWARDS_COMPATIBILITY 7.5
|
||||
// machines before 7.5 expect customer with sanctions result
|
||||
const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0')
|
||||
const shouldRunOfacCompat = !compatTriggers.sanctions && isOlderMachineVersion
|
||||
if (!shouldRunOfacCompat) return customer
|
||||
|
||||
return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, customer)
|
||||
.then(patch => {
|
||||
if (_.isEmpty(patch)) return customer
|
||||
return customers.update(customer.id, patch)
|
||||
})
|
||||
}).then(customer => {
|
||||
// TODO new-admin: only get customer history till max needed for triggers
|
||||
return Tx.customerHistory(customer.id)
|
||||
.then(result => {
|
||||
customer.txHistory = result
|
||||
return customer
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +262,7 @@ function getCustomerWithPhoneCode (req, res, next) {
|
|||
|
||||
function updateCustomer (req, res, next) {
|
||||
const id = req.params.id
|
||||
const machineVersion = req.query.version
|
||||
const txId = req.query.txId
|
||||
const patch = req.body
|
||||
const triggers = configManager.getTriggers(req.settings.config)
|
||||
|
|
@ -254,16 +273,57 @@ function updateCustomer (req, res, next) {
|
|||
if (!customer) { throw httpError('Not Found', 404) }
|
||||
|
||||
const mergedCustomer = _.merge(customer, patch)
|
||||
return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, mergedCustomer)
|
||||
|
||||
// BACKWARDS_COMPATIBILITY 7.5
|
||||
// machines before 7.5 expect customer with sanctions result
|
||||
const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0')
|
||||
|
||||
Promise.resolve({})
|
||||
.then(emptyObj => {
|
||||
if (!isOlderMachineVersion) return Promise.resolve(emptyObj)
|
||||
return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, mergedCustomer)
|
||||
})
|
||||
.then(_.merge(patch))
|
||||
.then(newPatch => customers.updatePhotoCard(id, newPatch))
|
||||
.then(newPatch => customers.updateFrontCamera(id, newPatch))
|
||||
.then(newPatch => customers.update(id, newPatch, null, txId))
|
||||
|
||||
})
|
||||
.then(customer => respond(req, res, { customer }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function triggerSanctions (req, res, next) {
|
||||
const id = req.params.id
|
||||
|
||||
customers.getById(id)
|
||||
.then(customer => {
|
||||
if (!customer) { throw httpError('Not Found', 404) }
|
||||
|
||||
return compliance.validationPatch(req.deviceId, true, customer)
|
||||
.then(patch => customers.update(id, patch))
|
||||
|
||||
})
|
||||
.then(customer => respond(req, res, { customer }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function triggerBlock (req, res, next) {
|
||||
const id = req.params.id
|
||||
|
||||
customers.update(id, { authorizedOverride: 'blocked' })
|
||||
.then(customer => respond(req, res, { customer }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function triggerSuspend (req, res, next) {
|
||||
const id = req.params.id
|
||||
|
||||
customers.update(id, { authorizedOverride: 'blocked' })
|
||||
.then(customer => respond(req, res, { customer }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function getLastSeen (req, res, next) {
|
||||
const deviceId = req.deviceId
|
||||
const timestamp = Date.now()
|
||||
|
|
@ -409,6 +469,9 @@ app.post('/verify_transaction', verifyTx)
|
|||
|
||||
app.post('/phone_code', getCustomerWithPhoneCode)
|
||||
app.patch('/customer/:id', updateCustomer)
|
||||
app.patch('/customer/:id/sanctions', triggerSanctions)
|
||||
app.patch('/customer/:id/block', triggerBlock)
|
||||
app.patch('/customer/:id/suspend', triggerSuspend)
|
||||
|
||||
app.post('/tx', postTx)
|
||||
app.get('/tx/:id', getTx)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
// const configManager = require('./config-manager')
|
||||
const ph = require('./plugin-helper')
|
||||
const argv = require('minimist')(process.argv.slice(2))
|
||||
|
||||
function sendMessage (settings, rec) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
// TODO new-admin: how to load mock here? Only on dev?
|
||||
// const pluginCode = configManager.unscoped(settings.config).sms
|
||||
const pluginCode = 'twilio'
|
||||
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
|
||||
const plugin = ph.load(ph.SMS, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
|
||||
|
|
|
|||
14
lib/tx.js
14
lib/tx.js
|
|
@ -1,4 +1,5 @@
|
|||
const _ = require('lodash/fp')
|
||||
const db = require('./db')
|
||||
const BN = require('./bn')
|
||||
const CashInTx = require('./cash-in/cash-in-tx')
|
||||
const CashOutTx = require('./cash-out/cash-out-tx')
|
||||
|
|
@ -63,4 +64,15 @@ function cancel (txId) {
|
|||
})
|
||||
}
|
||||
|
||||
module.exports = {post, cancel}
|
||||
function customerHistory (customerId) {
|
||||
const sql = ` select txIn.id, txIn.created, txIn.fiat, 'cashIn' as direction from cash_in_txs txIn
|
||||
where txIn.customer_id = $1
|
||||
union
|
||||
select txOut.id, txOut.created, txOut.fiat, 'cashOut' as direction from cash_out_txs txOut
|
||||
where txOut.customer_id = $1
|
||||
order by created;`
|
||||
|
||||
return db.any(sql, [customerId])
|
||||
}
|
||||
|
||||
module.exports = {post, cancel, customerHistory}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function getWalletStatus (settings, tx) {
|
|||
|
||||
function authorizeZeroConf (settings, tx, machineId) {
|
||||
const plugin = configManager.getWalletSettings(tx.cryptoCode, settings.config).zeroConf
|
||||
const cashOutConfig = configManager.cashOutConfig(machineId, settings.config)
|
||||
const cashOutConfig = configManager.getCashOut(machineId, settings.config)
|
||||
const zeroConfLimit = cashOutConfig.zeroConfLimit
|
||||
|
||||
if (!_.isObject(tx.fiat)) {
|
||||
|
|
|
|||
|
|
@ -71,9 +71,12 @@ export default {
|
|||
borderRadius: 8,
|
||||
backgroundColor: zircon,
|
||||
margin: [[0, 28, 0, 0]],
|
||||
padding: [[30]],
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
justifyContent: 'center',
|
||||
display: 'flex'
|
||||
},
|
||||
img: {
|
||||
width: 80
|
||||
},
|
||||
customerName: {
|
||||
marginBottom: 32
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ const FrontCameraPhoto = memo(({ frontCameraPath }) => {
|
|||
return (
|
||||
<Paper className={classes.photo} elevation={0}>
|
||||
{frontCameraPath ? (
|
||||
<img src={`${URI}/front-camera-photo/${frontCameraPath}`} alt="" />
|
||||
<img
|
||||
className={classes.img}
|
||||
src={`${URI}/front-camera-photo/${frontCameraPath}`}
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<CrossedCameraIcon />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const Cashboxes = () => {
|
|||
const [resetCashOut] = useMutation(RESET_CASHOUT_BILLS, {
|
||||
onError: ({ graphQLErrors, message }) => {
|
||||
const errorMessage = graphQLErrors[0] ? graphQLErrors[0].message : message
|
||||
// TODO: this should not be final
|
||||
// TODO new-admin : this should not be final
|
||||
alert(JSON.stringify(errorMessage))
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ const MachineDetailsRow = ({ it: machine }) => {
|
|||
<Label>Lamassu Support article</Label>
|
||||
<div>
|
||||
{machine.statuses.map((...[, index]) => (
|
||||
// TODO support articles
|
||||
// TODO new-admin: support articles
|
||||
<span key={index}></span>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,15 +60,17 @@ const formatDate = date => {
|
|||
return moment(date).format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
const NUM_LOG_RESULTS = 1000
|
||||
|
||||
const GET_DATA = gql`
|
||||
{
|
||||
query ServerData($limit: Int) {
|
||||
serverVersion
|
||||
uptime {
|
||||
name
|
||||
state
|
||||
uptime
|
||||
}
|
||||
serverLogs {
|
||||
serverLogs(limit: $limit) {
|
||||
logLevel
|
||||
id
|
||||
timestamp
|
||||
|
|
@ -93,7 +95,10 @@ const Logs = () => {
|
|||
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
||||
|
||||
const { data } = useQuery(GET_DATA, {
|
||||
onCompleted: () => setSaveMessage('')
|
||||
onCompleted: () => setSaveMessage(''),
|
||||
variables: {
|
||||
limit: NUM_LOG_RESULTS
|
||||
}
|
||||
})
|
||||
|
||||
const serverVersion = data?.serverVersion
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Button } from 'src/components/buttons'
|
|||
import { H5, Info3 } from 'src/components/typography'
|
||||
import { comet } from 'src/styling/variables'
|
||||
|
||||
import { direction, type, requirements } from './helper'
|
||||
import { txDirection, type, requirements } from './helper'
|
||||
|
||||
const LAST_STEP = 3
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ const useStyles = makeStyles(styles)
|
|||
const getStep = step => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return direction
|
||||
return txDirection
|
||||
case 2:
|
||||
return type
|
||||
case 3:
|
||||
|
|
@ -72,7 +72,7 @@ const orUnderline = value => {
|
|||
}
|
||||
|
||||
const getDirectionText = config => {
|
||||
switch (config.cashDirection) {
|
||||
switch (config.direction) {
|
||||
case 'both':
|
||||
return 'both cash-in and cash-out'
|
||||
case 'cashIn':
|
||||
|
|
@ -111,9 +111,9 @@ const getRequirementText = config => {
|
|||
switch (config.requirement?.requirement) {
|
||||
case 'sms':
|
||||
return 'asked to enter code provided through SMS verification'
|
||||
case 'idPhoto':
|
||||
case 'idCardPhoto':
|
||||
return 'asked to scan a ID with photo'
|
||||
case 'idData':
|
||||
case 'idCardData':
|
||||
return 'asked to scan a ID'
|
||||
case 'facephoto':
|
||||
return 'asked to have a photo taken'
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const useStyles = makeStyles({
|
|||
}
|
||||
})
|
||||
|
||||
const cashDirection = Yup.string().required('Required')
|
||||
const direction = Yup.string().required('Required')
|
||||
const triggerType = Yup.string().required('Required')
|
||||
const threshold = Yup.object().shape({
|
||||
threshold: Yup.number(),
|
||||
|
|
@ -83,11 +83,11 @@ const Schema = Yup.object().shape({
|
|||
triggerType,
|
||||
requirement,
|
||||
threshold,
|
||||
cashDirection
|
||||
direction
|
||||
})
|
||||
|
||||
// Direction
|
||||
const directionSchema = Yup.object().shape({ cashDirection })
|
||||
const directionSchema = Yup.object().shape({ direction })
|
||||
|
||||
const directionOptions = [
|
||||
{
|
||||
|
|
@ -143,7 +143,7 @@ const Direction = () => {
|
|||
const { errors } = useFormikContext()
|
||||
|
||||
const titleClass = {
|
||||
[classes.error]: errors.cashDirection
|
||||
[classes.error]: errors.direction
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -155,7 +155,7 @@ const Direction = () => {
|
|||
</Box>
|
||||
<Field
|
||||
component={RadioGroup}
|
||||
name="cashDirection"
|
||||
name="direction"
|
||||
options={directionOptions}
|
||||
labelClassName={classes.radioLabel}
|
||||
radioClassName={classes.radio}
|
||||
|
|
@ -165,11 +165,11 @@ const Direction = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const direction = {
|
||||
const txDirection = {
|
||||
schema: directionSchema,
|
||||
options: directionOptions,
|
||||
Component: Direction,
|
||||
initialValues: { cashDirection: '' }
|
||||
initialValues: { direction: '' }
|
||||
}
|
||||
|
||||
// TYPE
|
||||
|
|
@ -257,11 +257,11 @@ const requirementSchema = Yup.object().shape({
|
|||
|
||||
const requirementOptions = [
|
||||
{ display: 'SMS verification', code: 'sms' },
|
||||
{ display: 'ID card image', code: 'idPhoto' },
|
||||
{ display: 'ID data', code: 'idData' },
|
||||
{ display: 'ID card image', code: 'idCardPhoto' },
|
||||
{ display: 'ID data', code: 'idCardData' },
|
||||
{ display: 'Customer camera', code: 'facephoto' },
|
||||
{ display: 'Sanctions', code: 'sanctions' },
|
||||
{ display: 'Super user', code: 'superuser' },
|
||||
// { display: 'Super user', code: 'superuser' },
|
||||
{ display: 'Suspend', code: 'suspend' },
|
||||
{ display: 'Block', code: 'block' }
|
||||
]
|
||||
|
|
@ -506,7 +506,7 @@ const getElements = (currency, classes) => [
|
|||
view: (it, config) => <ThresholdView config={config} currency={currency} />
|
||||
},
|
||||
{
|
||||
name: 'cashDirection',
|
||||
name: 'direction',
|
||||
size: 'sm',
|
||||
width: 282,
|
||||
view: it => <DirectionDisplay code={it} />,
|
||||
|
|
@ -554,7 +554,7 @@ const toServer = triggers =>
|
|||
export {
|
||||
Schema,
|
||||
getElements,
|
||||
direction,
|
||||
txDirection,
|
||||
type,
|
||||
requirements,
|
||||
sortBy,
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
Overall:
|
||||
- caching the page
|
||||
- transitions
|
||||
- error handling
|
||||
- validation is bad rn, negatives being allowed
|
||||
- locale based mil separators 1.000 1,000
|
||||
- Table should be loaded on slow internet (we want to load the table with no data)
|
||||
- tooltip like components should close on esc
|
||||
- saving should be a one time thing. disable buttons so user doesnt spam it
|
||||
- disable edit on non-everrides => overrides
|
||||
- splash screens and home
|
||||
- maybe a indication that there's more to search on dropdown
|
||||
- required signifier on form fields - (required) or *
|
||||
- stop line breaking on multi select
|
||||
- input width should be enough to hold values without cutting text
|
||||
|
||||
Locale:
|
||||
- Only allow one override per machine
|
||||
|
||||
Notifications:
|
||||
- one of the crypto balance alerts has to be optional because of migration
|
||||
|
||||
Server:
|
||||
- Takes too long to load. Investigate
|
||||
|
||||
Operator Info:
|
||||
- That should be paginated with routes!
|
||||
|
||||
CoinATMRadar:
|
||||
- relay facephoto info
|
||||
- we should show the highest amount that per requirement
|
||||
|
||||
Customers:
|
||||
- add status
|
||||
|
||||
Machine name:
|
||||
- update the db with whatever name is on the old config (check 1509439657189-add_machine_name_to_devices)
|
||||
|
||||
Compliance:
|
||||
- Currently admin only handles { type: 'amount', direction: 'both' }
|
||||
- Sanctions should have more care in customers.js, currently just looking if is active as if old config
|
||||
|
||||
Ideas
|
||||
- Transactions could have a link to the customer
|
||||
- Transactions table on customer should have a link to "transactions"
|
||||
|
||||
|
||||
Feedback needed
|
||||
- font sizes could be better (I've bumped all font sizes by 1px, looks pretty good as fonts do a good vertical bump in size. Maybe some of the fonts don't like even values)
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -2589,7 +2589,7 @@
|
|||
},
|
||||
"blake2b": {
|
||||
"version": "git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac",
|
||||
"from": "blake2b@git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac",
|
||||
"from": "git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac",
|
||||
"requires": {
|
||||
"blake2b-wasm": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b",
|
||||
"nanoassert": "^1.0.0"
|
||||
|
|
@ -2597,7 +2597,7 @@
|
|||
},
|
||||
"blake2b-wasm": {
|
||||
"version": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b",
|
||||
"from": "blake2b-wasm@git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b",
|
||||
"from": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b",
|
||||
"requires": {
|
||||
"nanoassert": "^1.0.0"
|
||||
}
|
||||
|
|
@ -5623,7 +5623,7 @@
|
|||
"got": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
|
||||
"integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=",
|
||||
"integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==",
|
||||
"requires": {
|
||||
"decompress-response": "^3.2.0",
|
||||
"duplexer3": "^0.1.4",
|
||||
|
|
@ -6947,7 +6947,7 @@
|
|||
"isurl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
|
||||
"integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=",
|
||||
"integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==",
|
||||
"requires": {
|
||||
"has-to-string-tag-x": "^1.2.0",
|
||||
"is-object": "^1.0.1"
|
||||
|
|
@ -7792,7 +7792,7 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
|
@ -8015,7 +8015,7 @@
|
|||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
"integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
|
||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
|
|
@ -8404,7 +8404,7 @@
|
|||
"p-cancelable": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
|
||||
"integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo="
|
||||
"integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw=="
|
||||
},
|
||||
"p-defer": {
|
||||
"version": "1.0.0",
|
||||
|
|
@ -11899,7 +11899,7 @@
|
|||
"dependencies": {
|
||||
"bignumber.js": {
|
||||
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
|
||||
"from": "bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934"
|
||||
"from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
2
test/fixtures/new-settings.json
vendored
2
test/fixtures/new-settings.json
vendored
|
|
@ -3,7 +3,7 @@
|
|||
"triggers": [
|
||||
{
|
||||
"id": "ce8f240f-7235-4948-b833-4af56ccfa90f",
|
||||
"cashDirection": "cash-out",
|
||||
"direction": "cash-out",
|
||||
"triggerType": "volume",
|
||||
"threshold": "12",
|
||||
"requirement": "block"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const config = settings.config
|
|||
|
||||
test('first examples', () => {
|
||||
const triggers = configManager.getTriggers(config)
|
||||
const filtered = _.filter(_.matches({ triggerType: 'volume', cashDirection: 'both' }))(triggers)
|
||||
const filtered = _.filter(_.matches({ triggerType: 'volume', direction: 'both' }))(triggers)
|
||||
const grouped = _.groupBy(_.prop('requirement'))(filtered)
|
||||
const final = _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue