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 714a29cd..1093a91d 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -1,7 +1,9 @@ const express = require('express') 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') @@ -11,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 @@ -116,11 +122,59 @@ 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)) + buildSms(req.body.data, receiptOptions) + .then(smsRequest => { + sms.sendMessage(req.settings, smsRequest) + .then(() => respond(req, res, {})) + .catch(next) + }) +} + 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, diff --git a/lib/sms.js b/lib/sms.js index 2876a9cf..597f5bd1 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -1,5 +1,6 @@ const ph = require('./plugin-helper') const argv = require('minimist')(process.argv.slice(2)) +const { utils: coinUtils } = require('lamassu-coins') function getPlugin (settings) { const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio' @@ -24,5 +25,70 @@ function getLookup (settings, number) { return plugin.getLookup(account, number) }) } +const toCryptoUnits = (cryptoAtoms, cryptoCode) => { + const unitScale = coinUtils.getCryptoCurrency(cryptoCode).unitScale + return cryptoAtoms.shiftedBy(-unitScale) +} -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, toCryptoUnits } 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 }) => {
Enable receipt printing?
+Enable receipt printing
Offer SMS receipt
+