Merge pull request #817 from josepfo/feat/sms-receipts

feat: toggle for sms receipt
This commit is contained in:
Rafael Taranto 2021-11-17 14:30:14 +00:00 committed by GitHub
commit c580f4cbf1
5 changed files with 146 additions and 3 deletions

View file

@ -233,5 +233,6 @@ module.exports = {
updateNetworkPerformance, updateNetworkPerformance,
updateNetworkHeartbeat, updateNetworkHeartbeat,
getNetworkPerformance, getNetworkPerformance,
getNetworkHeartbeat getNetworkHeartbeat,
getConfig
} }

View file

@ -1,7 +1,9 @@
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const semver = require('semver') const semver = require('semver')
const sms = require('../sms')
const _ = require('lodash/fp') const _ = require('lodash/fp')
const BN = require('../bn')
const compliance = require('../compliance') const compliance = require('../compliance')
const complianceTriggers = require('../compliance-triggers') const complianceTriggers = require('../compliance-triggers')
@ -11,6 +13,10 @@ const txs = require('../new-admin/services/transactions')
const httpError = require('../route-helpers').httpError const httpError = require('../route-helpers').httpError
const notifier = require('../notifier') const notifier = require('../notifier')
const respond = require('../respond') 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) { function updateCustomer (req, res, next) {
const id = req.params.id const id = req.params.id
@ -116,11 +122,59 @@ function updateTxCustomerPhoto (req, res, next) {
.catch(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', updateCustomer)
router.patch('/:id/sanctions', triggerSanctions) router.patch('/:id/sanctions', triggerSanctions)
router.patch('/:id/block', triggerBlock) router.patch('/:id/block', triggerBlock)
router.patch('/:id/suspend', triggerSuspend) router.patch('/:id/suspend', triggerSuspend)
router.patch('/:id/photos/idcarddata', updateIdCardData) router.patch('/:id/photos/idcarddata', updateIdCardData)
router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto) router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto)
router.patch('/:id/smsreceipt', sendSmsReceipt)
module.exports = router module.exports = router

View file

@ -73,6 +73,7 @@ function poll (req, res, next) {
locale, locale,
version, version,
receiptPrintingActive: receipt.active, receiptPrintingActive: receipt.active,
smsReceiptActive: receipt.sms,
cassettes, cassettes,
twoWayMode: cashOutConfig.active, twoWayMode: cashOutConfig.active,
zeroConfLimits, zeroConfLimits,

View file

@ -1,5 +1,6 @@
const ph = require('./plugin-helper') const ph = require('./plugin-helper')
const argv = require('minimist')(process.argv.slice(2)) const argv = require('minimist')(process.argv.slice(2))
const { utils: coinUtils } = require('lamassu-coins')
function getPlugin (settings) { function getPlugin (settings) {
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio' const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
@ -24,5 +25,70 @@ function getLookup (settings, number) {
return plugin.getLookup(account, 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 }

View file

@ -49,7 +49,7 @@ const ReceiptPrinting = memo(({ wizard }) => {
<H4>Receipt options</H4> <H4>Receipt options</H4>
</div> </div>
<div className={classes.switchRow}> <div className={classes.switchRow}>
<P>Enable receipt printing?</P> <P>Enable receipt printing</P>
<div className={classes.switch}> <div className={classes.switch}>
<Switch <Switch
checked={receiptPrintingConfig.active} checked={receiptPrintingConfig.active}
@ -69,6 +69,27 @@ const ReceiptPrinting = memo(({ wizard }) => {
<Label2>{receiptPrintingConfig.active ? 'Yes' : 'No'}</Label2> <Label2>{receiptPrintingConfig.active ? 'Yes' : 'No'}</Label2>
</div> </div>
</div> </div>
<div className={classes.switchRow}>
<P>Offer SMS receipt</P>
<div className={classes.switch}>
<Switch
checked={receiptPrintingConfig.sms}
onChange={event =>
saveConfig({
variables: {
config: toNamespace(
namespaces.RECEIPT,
R.merge(receiptPrintingConfig, {
sms: event.target.checked
})
)
}
})
}
/>
<Label2>{receiptPrintingConfig.sms ? 'Yes' : 'No'}</Label2>
</div>
</div>
<BooleanPropertiesTable <BooleanPropertiesTable
editing={wizard} editing={wizard}
title={'Visible on the receipt (options)'} title={'Visible on the receipt (options)'}