From f144c8a7cbd1cb2bae4afd32ed91b7699a68d03a Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 27 Mar 2024 14:14:20 +0000 Subject: [PATCH 1/4] refactor: convert function to single-expression arrow function --- lib/compliance.js | 12 +++++------- .../Customers/components/CustomInfoRequestsData.js | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/compliance.js b/lib/compliance.js index 22562587..8ecd1770 100644 --- a/lib/compliance.js +++ b/lib/compliance.js @@ -72,13 +72,11 @@ function validateOfac (deviceId, sanctionsActive, customer) { function validationPatch (deviceId, sanctionsActive, customer) { return validateOfac(deviceId, sanctionsActive, customer) - .then(ofacValidation => { - if (_.isNil(customer.sanctions) || customer.sanctions !== ofacValidation) { - return {sanctions: ofacValidation} - } - - return {} - }) + .then(sactions => + _.isNil(customer.sanctions) || customer.sanctions !== sactions ? + { sanctions } : + {} + ) } module.exports = {validationPatch} diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js b/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js index d8ca647d..635b1c78 100644 --- a/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js +++ b/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js @@ -130,13 +130,12 @@ const CustomInfoRequestsData = ({ data }) => { ) } - const getAuthorizedStatus = it => { - return it.approved === null + const getAuthorizedStatus = it => + it.approved === null ? { label: 'Pending', type: 'neutral' } : it.approved === false ? { label: 'Rejected', type: 'error' } : { label: 'Accepted', type: 'success' } - } return ( <> From 08843ec73a0eee4064feda0a751d2c3bbbbcae35 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 27 Mar 2024 14:19:19 +0000 Subject: [PATCH 2/4] refactor: make `deviceId` its own variable --- lib/routes/customerRoutes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 963cb1f1..4f205232 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -43,6 +43,7 @@ function updateCustomer (req, res, next) { const patch = req.body const triggers = configManager.getTriggers(req.settings.config) const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + const deviceId = req.deviceId if (patch.customRequestPatch) { return updateCustomerCustomInfoRequest(id, patch.customRequestPatch, req, res).catch(next) @@ -61,7 +62,7 @@ function updateCustomer (req, res, next) { return Promise.resolve({}) .then(emptyObj => { if (!isOlderMachineVersion) return Promise.resolve(emptyObj) - return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, mergedCustomer) + return compliance.validationPatch(deviceId, !!compatTriggers.sanctions, mergedCustomer) }) .then(_.merge(patch)) .then(newPatch => customers.updatePhotoCard(id, newPatch)) From 068f68e838c27429443089510b6484bee3c4b796 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 27 Mar 2024 14:27:00 +0000 Subject: [PATCH 3/4] refactor: give settings object to `complianceNotify` --- lib/notifier/index.js | 9 +++------ lib/routes/customerRoutes.js | 6 ++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/notifier/index.js b/lib/notifier/index.js index ab75d84e..a70c01b1 100644 --- a/lib/notifier/index.js +++ b/lib/notifier/index.js @@ -166,12 +166,9 @@ function transactionNotify (tx, rec) { }) } -function complianceNotify (customer, deviceId, action, period) { - return Promise.all([ - settingsLoader.loadLatest(), - queries.getMachineName(deviceId) - ]) - .then(([settings, machineName]) => { +function complianceNotify (settings, customer, deviceId, action, period) { + return queries.getMachineName(deviceId) + .then(machineName => { const notifications = configManager.getGlobalNotifications(settings.config) const msgCore = { diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 4f205232..0af25570 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -101,10 +101,11 @@ function triggerSanctions (req, res, next) { function triggerBlock (req, res, next) { const id = req.params.id + const settings = req.settings customers.update(id, { authorizedOverride: 'blocked' }) .then(customer => { - notifier.complianceNotify(customer, req.deviceId, 'BLOCKED') + notifier.complianceNotify(settings, customer, req.deviceId, 'BLOCKED') return respond(req, res, { customer }) }) .catch(next) @@ -113,6 +114,7 @@ function triggerBlock (req, res, next) { function triggerSuspend (req, res, next) { const id = req.params.id const triggerId = req.body.triggerId + const settings = req.settings const triggers = configManager.getTriggers(req.settings.config) const getSuspendDays = _.compose(_.get('suspensionDays'), _.find(_.matches({ id: triggerId }))) @@ -123,7 +125,7 @@ function triggerSuspend (req, res, next) { customers.update(id, { suspendedUntil: add(suspensionDuration, new Date()) }) .then(customer => { - notifier.complianceNotify(customer, req.deviceId, 'SUSPENDED', days) + notifier.complianceNotify(settings, customer, req.deviceId, 'SUSPENDED', days) return respond(req, res, { customer }) }) .catch(next) From 3dbf10183ea69fa28ae04e8fb52bdb6bc3380488 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 10 Apr 2024 11:34:36 +0100 Subject: [PATCH 4/4] feat: add pending manual compliance notifications --- lib/notifier/index.js | 3 +- lib/notifier/notificationCenter.js | 4 +- lib/routes/customerRoutes.js | 94 ++++++++++++++------ package-lock.json | 132 ++++++++++++++++++++++------- 4 files changed, 175 insertions(+), 58 deletions(-) diff --git a/lib/notifier/index.js b/lib/notifier/index.js index a70c01b1..06163030 100644 --- a/lib/notifier/index.js +++ b/lib/notifier/index.js @@ -173,7 +173,8 @@ function complianceNotify (settings, customer, deviceId, action, period) { const msgCore = { BLOCKED: `was blocked`, - SUSPENDED: `was suspended for ${!!period && period} days` + SUSPENDED: `was suspended for ${!!period && period} days`, + PENDING_COMPLIANCE: `is waiting for your manual approval`, } const rec = { diff --git a/lib/notifier/notificationCenter.js b/lib/notifier/notificationCenter.js index c5ac36fd..11392248 100644 --- a/lib/notifier/notificationCenter.js +++ b/lib/notifier/notificationCenter.js @@ -38,7 +38,9 @@ const customerComplianceNotify = (customer, deviceId, code, days = null) => { if (days) { date.setDate(date.getDate() + days) } - const message = code === 'SUSPENDED' ? `Customer suspended until ${date.toLocaleString()}` : `Customer blocked` + const message = code === 'SUSPENDED' ? `Customer ${customer.phone} suspended until ${date.toLocaleString()}` : + code === 'BLOCKED' ? `Customer ${customer.phone} blocked` : + `Customer ${customer.phone} has pending compliance` return clearOldCustomerSuspendedNotifications(customer.id, deviceId) .then(() => queries.getValidNotifications(COMPLIANCE, detailB)) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 0af25570..d0e11431 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -4,6 +4,7 @@ const semver = require('semver') const _ = require('lodash/fp') const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz/fp') const { add, intervalToDuration } = require('date-fns/fp') +const uuid = require('uuid') const sms = require('../sms') const BN = require('../bn') @@ -25,15 +26,54 @@ const Tx = require('../tx') const loyalty = require('../loyalty') const logger = require('../logger') -function updateCustomerCustomInfoRequest (customerId, patch, req, res) { - if (_.isNil(patch.data)) { - return customers.getById(customerId) - .then(customer => respond(req, res, { customer })) +function updateCustomerCustomInfoRequest (customerId, patch) { + const promise = _.isNil(patch.data) ? + Promise.resolve(null) : + customInfoRequestQueries.setCustomerDataViaMachine(customerId, patch.infoRequestId, patch) + return promise.then(() => customers.getById(customerId)) +} + +const createPendingManualComplianceNotifs = (settings, customer, deviceId) => { + const customInfoRequests = _.reduce( + (reqs, req) => _.set(req.info_request_id, req, reqs), + {}, + _.get(['customInfoRequestData'], customer) + ) + + const isPending = field => + uuid.validate(field) ? + _.get([field, 'override'], customInfoRequests) === 'automatic' : + customer[`${field}At`] + && (!customer[`${field}OverrideAt`] + || customer[`${field}OverrideAt`].getTime() < customer[`${field}At`].getTime()) + + const unnestCustomTriggers = triggersAutomation => { + const customTriggers = _.fromPairs(_.map(({ id, type }) => [id, type], triggersAutomation.custom)) + return _.flow( + _.unset('custom'), + _.mapKeys(k => k === 'facephoto' ? 'frontCamera' : k), + _.assign(customTriggers), + )(triggersAutomation) } - return customInfoRequestQueries.setCustomerDataViaMachine(customerId, patch.infoRequestId, patch) - .then(() => customers.getById(customerId)) - .then(customer => respond(req, res, { customer })) + const isManual = v => v === 'Manual' + + const hasManualAutomation = triggersAutomation => + _.any(isManual, _.values(triggersAutomation)) + + configManager.getTriggersAutomation(customInfoRequestQueries.getCustomInfoRequests(true), settings.config) + .then(triggersAutomation => { + triggersAutomation = unnestCustomTriggers(triggersAutomation) + if (!hasManualAutomation(triggersAutomation)) return + + const pendingFields = _.filter( + field => isManual(triggersAutomation[field]) && isPending(field), + _.keys(triggersAutomation) + ) + + if (!_.isEmpty(pendingFields)) + notifier.complianceNotify(settings, customer, deviceId, 'PENDING_COMPLIANCE') + }) } function updateCustomer (req, res, next) { @@ -44,32 +84,34 @@ function updateCustomer (req, res, next) { const triggers = configManager.getTriggers(req.settings.config) const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) const deviceId = req.deviceId + const settings = req.settings if (patch.customRequestPatch) { - return updateCustomerCustomInfoRequest(id, patch.customRequestPatch, req, res).catch(next) + return updateCustomerCustomInfoRequest(id, patch.customRequestPatch) + .then(customer => { + createPendingManualComplianceNotifs(settings, customer, deviceId) + respond(req, res, { customer }) + }) + .catch(next) } + // BACKWARDS_COMPATIBILITY 7.5 + // machines before 7.5 expect customer with sanctions result + const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0') customers.getById(id) + .then(customer => + !customer ? Promise.reject(httpError('Not Found', 404)) : + !isOlderMachineVersion ? {} : + compliance.validationPatch(deviceId, !!compatTriggers.sanctions, _.merge(customer, patch)) + ) + .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 => { - if (!customer) { throw httpError('Not Found', 404) } - - const mergedCustomer = _.merge(customer, patch) - - // BACKWARDS_COMPATIBILITY 7.5 - // machines before 7.5 expect customer with sanctions result - const isOlderMachineVersion = !machineVersion || semver.lt(machineVersion, '7.5.0-beta.0') - - return Promise.resolve({}) - .then(emptyObj => { - if (!isOlderMachineVersion) return Promise.resolve(emptyObj) - return compliance.validationPatch(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)) + createPendingManualComplianceNotifs(settings, customer, deviceId) + respond(req, res, { customer }) }) - .then(customer => respond(req, res, { customer })) .catch(next) } diff --git a/package-lock.json b/package-lock.json index db9bfec8..fd068b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -770,6 +770,36 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, + "bitcoinjs-message": { + "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", + "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", + "requires": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "5.0.0", + "varuint-bitcoin": "^1.0.1" + }, + "dependencies": { + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + } + } + } + }, "ethereumjs-util": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", @@ -960,6 +990,38 @@ "secp256k1": "^4.0.2", "secrets.js-grempe": "^1.1.0", "superagent": "3.8.3" + }, + "dependencies": { + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "bitcoinjs-message": { + "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", + "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", + "requires": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "5.0.0", + "varuint-bitcoin": "^1.0.1" + }, + "dependencies": { + "secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + } + } + } + } } }, "@bitgo/sdk-coin-bch": { @@ -1058,6 +1120,26 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, + "bitcoinjs-message": { + "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", + "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", + "requires": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "5.0.0", + "varuint-bitcoin": "^1.0.1" + }, + "dependencies": { + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + } + } + }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -1278,6 +1360,26 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, + "bitcoinjs-message": { + "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", + "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", + "requires": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "5.0.0", + "varuint-bitcoin": "^1.0.1" + }, + "dependencies": { + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + } + } + }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -4629,36 +4731,6 @@ "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" }, - "bitcoinjs-message": { - "version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2", - "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz", - "integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==", - "requires": { - "bech32": "^1.1.3", - "bs58check": "^2.1.2", - "buffer-equals": "^1.0.3", - "create-hash": "^1.1.2", - "secp256k1": "5.0.0", - "varuint-bitcoin": "^1.0.1" - }, - "dependencies": { - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "secp256k1": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", - "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^5.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - }, "bitcore-lib": { "version": "8.25.47", "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.25.47.tgz",