improve cash-in error handling
This commit is contained in:
parent
86adfd0a63
commit
84a93599f5
9 changed files with 263 additions and 47 deletions
|
|
@ -2,13 +2,17 @@ const _ = require('lodash/fp')
|
||||||
const pgp = require('pg-promise')()
|
const pgp = require('pg-promise')()
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const BN = require('./bn')
|
const BN = require('./bn')
|
||||||
|
const plugins = require('./plugins')
|
||||||
|
const logger = require('./logger')
|
||||||
|
|
||||||
const mapValuesWithKey = _.mapValues.convert({cap: false})
|
const mapValuesWithKey = _.mapValues.convert({cap: false})
|
||||||
|
|
||||||
module.exports = {post}
|
module.exports = {post, monitorPending}
|
||||||
|
|
||||||
const UPDATEABLE_FIELDS = ['fee', 'txHash', 'phone', 'error', 'send',
|
const UPDATEABLE_FIELDS = ['fee', 'txHash', 'phone', 'error', 'send',
|
||||||
'cryptoAtoms', 'fiat', 'timedout']
|
'cryptoAtoms', 'fiat', 'timedout']
|
||||||
|
const PENDING_INTERVAL = '1 day'
|
||||||
|
const MAX_PENDING = 10
|
||||||
|
|
||||||
function post (tx, pi) {
|
function post (tx, pi) {
|
||||||
const TransactionMode = pgp.txMode.TransactionMode
|
const TransactionMode = pgp.txMode.TransactionMode
|
||||||
|
|
@ -24,7 +28,10 @@ function post (tx, pi) {
|
||||||
.then(row => {
|
.then(row => {
|
||||||
return t.any(sql2, [tx.id])
|
return t.any(sql2, [tx.id])
|
||||||
.then(billRows => {
|
.then(billRows => {
|
||||||
return upsert(row, tx)
|
const oldTx = toObj(row)
|
||||||
|
|
||||||
|
return preProcess(oldTx, tx, pi)
|
||||||
|
.then(preProcessedTx => upsert(oldTx, preProcessedTx))
|
||||||
.then(vector => {
|
.then(vector => {
|
||||||
return insertNewBills(billRows, tx)
|
return insertNewBills(billRows, tx)
|
||||||
.then(newBills => _.concat(vector, [newBills]))
|
.then(newBills => _.concat(vector, [newBills]))
|
||||||
|
|
@ -122,10 +129,7 @@ function insertNewBills (billRows, tx) {
|
||||||
.then(() => bills)
|
.then(() => bills)
|
||||||
}
|
}
|
||||||
|
|
||||||
function upsert (row, tx) {
|
function upsert (oldTx, tx) {
|
||||||
console.log('DEBUG501: %j', row)
|
|
||||||
const oldTx = toObj(row)
|
|
||||||
|
|
||||||
if (!oldTx) {
|
if (!oldTx) {
|
||||||
console.log('DEBUG500: %j', tx)
|
console.log('DEBUG500: %j', tx)
|
||||||
return insert(tx)
|
return insert(tx)
|
||||||
|
|
@ -162,24 +166,81 @@ function registerTrades (pi, txVector) {
|
||||||
_.forEach(bill => pi.buy(bill), newBills)
|
_.forEach(bill => pi.buy(bill), newBills)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logAction (rec, tx) {
|
||||||
|
const action = {
|
||||||
|
tx_id: tx.id,
|
||||||
|
action: 'sendCoins',
|
||||||
|
error: rec.error,
|
||||||
|
error_code: rec.errorCode,
|
||||||
|
tx_hash: rec.txHash,
|
||||||
|
success: rec.sendConfirmed === true
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql = pgp.helpers.insert(action, null, 'cash_in_actions')
|
||||||
|
|
||||||
|
return db.none(sql)
|
||||||
|
.then(_.constant(rec))
|
||||||
|
}
|
||||||
|
|
||||||
function postProcess (txVector, pi) {
|
function postProcess (txVector, pi) {
|
||||||
const [oldTx, newTx] = txVector
|
const [oldTx, newTx] = txVector
|
||||||
|
|
||||||
registerTrades(pi, txVector)
|
registerTrades(pi, txVector)
|
||||||
|
|
||||||
if (newTx.send && !oldTx.send) {
|
const isClearToSend = newTx.send &&
|
||||||
|
!oldTx.sendPending &&
|
||||||
|
!oldTx.sendConfirmed
|
||||||
|
|
||||||
|
if (isClearToSend) {
|
||||||
return pi.sendCoins(newTx)
|
return pi.sendCoins(newTx)
|
||||||
.then(txHash => ({
|
.then(txHash => ({
|
||||||
txHash,
|
txHash,
|
||||||
sendConfirmed: true,
|
sendConfirmed: true,
|
||||||
sendTime: 'now()^'
|
sendTime: 'now()^',
|
||||||
|
sendPending: false,
|
||||||
|
error: null,
|
||||||
|
errorCode: null
|
||||||
}))
|
}))
|
||||||
.catch(err => ({
|
.catch(err => ({
|
||||||
sendTime: 'now()^',
|
sendTime: 'now()^',
|
||||||
error: err.message,
|
error: err.message,
|
||||||
errorCode: err.name
|
errorCode: err.name,
|
||||||
|
sendPending: false
|
||||||
}))
|
}))
|
||||||
|
.then(r => logAction(r, newTx))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preProcess (oldTx, newTx, pi) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (!oldTx) return resolve(newTx)
|
||||||
|
if (newTx.send && !oldTx.send) return resolve(_.set('sendPending', true, newTx))
|
||||||
|
return resolve(newTx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function monitorPending (settings) {
|
||||||
|
const sql = `select * from cash_in_txs
|
||||||
|
where created > now() - interval $1
|
||||||
|
and send
|
||||||
|
and not send_confirmed
|
||||||
|
and not send_pending
|
||||||
|
and not operator_completed
|
||||||
|
order by created
|
||||||
|
limit $2`
|
||||||
|
|
||||||
|
const processPending = row => {
|
||||||
|
const tx = toObj(row)
|
||||||
|
const pi = plugins(settings, tx.deviceId)
|
||||||
|
|
||||||
|
return post(tx, pi)
|
||||||
|
.catch(logger.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.any(sql, [PENDING_INTERVAL, MAX_PENDING])
|
||||||
|
.then(_.tap(console.log))
|
||||||
|
.then(rows => Promise.all(_.map(processPending, rows)))
|
||||||
|
.catch(logger.error)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,60 @@ function post (tx, pi) {
|
||||||
const [, newTx] = txVector
|
const [, newTx] = txVector
|
||||||
return postProcess(txVector, pi)
|
return postProcess(txVector, pi)
|
||||||
.then(changes => update(newTx, changes))
|
.then(changes => update(newTx, changes))
|
||||||
|
.then(savedTx => {
|
||||||
|
return logAction(tx, savedTx)
|
||||||
|
.then(_.constant(savedTx))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function logError (action, err) {
|
||||||
|
return logAction({
|
||||||
|
action,
|
||||||
|
error: err.message,
|
||||||
|
error_code: err.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispense (tx) {
|
||||||
|
const bills = tx.bills
|
||||||
|
|
||||||
|
if (_.isEmpty(bills)) return {}
|
||||||
|
|
||||||
|
const dispense = {
|
||||||
|
provisioned_1: bills[0].provisioned,
|
||||||
|
provisioned_2: bills[1].provisioned,
|
||||||
|
dispensed_1: bills[0].actualDispense,
|
||||||
|
dispensed_2: bills[1].actualDispense,
|
||||||
|
rejected_1: bills[0].rejected,
|
||||||
|
rejected_2: bills[1].rejected,
|
||||||
|
denomination_1: bills[0].denomination,
|
||||||
|
denomination_2: bills[1].denomination
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispense
|
||||||
|
}
|
||||||
|
|
||||||
|
function logDispense (tx) {
|
||||||
|
const baseRec = {error: tx.error, errorCode: tx.errorCode}
|
||||||
|
const rec = _.merge(mapDispense(tx), baseRec)
|
||||||
|
|
||||||
|
return logAction('dispense', rec, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logActionById (action, _rec, txId) {
|
||||||
|
const rec = _.assign(_rec, {action, tx_id: txId, redeem: false})
|
||||||
|
const sql = pgp.helpers.insert(rec, null, 'cash_out_actions')
|
||||||
|
|
||||||
|
return db.none(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logAction (action, _rec, tx) {
|
||||||
|
const rec = _.assign(_rec, {action, tx_id: tx.id, redeem: tx.redeem})
|
||||||
|
const sql = pgp.helpers.insert(rec, null, 'cash_out_actions')
|
||||||
|
|
||||||
|
return db.none(sql)
|
||||||
|
.then(_.constant(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
function nilEqual (a, b) {
|
function nilEqual (a, b) {
|
||||||
|
|
@ -95,8 +148,6 @@ function toObj (row) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function upsert (oldTx, tx) {
|
function upsert (oldTx, tx) {
|
||||||
// insert bills
|
|
||||||
|
|
||||||
if (!oldTx) {
|
if (!oldTx) {
|
||||||
return insert(tx)
|
return insert(tx)
|
||||||
.then(newTx => [oldTx, newTx])
|
.then(newTx => [oldTx, newTx])
|
||||||
|
|
@ -106,24 +157,6 @@ function upsert (oldTx, tx) {
|
||||||
.then(newTx => [oldTx, newTx])
|
.then(newTx => [oldTx, newTx])
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispense (tx) {
|
|
||||||
const bills = tx.bills
|
|
||||||
|
|
||||||
if (_.isEmpty(bills)) return tx
|
|
||||||
|
|
||||||
const extra = {
|
|
||||||
dispensed1: bills[0].actualDispense,
|
|
||||||
dispensed2: bills[1].actualDispense,
|
|
||||||
rejected1: bills[0].rejected,
|
|
||||||
rejected2: bills[1].rejected,
|
|
||||||
denomination1: bills[0].denomination,
|
|
||||||
denomination2: bills[1].denomination,
|
|
||||||
'dispenseTime^': 'NOW()'
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.assign(tx, extra)
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertBigNumFields (obj) {
|
function convertBigNumFields (obj) {
|
||||||
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
|
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
|
||||||
? value.toString()
|
? value.toString()
|
||||||
|
|
@ -141,7 +174,7 @@ function convertField (key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toDb (tx) {
|
function toDb (tx) {
|
||||||
const massager = _.flow(convertBigNumFields, mapDispense, _.omit(['direction', 'bills']), _.mapKeys(convertField))
|
const massager = _.flow(convertBigNumFields, _.omit(['direction', 'bills']), _.mapKeys(convertField))
|
||||||
return massager(tx)
|
return massager(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,17 +208,47 @@ function nextHd (isHd, tx) {
|
||||||
.then(row => _.set('hdIndex', row.hd_index, tx))
|
.then(row => _.set('hdIndex', row.hd_index, tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
function preProcess (tx, newTx, pi) {
|
function preProcess (oldTx, newTx, pi) {
|
||||||
if (!tx) {
|
if (!oldTx) {
|
||||||
return pi.isHd(newTx)
|
return pi.isHd(newTx)
|
||||||
.then(isHd => nextHd(isHd, newTx))
|
.then(isHd => nextHd(isHd, newTx))
|
||||||
.then(newTxHd => {
|
.then(newTxHd => {
|
||||||
return pi.newAddress(newTxHd)
|
return pi.newAddress(newTxHd)
|
||||||
.then(_.set('toAddress', _, newTxHd))
|
.then(_.set('toAddress', _, newTxHd))
|
||||||
})
|
})
|
||||||
|
.then(addressedTx => {
|
||||||
|
const rec = {toAddress: addressedTx.toAddress}
|
||||||
|
return logAction('provisionAddress', rec, addressedTx)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
return logError('provisionAddress', err)
|
||||||
|
.then(() => { throw err })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(updateStatus(tx, newTx))
|
return Promise.resolve(updateStatus(oldTx, newTx))
|
||||||
|
.then(updatedTx => {
|
||||||
|
if (!oldTx) return updatedTx
|
||||||
|
|
||||||
|
if (updatedTx.status !== oldTx.status) {
|
||||||
|
return logAction(updatedTx.status, {}, updatedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isNil(oldTx.dispenseConfirmed) && _.isBoolean(updatedTx.dispenseConfirmed)) {
|
||||||
|
return logDispense(updatedTx)
|
||||||
|
.then(pi.updateCassettes(updatedTx))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldTx.phone && newTx.phone) {
|
||||||
|
return logAction('addPhone', {}, updatedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldTx.redeem && newTx.redeem) {
|
||||||
|
return logAction('redeemLater', {}, updatedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedTx
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function postProcess (txVector, pi) {
|
function postProcess (txVector, pi) {
|
||||||
|
|
@ -197,6 +260,14 @@ function postProcess (txVector, pi) {
|
||||||
pi.sell(newTx)
|
pi.sell(newTx)
|
||||||
return _.set('bills', billMath.makeChange(cartridges.cartridges, newTx.fiat), newTx)
|
return _.set('bills', billMath.makeChange(cartridges.cartridges, newTx.fiat), newTx)
|
||||||
})
|
})
|
||||||
|
.then(tx => {
|
||||||
|
const rec = {provisioned_1: tx.bills[0], provisioned_2: tx.bills[1]}
|
||||||
|
return logAction('provisionNotes', rec, tx)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
return logError('provisionNotes', err)
|
||||||
|
.then(() => { throw err })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(newTx)
|
return Promise.resolve(newTx)
|
||||||
|
|
@ -277,7 +348,7 @@ function monitorUnnotified (settings) {
|
||||||
function cancel (txId) {
|
function cancel (txId) {
|
||||||
const updateRec = {
|
const updateRec = {
|
||||||
'dispense_time': 'now()^',
|
'dispense_time': 'now()^',
|
||||||
dispense_error: 'Operator cancel',
|
error: 'Operator cancel',
|
||||||
dispensed: true
|
dispensed: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,4 +361,5 @@ function cancel (txId) {
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||||
})
|
})
|
||||||
|
.then(() => logActionById('operatorCompleted', {}, txId))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,20 @@ function plugins (settings, deviceId) {
|
||||||
.catch(err => logger.error(err))
|
.catch(err => logger.error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCassettes (tx) {
|
||||||
|
// Note: This is the only place we update config from outside admin,
|
||||||
|
// so should be safe even though it's not an atomic operation.
|
||||||
|
//
|
||||||
|
// However, we should make all config changes atomic in the future.
|
||||||
|
const config = configManager.machineScoped(deviceId, settings.config)
|
||||||
|
config.topCashOutDenomination -= tx.bills[0].actualDispense +
|
||||||
|
tx.bills[0].rejected
|
||||||
|
config.bottomCashOutDenomination -= tx.bills[1].actualDispense +
|
||||||
|
tx.bills[1].rejected
|
||||||
|
|
||||||
|
// save
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pollQueries,
|
pollQueries,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
|
|
@ -525,6 +539,7 @@ function plugins (settings, deviceId) {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
checkBalances,
|
checkBalances,
|
||||||
buildCartridges,
|
buildCartridges,
|
||||||
|
updateCassettes,
|
||||||
buy,
|
buy,
|
||||||
sell
|
sell
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,27 @@ function balance (account, cryptoCode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInsufficient (cryptoCode) {
|
// Note: This makes it easier to test insufficient funds errors
|
||||||
if (cryptoCode === 'BTC') return BN(1e5 * 10)
|
let sendCount = 0
|
||||||
if (cryptoCode === 'ETH') return BN(1e18 * 0.25)
|
|
||||||
|
function isInsufficient (cryptoAtoms, cryptoCode) {
|
||||||
|
if (cryptoCode === 'BTC') return cryptoAtoms.gt(1e5 * 10 * sendCount)
|
||||||
|
if (cryptoCode === 'ETH') return cryptoAtoms.gt(1e18 * 0.25 * sendCount)
|
||||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
|
function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||||
|
sendCount++
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (isInsufficient(cryptoAtoms, cryptoCode)) {
|
||||||
|
console.log('[%s] DEBUG: Mock wallet insufficient funds: %s',
|
||||||
|
cryptoCode, cryptoAtoms.toString())
|
||||||
|
return reject(new E.InsufficientFundsError())
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[%s] DEBUG: Mock wallet sending %s cryptoAtoms to %s',
|
console.log('[%s] DEBUG: Mock wallet sending %s cryptoAtoms to %s',
|
||||||
cryptoCode, cryptoAtoms.toString(), toAddress)
|
cryptoCode, cryptoAtoms.toString(), toAddress)
|
||||||
if (isInsufficient(cryptoCode)) return reject(new E.InsufficientFundsError())
|
|
||||||
return resolve('<txHash>')
|
return resolve('<txHash>')
|
||||||
}, 2000)
|
}, 2000)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const notifier = require('./notifier')
|
||||||
const T = require('./time')
|
const T = require('./time')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const cashOutTx = require('./cash-out-tx')
|
const cashOutTx = require('./cash-out-tx')
|
||||||
|
const cashInTx = require('./cash-in-tx')
|
||||||
|
|
||||||
const INCOMING_TX_INTERVAL = 30 * T.seconds
|
const INCOMING_TX_INTERVAL = 30 * T.seconds
|
||||||
const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds
|
const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds
|
||||||
|
|
@ -12,6 +13,7 @@ const TRADE_INTERVAL = 10 * T.seconds
|
||||||
const PONG_INTERVAL = 10 * T.seconds
|
const PONG_INTERVAL = 10 * T.seconds
|
||||||
const PONG_CLEAR_INTERVAL = 1 * T.day
|
const PONG_CLEAR_INTERVAL = 1 * T.day
|
||||||
const CHECK_NOTIFICATION_INTERVAL = 30 * T.seconds
|
const CHECK_NOTIFICATION_INTERVAL = 30 * T.seconds
|
||||||
|
const PENDING_INTERVAL = 10 * T.seconds
|
||||||
|
|
||||||
let _pi, _settings
|
let _pi, _settings
|
||||||
|
|
||||||
|
|
@ -40,6 +42,7 @@ function start (__settings) {
|
||||||
setInterval(() => cashOutTx.monitorLiveIncoming(settings()), LIVE_INCOMING_TX_INTERVAL)
|
setInterval(() => cashOutTx.monitorLiveIncoming(settings()), LIVE_INCOMING_TX_INTERVAL)
|
||||||
setInterval(() => cashOutTx.monitorStaleIncoming(settings()), INCOMING_TX_INTERVAL)
|
setInterval(() => cashOutTx.monitorStaleIncoming(settings()), INCOMING_TX_INTERVAL)
|
||||||
setInterval(() => cashOutTx.monitorUnnotified(settings()), UNNOTIFIED_INTERVAL)
|
setInterval(() => cashOutTx.monitorUnnotified(settings()), UNNOTIFIED_INTERVAL)
|
||||||
|
setInterval(() => cashInTx.monitorPending(settings()), PENDING_INTERVAL)
|
||||||
setInterval(() => pi().sweepHd(), SWEEP_HD_INTERVAL)
|
setInterval(() => pi().sweepHd(), SWEEP_HD_INTERVAL)
|
||||||
setInterval(() => pi().pong(), PONG_INTERVAL)
|
setInterval(() => pi().pong(), PONG_INTERVAL)
|
||||||
setInterval(() => pi().pongClear(), PONG_CLEAR_INTERVAL)
|
setInterval(() => pi().pongClear(), PONG_CLEAR_INTERVAL)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const plugins = require('./plugins')
|
||||||
const helpers = require('./route-helpers')
|
const helpers = require('./route-helpers')
|
||||||
const poller = require('./poller')
|
const poller = require('./poller')
|
||||||
const Tx = require('./tx')
|
const Tx = require('./tx')
|
||||||
const E = require('./error')
|
|
||||||
|
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
|
|
||||||
|
|
@ -104,14 +103,7 @@ function postTx (req, res, next) {
|
||||||
|
|
||||||
return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
|
return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
|
||||||
.then(tx => {
|
.then(tx => {
|
||||||
if (tx.errorCode) {
|
if (tx.errorCode) throw httpError(tx.error, 500)
|
||||||
console.log('DEBUG100: %s, %s', tx.errorCode, E.InsufficientFundsError.name)
|
|
||||||
if (tx.errorCode === E.InsufficientFundsError.code) {
|
|
||||||
throw httpError(tx.error, 570)
|
|
||||||
}
|
|
||||||
throw httpError(tx.error, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json(tx)
|
return res.json(tx)
|
||||||
})
|
})
|
||||||
.catch(next)
|
.catch(next)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ exports.up = function (next) {
|
||||||
'alter table cash_in_txs add column timedout boolean not null default false',
|
'alter table cash_in_txs add column timedout boolean not null default false',
|
||||||
'alter table cash_in_txs add column send_time timestamptz',
|
'alter table cash_in_txs add column send_time timestamptz',
|
||||||
'alter table cash_in_txs add column error_code text',
|
'alter table cash_in_txs add column error_code text',
|
||||||
|
'alter table cash_in_txs add column operator_completed boolean not null default false',
|
||||||
|
'alter table cash_in_txs add column send_pending boolean not null default false',
|
||||||
'alter table cash_out_txs add column device_time bigint not null',
|
'alter table cash_out_txs add column device_time bigint not null',
|
||||||
'alter table cash_out_txs add column timedout boolean not null default false'
|
'alter table cash_out_txs add column timedout boolean not null default false'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
21
migrations/027-tx_errors.js
Normal file
21
migrations/027-tx_errors.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`create table cash_in_actions (
|
||||||
|
id serial primary key,
|
||||||
|
tx_id uuid references cash_in_txs not null,
|
||||||
|
action text not null,
|
||||||
|
error text,
|
||||||
|
error_code text,
|
||||||
|
tx_hash text,
|
||||||
|
success boolean not null,
|
||||||
|
created timestamptz not null default now()
|
||||||
|
)`
|
||||||
|
]
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
40
migrations/028-cash_out_actions.js
Normal file
40
migrations/028-cash_out_actions.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`create table cash_out_actions (
|
||||||
|
id serial primary key,
|
||||||
|
tx_id uuid references cash_out_txs not null,
|
||||||
|
action text not null,
|
||||||
|
to_address text,
|
||||||
|
error text,
|
||||||
|
error_code text,
|
||||||
|
tx_hash text,
|
||||||
|
success boolean not null,
|
||||||
|
provisioned_1 integer,
|
||||||
|
provisioned_2 integer,
|
||||||
|
dispensed_1 integer,
|
||||||
|
dispensed_2 integer,
|
||||||
|
rejected_1 integer,
|
||||||
|
rejected_2 integer,
|
||||||
|
denomination_1 integer,
|
||||||
|
denomination_2 integer,
|
||||||
|
redeem boolean not null default false,
|
||||||
|
device_time bigint,
|
||||||
|
created timestamptz not null default now()
|
||||||
|
)`,
|
||||||
|
'alter table cash_out_txs drop column dispensed_1',
|
||||||
|
'alter table cash_out_txs drop column dispensed_2',
|
||||||
|
'alter table cash_out_txs drop column rejected_1',
|
||||||
|
'alter table cash_out_txs drop column rejected_2',
|
||||||
|
'alter table cash_out_txs drop column denomination_1',
|
||||||
|
'alter table cash_out_txs drop column denomination_2',
|
||||||
|
'alter table cash_out_txs drop column dispense_error',
|
||||||
|
'alter table cash_out_txs add column dispense_confirmed boolean default false'
|
||||||
|
]
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue