From 938ca641315ba77f786e9fbe0ee83cb0bb1a19bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 9 Aug 2021 00:46:35 +0100 Subject: [PATCH 1/6] feat: toggle for sms receipt --- .../src/pages/OperatorInfo/ReceiptPrinting.js | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting.js b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting.js index 907700c1..dd3f9d19 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting.js @@ -49,7 +49,7 @@ const ReceiptPrinting = memo(({ wizard }) => {

Receipt options

-

Enable receipt printing?

+

Enable receipt printing

{ {receiptPrintingConfig.active ? 'Yes' : 'No'}
+
+

Offer SMS receipt

+
+ + saveConfig({ + variables: { + config: toNamespace( + namespaces.RECEIPT, + R.merge(receiptPrintingConfig, { + sms: event.target.checked + }) + ) + } + }) + } + /> + {receiptPrintingConfig.sms ? 'Yes' : 'No'} +
+
Date: Tue, 10 Aug 2021 01:46:56 +0100 Subject: [PATCH 2/6] feat: sms receipt route --- lib/routes/customerRoutes.js | 6 ++++++ lib/routes/pollingRoutes.js | 1 + 2 files changed, 7 insertions(+) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 714a29cd..41acb14e 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -116,11 +116,17 @@ function updateTxCustomerPhoto (req, res, next) { .catch(next) } +function sendSmsReceipt (req, res, next) { + console.log(req.body) + return respond(req, res, {}) +} + router.patch('/:id', updateCustomer) router.patch('/:id/sanctions', triggerSanctions) router.patch('/:id/block', triggerBlock) router.patch('/:id/suspend', triggerSuspend) router.patch('/:id/photos/idcarddata', updateIdCardData) router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto) +router.patch('/:id/smsreceipt', sendSmsReceipt) module.exports = router diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index cd64f6b5..af732e69 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -73,6 +73,7 @@ function poll (req, res, next) { locale, version, receiptPrintingActive: receipt.active, + smsReceiptActive: receipt.sms, cassettes, twoWayMode: cashOutConfig.active, zeroConfLimits, From 5f18fcbeb625ab1c224298052da66fed36d7c0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Tue, 10 Aug 2021 20:32:45 +0100 Subject: [PATCH 3/6] feat: send twilio sms --- lib/routes/customerRoutes.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 41acb14e..d5daee99 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -1,6 +1,7 @@ const express = require('express') const router = express.Router() const semver = require('semver') +const sms = require('../sms') const _ = require('lodash/fp') const compliance = require('../compliance') @@ -117,8 +118,11 @@ function updateTxCustomerPhoto (req, res, next) { } function sendSmsReceipt (req, res, next) { - console.log(req.body) - return respond(req, res, {}) + const receiptOptions = _.omit(['active', 'sms'], configManager.getReceipt(req.settings.config)) + const smsRequest = sms.formatSmsRequest(req.body.data, receiptOptions) + sms.sendMessage(req.settings, smsRequest) + .then(() => respond(req, res, {})) + .catch(next) } router.patch('/:id', updateCustomer) From 1d2f526c1d97edace85b85fd53eb3b76a4916987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Fri, 13 Aug 2021 12:05:19 +0100 Subject: [PATCH 4/6] feat: format sms receipts --- lib/sms.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/sms.js b/lib/sms.js index 2876a9cf..3d67bf86 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -25,4 +25,65 @@ function getLookup (settings, number) { }) } -module.exports = { sendMessage, getLookup } +function formatSmsReceipt (data, options) { + var message = `RECEIPT\n` + if (data.operatorInfo) { + message = message.concat(`Operator information:\n`) + if (data.operatorInfo.name) { + message = message.concat(`${data.operatorInfo.name}\n`) + } + + if (data.operatorInfo.website && options.operatorWebsite) { + message = message.concat(`${data.operatorInfo.website}\n`) + } + + if (data.operatorInfo.email && options.operatorEmail) { + message = message.concat(`${data.operatorInfo.email}\n`) + } + + if (data.operatorInfo.phone && options.operatorPhone) { + message = message.concat(`${data.operatorInfo.phone}\n`) + } + + if (data.operatorInfo.companyNumber && options.companyNumber) { + message = message.concat(`${data.operatorInfo.companyNumber}\n`) + } + } + if (data.location && options.machineLocation) { + message = message.concat(`Location: ${data.location}\n`) + } + + if (options.customerNameOrPhoneNumber) { + if (data.customerName) { + message = message.concat(`Customer: ${data.customerName}\n`) + } else { + message = message.concat(`Customer: ${data.customerPhone}\n`) + } + } + + message = message.concat(`Session: ${data.session}\n`) + message = message.concat(`Time: ${data.time}\n`) + message = message.concat(`Direction: ${data.direction}\n`) + message = message.concat(`Fiat: ${data.fiat}\n`) + message = message.concat(`Crypto: ${data.crypto}\n`) + + if (data.rate && options.exchangeRate) { + message = message.concat(`Rate: ${data.rate}\n`) + } + + message = message.concat(`TXID: ${data.txId}\n`) + + if (data.address && options.addressQRCode) { + message = message.concat(`Address: ${data.address}\n`) + } + + const request = { + sms: { + toNumber: data.customerPhone, + body: message + } + } + return request +} + +module.exports = { sendMessage, formatSmsReceipt, getLookup } From 9506b98b47163f8f53abe3a6648372ad566a4969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 27 Sep 2021 14:53:18 +0100 Subject: [PATCH 5/6] refactor: move sms receipt content to server --- lib/routes/customerRoutes.js | 10 +++++--- lib/sms.js | 48 +++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index d5daee99..cccb9554 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -119,10 +119,12 @@ function updateTxCustomerPhoto (req, res, next) { function sendSmsReceipt (req, res, next) { const receiptOptions = _.omit(['active', 'sms'], configManager.getReceipt(req.settings.config)) - const smsRequest = sms.formatSmsRequest(req.body.data, receiptOptions) - sms.sendMessage(req.settings, smsRequest) - .then(() => respond(req, res, {})) - .catch(next) + sms.buildSms(req.body.data, receiptOptions) + .then(smsRequest => { + sms.sendMessage(req.settings, smsRequest) + .then(() => respond(req, res, {})) + .catch(next) + }) } router.patch('/:id', updateCustomer) diff --git a/lib/sms.js b/lib/sms.js index 3d67bf86..ed2de98b 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -1,5 +1,11 @@ +const { BigNumber } = require('bignumber.js') const ph = require('./plugin-helper') const argv = require('minimist')(process.argv.slice(2)) +const { getTx } = require('./new-admin/services/transactions.js') +const { getCustomerById } = require('./customers') +const configManager = require('./new-config-manager') +const machineLoader = require('./machine-loader') +const { utils: coinUtils } = require('lamassu-coins') function getPlugin (settings) { const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio' @@ -24,6 +30,46 @@ function getLookup (settings, number) { return plugin.getLookup(account, number) }) } +const toCryptoUnits = (cryptoAtoms, cryptoCode) => { + const unitScale = coinUtils.getCryptoCurrency(cryptoCode).unitScale + return cryptoAtoms.shift(-unitScale) +} + +function buildSms (data, receiptOptions) { + return getTx(data.session, data.txClass) + .then(tx => { + Promise.all([getCustomerById(tx.customer_id), machineLoader.getConfig(), machineLoader.getMachine()]) + .then(([customer, config, deviceConfig]) => { + const localeConfig = configManager.getLocale(tx.device_id, config) + const timezone = localeConfig.timezone.split(':') + const dstOffset = timezone[1] + + const cashInCommission = BigNumber(1).add(BigNumber(tx.commissionPercentage)) + + const rate = BigNumber(tx.rawTickerPrice).mul(cashInCommission).round(2) + const date = new Date() + date.setMinutes(date.getMinutes() + parseInt(dstOffset)) + const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}` + + const data = { + operatorInfo: configManager.getOperatorInfo(config), + location: deviceConfig.machineLocation, + customerName: customer.name, + customerPhone: customer.phone, + session: tx.id, + time: dateString, + direction: tx.direction === 'cashIn' ? 'Cash-in' : 'Cash-out', + fiat: `${tx.fiat.toString()} ${tx.fiatCode}`, + crypto: `${toCryptoUnits(tx.cryptoAtoms, tx.cryptoCode)} ${tx.cryptoCode}`, + rate: `1 ${tx.cryptoCode} = ${rate} ${tx.fiatCode}`, + address: tx.toAddress, + txId: tx.txHash + } + + return formatSmsReceipt(data, receiptOptions) + }) + }) +} function formatSmsReceipt (data, options) { var message = `RECEIPT\n` @@ -86,4 +132,4 @@ function formatSmsReceipt (data, options) { return request } -module.exports = { sendMessage, formatSmsReceipt, getLookup } +module.exports = { sendMessage, formatSmsReceipt, getLookup, buildSms } From 154fe7affbc34b8209eca839e46ba0622706fd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Fri, 8 Oct 2021 11:58:21 +0100 Subject: [PATCH 6/6] fix: sms receipt data construction --- lib/machine-loader.js | 3 ++- lib/routes/customerRoutes.js | 44 ++++++++++++++++++++++++++++++++++- lib/sms.js | 45 ++---------------------------------- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 5ce26a5a..41838bde 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -233,5 +233,6 @@ module.exports = { updateNetworkPerformance, updateNetworkHeartbeat, getNetworkPerformance, - getNetworkHeartbeat + getNetworkHeartbeat, + getConfig } diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index cccb9554..1093a91d 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -3,6 +3,7 @@ const router = express.Router() const semver = require('semver') const sms = require('../sms') const _ = require('lodash/fp') +const BN = require('../bn') const compliance = require('../compliance') const complianceTriggers = require('../compliance-triggers') @@ -12,6 +13,10 @@ const txs = require('../new-admin/services/transactions') const httpError = require('../route-helpers').httpError const notifier = require('../notifier') const respond = require('../respond') +const { getTx } = require('../new-admin/services/transactions.js') +const { getCustomerById } = require('../customers') +const machineLoader = require('../machine-loader') +const { loadLatestConfig } = require('../new-settings-loader') function updateCustomer (req, res, next) { const id = req.params.id @@ -117,9 +122,46 @@ function updateTxCustomerPhoto (req, res, next) { .catch(next) } +function buildSms (data, receiptOptions) { + return Promise.all([getTx(data.session, data.txClass), loadLatestConfig()]) + .then(([tx, config]) => { + return Promise.all([getCustomerById(tx.customer_id), machineLoader.getMachine(tx.device_id, config)]) + .then(([customer, deviceConfig]) => { + const formattedTx = _.mapKeys(_.camelCase)(tx) + const localeConfig = configManager.getLocale(formattedTx.deviceId, config) + const timezone = localeConfig.timezone.split(':') + const dstOffset = timezone[1] + + const cashInCommission = new BN(1).plus(new BN(formattedTx.commissionPercentage)) + + const rate = new BN(formattedTx.rawTickerPrice).multipliedBy(cashInCommission).decimalPlaces(2) + const date = new Date() + date.setMinutes(date.getMinutes() + parseInt(dstOffset)) + const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}` + + const data = { + operatorInfo: configManager.getOperatorInfo(config), + location: deviceConfig.machineLocation, + customerName: customer.name, + customerPhone: customer.phone, + session: formattedTx.id, + time: dateString, + direction: formattedTx.direction === 'cashIn' ? 'Cash-in' : 'Cash-out', + fiat: `${formattedTx.fiat.toString()} ${formattedTx.fiatCode}`, + crypto: `${sms.toCryptoUnits(BN(formattedTx.cryptoAtoms), formattedTx.cryptoCode)} ${formattedTx.cryptoCode}`, + rate: `1 ${formattedTx.cryptoCode} = ${rate} ${formattedTx.fiatCode}`, + address: formattedTx.toAddress, + txId: formattedTx.txHash + } + + return sms.formatSmsReceipt(data, receiptOptions) + }) + }) +} + function sendSmsReceipt (req, res, next) { const receiptOptions = _.omit(['active', 'sms'], configManager.getReceipt(req.settings.config)) - sms.buildSms(req.body.data, receiptOptions) + buildSms(req.body.data, receiptOptions) .then(smsRequest => { sms.sendMessage(req.settings, smsRequest) .then(() => respond(req, res, {})) diff --git a/lib/sms.js b/lib/sms.js index ed2de98b..597f5bd1 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -1,10 +1,5 @@ -const { BigNumber } = require('bignumber.js') const ph = require('./plugin-helper') const argv = require('minimist')(process.argv.slice(2)) -const { getTx } = require('./new-admin/services/transactions.js') -const { getCustomerById } = require('./customers') -const configManager = require('./new-config-manager') -const machineLoader = require('./machine-loader') const { utils: coinUtils } = require('lamassu-coins') function getPlugin (settings) { @@ -32,43 +27,7 @@ function getLookup (settings, number) { } const toCryptoUnits = (cryptoAtoms, cryptoCode) => { const unitScale = coinUtils.getCryptoCurrency(cryptoCode).unitScale - return cryptoAtoms.shift(-unitScale) -} - -function buildSms (data, receiptOptions) { - return getTx(data.session, data.txClass) - .then(tx => { - Promise.all([getCustomerById(tx.customer_id), machineLoader.getConfig(), machineLoader.getMachine()]) - .then(([customer, config, deviceConfig]) => { - const localeConfig = configManager.getLocale(tx.device_id, config) - const timezone = localeConfig.timezone.split(':') - const dstOffset = timezone[1] - - const cashInCommission = BigNumber(1).add(BigNumber(tx.commissionPercentage)) - - const rate = BigNumber(tx.rawTickerPrice).mul(cashInCommission).round(2) - const date = new Date() - date.setMinutes(date.getMinutes() + parseInt(dstOffset)) - const dateString = `${date.toISOString().replace('T', ' ').slice(0, 19)}` - - const data = { - operatorInfo: configManager.getOperatorInfo(config), - location: deviceConfig.machineLocation, - customerName: customer.name, - customerPhone: customer.phone, - session: tx.id, - time: dateString, - direction: tx.direction === 'cashIn' ? 'Cash-in' : 'Cash-out', - fiat: `${tx.fiat.toString()} ${tx.fiatCode}`, - crypto: `${toCryptoUnits(tx.cryptoAtoms, tx.cryptoCode)} ${tx.cryptoCode}`, - rate: `1 ${tx.cryptoCode} = ${rate} ${tx.fiatCode}`, - address: tx.toAddress, - txId: tx.txHash - } - - return formatSmsReceipt(data, receiptOptions) - }) - }) + return cryptoAtoms.shiftedBy(-unitScale) } function formatSmsReceipt (data, options) { @@ -132,4 +91,4 @@ function formatSmsReceipt (data, options) { return request } -module.exports = { sendMessage, formatSmsReceipt, getLookup, buildSms } +module.exports = { sendMessage, formatSmsReceipt, getLookup, toCryptoUnits }