lamassu-server/lib/customers.js
2018-03-10 18:59:40 +00:00

331 lines
8.2 KiB
JavaScript

const uuid = require('uuid')
const Pgp = require('pg-promise')()
const _ = require('lodash/fp')
const db = require('./db')
const BN = require('./bn')
const anonymous = require('../lib/constants').anonymousCustomer
const complianceOverrides = require('./compliance_overrides')
const users = require('./users')
const NUM_RESULTS = 20
/**
* Add new customer
*
* @name add
* @function
*
* @param {object} Customer object (with phone number)
*
* @returns {object} Newly created customer
*/
function add (customer) {
const sql = 'insert into customers (id, phone, phone_at) values ($1, $2, now()) returning *'
return db.one(sql, [uuid.v4(), customer.phone])
}
/**
* Get single customer by phone
* Phone numbers are unique per customer
*
* @name get
* @function
*
* @param {string} phone Customer's phone number
*
* @returns {object} Customer
*/
function get (phone) {
const sql = 'select * from customers where phone=$1'
return db.oneOrNone(sql, [phone])
.then(populateDailyVolume)
.then(camelize)
}
/**
* Update customer record
*
* @name update
* @function
*
* @param {string} id Customer's id
* @param {object} data Fields to update
* @param {string} Acting user's token
*
* @returns {Promise} Newly updated Customer
*/
function update (id, data, userToken) {
const formattedData = _.omit(['id'], _.mapKeys(_.snakeCase, data))
const updateData = enhanceAtFields(enhanceOverrideFields(formattedData, userToken))
const sql = Pgp.helpers.update(updateData, _.keys(updateData), 'customers') +
' where id=$1 returning *'
return db.one(sql, [id])
.then(addComplianceOverrides(id, updateData, userToken))
.then(populateOverrideUsernames)
.then(computeStatus)
.then(populateDailyVolume)
.then(camelize)
}
/**
* Get customer by id
*
* @name getById
* @function
*
* @param {string} id Customer's unique id
* @param {string} userToken Acting user's token
*
* @returns {object} Customer found
*/
function getById (id, userToken) {
const sql = 'select * from customers where id=$1'
return db.oneOrNone(sql, [id])
.then(populateOverrideUsernames)
.then(computeStatus)
.then(populateDailyVolume)
.then(camelize)
}
/**
* Get and calculate customer's daily volume
* for both cash_in & cash_out txs
*
* @name getDailyVolume
* @function
*
* @param {string} id Customer's id
* @returns {Bignumber} Customer's daily volume
*/
function getDailyVolume (id) {
return Promise.all([
db.one(`select coalesce(sum(fiat), 0) as total from cash_in_txs
where customer_id=$1
and created > now() - interval '1 day'`, [id]),
db.one(`select coalesce(sum(fiat), 0) as total from cash_out_txs
where customer_id=$1
and created > now() - interval '1 day'`, [id])
]).then(([cashInTotal, cashOutTotal]) => {
return BN(cashInTotal.total).add(cashOutTotal.total)
})
}
/**
* Camelize customer fields
* Note: return null if customer is undefined
*
* @name camelize
* @function
*
* @param {object} customer Customer with snake_case fields
* @returns {object} Camelized Customer object
*/
function camelize (customer) {
return customer ? _.mapKeys(_.camelCase, customer) : null
}
/**
* Populate customer object
* with dailyVolume information
*
* @name populateDailyVolume
* @function
*
* @param {object} customer Customer object
* @returns {object} Customer object populated with dailyVolume
*/
function populateDailyVolume (customer) {
if (!customer) return
return getDailyVolume(customer.id).then(dailyVolume => {
return _.set('daily_volume', dailyVolume, customer)
})
}
/**
* Get all available complianceTypes
* that can be overriden (excluding hard_limit)
*
* @name getComplianceTypes
* @function
*
* @returns {array} Array of compliance types' names
*/
function getComplianceTypes () {
return [
'sms',
'id_card_data',
'id_card_photo',
'front_camera',
'sanctions',
'authorized' ]
}
function enhanceAtFields (fields) {
const updateableFields = [
'id_card_data',
'id_card_photo',
'front_camera',
'sanctions',
'authorized'
]
const updatedFields = _.intersection(updateableFields, _.keys(fields))
const atFields = _.fromPairs(_.map(f => [`${f}_at`, 'now()^'], updatedFields))
return _.merge(fields, atFields)
}
/**
* Add *override_by and *override_at fields with acting user's token
* and date of override respectively before saving to db.
*
* @name enhanceOverrideFields
* @function
*
* @param {object} fields Override fields to be enhanced
* @param {string} userToken Acting user's token
* @returns {object} fields enhanced with *_by and *_at fields
*/
function enhanceOverrideFields (fields, userToken) {
if (!userToken) return fields
// Populate with computedFields (user who overrode and overriden timestamps date)
return _.reduce(_.assign, fields, _.map((type) => {
return (fields[type + '_override'])
? {
[type + '_override_by']: userToken,
[type + '_override_at']: 'now()^'
}
: {}
}, getComplianceTypes()))
}
/**
* Save new compliance override records
*
* Take the override fields that are modified in customer and create
* a compliance override record in db for each compliance type.
*
* @name addComplianceOverrides
* @function
*
* @param {string} id Customer's id
* @param {object} customer Customer that is updating
* @param {string} userToken Acting user's token
*
* @returns {promise} Result from compliance_overrides creation
*/
function addComplianceOverrides (id, customer, userToken) {
// Prepare compliance overrides to save
const overrides = _.map(field => {
const complianceName = field + '_override'
return (customer[complianceName]) ? {
customerId: id,
complianceType: field,
overrideBy: userToken,
verification: customer[complianceName]
} : null
}, getComplianceTypes())
// Save all the updated override fields
return Promise.all(_.map(complianceOverrides.add, _.compact(overrides)))
.then(() => customer)
}
/**
* Compute status field
*
* Status field indicates the last
* compliance user has verified
*
* @name computeStatus
* @function
*
* @param {object} Customer object
* @returns {object} Customer populated with status field
*/
function computeStatus (customer) {
if (!customer) return null
/**
* Populate with status field
*
*/
const status = _.maxBy('value', [{
label: 'Phone',
value: customer.phone_at
}, {
label: 'ID card',
value: customer.id_card_at
}, {
label: 'Sanctions',
value: customer.sanctions_at
}, {
label: 'Front camera',
value: customer.front_camera_at
}, {
label: 'ID card image',
value: customer.id_card_image_at
}])
return _.assign(customer, {
status: status.label
})
}
/**
* Populate the customer object with user names
* for override fields ( fields ending with _override_by )
*
* @name populateOverrideUsernames
* @function
*
* @param {object} customer Customer object to populate
* @returns {promise} Customer with populated *by_name fields
*/
function populateOverrideUsernames (customer) {
const fieldsToUpdate = _.map(field => {
return {
token: customer[field + '_override_by'],
field: field + '_override_by_name'
}
}, getComplianceTypes())
const queryTokens = _.map('token', fieldsToUpdate)
return users.getByIds(queryTokens)
.then(usersList => {
return _.map(userField => {
const user = _.find({token: userField.token}, usersList)
return {
[userField.field]: user ? user.name : null
}
}, fieldsToUpdate)
})
.then(_.reduce(_.assign, customer))
}
/**
* Query all customers
*
* Add status as computed column,
* which will indicate the name of the latest
* compliance verfication completed by user.
*
* @returns {array} Array of customers populated with status field
*/
function batch () {
const sql = `select * from customers
where id != $1
order by created desc limit $2`
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
.then(customers => Promise.all(_.map(customer => {
return populateOverrideUsernames(customer)
.then(computeStatus)
.then(populateDailyVolume)
.then(camelize)
}, customers)))
}
module.exports = { add, get, batch, getById, update }