From 3dbf10183ea69fa28ae04e8fb52bdb6bc3380488 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 10 Apr 2024 11:34:36 +0100 Subject: [PATCH] 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",