feat: transactions table
This commit is contained in:
parent
1ead9fe359
commit
d6166ce752
29 changed files with 1204 additions and 726 deletions
|
|
@ -3,6 +3,9 @@ const anonymous = require('../../../constants').anonymousCustomer
|
|||
const customers = require('../../../customers')
|
||||
const customerNotes = require('../../../customer-notes')
|
||||
const machineLoader = require('../../../machine-loader')
|
||||
const {
|
||||
customers: { searchCustomers },
|
||||
} = require('typesafe-db')
|
||||
|
||||
const addLastUsedMachineName = customer =>
|
||||
(customer.lastUsedMachine
|
||||
|
|
@ -20,6 +23,8 @@ const resolvers = {
|
|||
customers: () => customers.getCustomersList(),
|
||||
customer: (...[, { customerId }]) =>
|
||||
customers.getCustomerById(customerId).then(addLastUsedMachineName),
|
||||
searchCustomers: (...[, { searchTerm, limit = 20 }]) =>
|
||||
searchCustomers(searchTerm, limit),
|
||||
},
|
||||
Mutation: {
|
||||
setCustomer: (root, { customerId, customerInput }, context) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
const DataLoader = require('dataloader')
|
||||
const { parseAsync } = require('json2csv')
|
||||
|
||||
const filters = require('../../filters')
|
||||
|
|
@ -8,15 +7,7 @@ const transactions = require('../../services/transactions')
|
|||
const anonymous = require('../../../constants').anonymousCustomer
|
||||
const logDateFormat = require('../../../logs').logDateFormat
|
||||
|
||||
const transactionsLoader = new DataLoader(
|
||||
ids => transactions.getCustomerTransactionsBatch(ids),
|
||||
{ cache: false },
|
||||
)
|
||||
|
||||
const resolvers = {
|
||||
Customer: {
|
||||
transactions: parent => transactionsLoader.load(parent.id),
|
||||
},
|
||||
Transaction: {
|
||||
isAnonymous: parent => parent.customerId === anonymous.uuid,
|
||||
},
|
||||
|
|
@ -32,6 +23,7 @@ const resolvers = {
|
|||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
customerId,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
|
|
@ -41,7 +33,7 @@ const resolvers = {
|
|||
},
|
||||
]
|
||||
) =>
|
||||
transactions.batch(
|
||||
transactions.batch({
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
|
|
@ -49,13 +41,14 @@ const resolvers = {
|
|||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
customerId,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
status,
|
||||
swept,
|
||||
excludeTestingCustomers,
|
||||
),
|
||||
}),
|
||||
transactionsCsv: (
|
||||
...[
|
||||
,
|
||||
|
|
@ -67,6 +60,7 @@ const resolvers = {
|
|||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
customerId,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
|
|
@ -79,7 +73,7 @@ const resolvers = {
|
|||
]
|
||||
) =>
|
||||
transactions
|
||||
.batch(
|
||||
.batch({
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
|
|
@ -87,6 +81,7 @@ const resolvers = {
|
|||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
customerId,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
|
|
@ -94,7 +89,7 @@ const resolvers = {
|
|||
swept,
|
||||
excludeTestingCustomers,
|
||||
simplified,
|
||||
)
|
||||
})
|
||||
.then(data =>
|
||||
parseAsync(
|
||||
logDateFormat(timezone, data, [
|
||||
|
|
|
|||
|
|
@ -94,6 +94,13 @@ const typeDef = gql`
|
|||
value: String
|
||||
}
|
||||
|
||||
type CustomerSearchResult {
|
||||
id: ID!
|
||||
name: String
|
||||
phone: String
|
||||
email: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
customers(
|
||||
phone: String
|
||||
|
|
@ -104,6 +111,8 @@ const typeDef = gql`
|
|||
): [Customer] @auth
|
||||
customer(customerId: ID!): Customer @auth
|
||||
customerFilters: [Filter] @auth
|
||||
searchCustomers(searchTerm: String!, limit: Int): [CustomerSearchResult]
|
||||
@auth
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
|
|
|||
|
|
@ -25,24 +25,21 @@ const typeDef = gql`
|
|||
sendPending: Boolean
|
||||
fixedFee: String
|
||||
minimumTx: Float
|
||||
customerId: ID
|
||||
isAnonymous: Boolean
|
||||
txVersion: Int!
|
||||
termsAccepted: Boolean
|
||||
commissionPercentage: String
|
||||
rawTickerPrice: String
|
||||
isPaperWallet: Boolean
|
||||
customerPhone: String
|
||||
customerEmail: String
|
||||
customerIdCardDataNumber: String
|
||||
customerIdCardDataExpiration: DateTimeISO
|
||||
customerIdCardData: JSONObject
|
||||
customerName: String
|
||||
customerFrontCameraPath: String
|
||||
customerIdCardPhotoPath: String
|
||||
expired: Boolean
|
||||
machineName: String
|
||||
discount: Int
|
||||
customerId: ID
|
||||
customerPhone: String
|
||||
customerEmail: String
|
||||
customerIdCardData: JSONObject
|
||||
customerFrontCameraPath: String
|
||||
customerIdCardPhotoPath: String
|
||||
txCustomerPhotoPath: String
|
||||
txCustomerPhotoAt: DateTimeISO
|
||||
batched: Boolean
|
||||
|
|
@ -51,6 +48,12 @@ const typeDef = gql`
|
|||
walletScore: Int
|
||||
profit: String
|
||||
swept: Boolean
|
||||
status: String
|
||||
paginationStats: PaginationStats
|
||||
}
|
||||
|
||||
type PaginationStats {
|
||||
totalCount: Int
|
||||
}
|
||||
|
||||
type Filter {
|
||||
|
|
@ -68,6 +71,7 @@ const typeDef = gql`
|
|||
txClass: String
|
||||
deviceId: String
|
||||
customerName: String
|
||||
customerId: ID
|
||||
fiatCode: String
|
||||
cryptoCode: String
|
||||
toAddress: String
|
||||
|
|
@ -83,6 +87,7 @@ const typeDef = gql`
|
|||
txClass: String
|
||||
deviceId: String
|
||||
customerName: String
|
||||
customerId: String
|
||||
fiatCode: String
|
||||
cryptoCode: String
|
||||
toAddress: String
|
||||
|
|
|
|||
|
|
@ -1,230 +1,72 @@
|
|||
const _ = require('lodash/fp')
|
||||
const pgp = require('pg-promise')()
|
||||
|
||||
const db = require('../../db')
|
||||
const BN = require('../../bn')
|
||||
const { utils: coinUtils } = require('@lamassu/coins')
|
||||
const tx = require('../../tx')
|
||||
const cashInTx = require('../../cash-in/cash-in-tx')
|
||||
const { REDEEMABLE_AGE } = require('../../cash-out/cash-out-helper')
|
||||
const {
|
||||
REDEEMABLE_AGE,
|
||||
CASH_OUT_TRANSACTION_STATES,
|
||||
} = require('../../cash-out/cash-out-helper')
|
||||
|
||||
const NUM_RESULTS = 1000
|
||||
transactions: { getTransactionList },
|
||||
} = require('typesafe-db')
|
||||
|
||||
function addProfits(txs) {
|
||||
return _.map(it => {
|
||||
const profit = getProfit(it).toString()
|
||||
return _.set('profit', profit, it)
|
||||
}, txs)
|
||||
return _.map(
|
||||
it => ({
|
||||
...it,
|
||||
profit: getProfit(it).toString(),
|
||||
cryptoAmount: getCryptoAmount(it).toString(),
|
||||
}),
|
||||
txs,
|
||||
)
|
||||
}
|
||||
|
||||
const camelize = _.mapKeys(_.camelCase)
|
||||
|
||||
const DEVICE_NAME_QUERY = `
|
||||
CASE
|
||||
WHEN ud.name IS NOT NULL THEN ud.name || ' (unpaired)'
|
||||
WHEN d.name IS NOT NULL THEN d.name
|
||||
ELSE 'Unpaired'
|
||||
END AS machine_name
|
||||
`
|
||||
|
||||
const DEVICE_NAME_JOINS = `
|
||||
LEFT JOIN devices d ON txs.device_id = d.device_id
|
||||
LEFT JOIN (
|
||||
SELECT device_id, name, unpaired, paired
|
||||
FROM unpaired_devices
|
||||
) ud ON txs.device_id = ud.device_id
|
||||
AND ud.unpaired >= txs.created
|
||||
AND (txs.created >= ud.paired)
|
||||
`
|
||||
|
||||
function batch(
|
||||
function batch({
|
||||
from = new Date(0).toISOString(),
|
||||
until = new Date().toISOString(),
|
||||
limit = null,
|
||||
offset = 0,
|
||||
txClass = null,
|
||||
deviceId = null,
|
||||
customerName = null,
|
||||
fiatCode = null,
|
||||
customerId = null,
|
||||
cryptoCode = null,
|
||||
toAddress = null,
|
||||
status = null,
|
||||
swept = null,
|
||||
excludeTestingCustomers = false,
|
||||
simplified,
|
||||
) {
|
||||
}) {
|
||||
const isCsvExport = _.isBoolean(simplified)
|
||||
const packager = _.flow(
|
||||
_.flatten,
|
||||
_.orderBy(_.property('created'), ['desc']),
|
||||
_.map(
|
||||
_.flow(
|
||||
camelize,
|
||||
_.mapKeys(k => (k == 'cashInFee' ? 'fixedFee' : k)),
|
||||
return (
|
||||
Promise.all([
|
||||
getTransactionList(
|
||||
{
|
||||
from,
|
||||
until,
|
||||
cryptoCode,
|
||||
txClass,
|
||||
deviceId,
|
||||
toAddress,
|
||||
customerId,
|
||||
swept,
|
||||
status,
|
||||
excludeTestingCustomers,
|
||||
},
|
||||
{ limit, offset },
|
||||
),
|
||||
),
|
||||
addProfits,
|
||||
])
|
||||
// Promise.all(promises)
|
||||
.then(it => addProfits(it[0]))
|
||||
.then(res =>
|
||||
!isCsvExport
|
||||
? res
|
||||
: // GQL transactions and transactionsCsv both use this function and
|
||||
// if we don't check for the correct simplified value, the Transactions page polling
|
||||
// will continuously build a csv in the background
|
||||
simplified
|
||||
? simplifiedBatch(res)
|
||||
: advancedBatch(res),
|
||||
)
|
||||
)
|
||||
|
||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
array_to_string(array[c.id_card_data::json->>'firstName', c.id_card_data::json->>'lastName'], ' ') AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
txs.tx_customer_photo_at AS tx_customer_photo_at,
|
||||
txs.tx_customer_photo_path AS tx_customer_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired,
|
||||
tb.error_message AS batch_error,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM (SELECT *, ${cashInTx.TRANSACTION_STATES} AS txStatus FROM cash_in_txs) AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE txs.created >= $2 AND txs.created <= $3
|
||||
AND ($6 is null or $6 = 'Cash In')
|
||||
AND ($7 is null or txs.device_id = $7)
|
||||
AND ($8 is null or concat(c.id_card_data::json->>'firstName', ' ', c.id_card_data::json->>'lastName') = $8)
|
||||
AND ($9 is null or txs.fiat_code = $9)
|
||||
AND ($10 is null or txs.crypto_code = $10)
|
||||
AND ($11 is null or txs.to_address = $11)
|
||||
AND ($12 is null or txs.txStatus = $12)
|
||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||
${isCsvExport && !simplified ? '' : 'AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)'}
|
||||
ORDER BY created DESC limit $4 offset $5`
|
||||
|
||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||
txs.*,
|
||||
actions.tx_hash,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
array_to_string(array[c.id_card_data::json->>'firstName', c.id_card_data::json->>'lastName'], ' ') AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
txs.tx_customer_photo_at AS tx_customer_photo_at,
|
||||
txs.tx_customer_photo_path AS tx_customer_photo_path,
|
||||
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $1) AS expired,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM (SELECT *, ${CASH_OUT_TRANSACTION_STATES} AS txStatus FROM cash_out_txs) txs
|
||||
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
|
||||
AND actions.action = 'provisionAddress'
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
WHERE txs.created >= $2 AND txs.created <= $3
|
||||
AND ($6 is null or $6 = 'Cash Out')
|
||||
AND ($7 is null or txs.device_id = $7)
|
||||
AND ($8 is null or concat(c.id_card_data::json->>'firstName', ' ', c.id_card_data::json->>'lastName') = $8)
|
||||
AND ($9 is null or txs.fiat_code = $9)
|
||||
AND ($10 is null or txs.crypto_code = $10)
|
||||
AND ($11 is null or txs.to_address = $11)
|
||||
AND ($12 is null or txs.txStatus = $12)
|
||||
AND ($13 is null or txs.swept = $13)
|
||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||
${isCsvExport ? '' : 'AND fiat > 0'}
|
||||
ORDER BY created DESC limit $4 offset $5`
|
||||
|
||||
// The swept filter is cash-out only, so omit the cash-in query entirely
|
||||
const hasCashInOnlyFilters = false
|
||||
const hasCashOutOnlyFilters = !_.isNil(swept)
|
||||
|
||||
let promises
|
||||
|
||||
if (hasCashInOnlyFilters && hasCashOutOnlyFilters) {
|
||||
throw new Error(
|
||||
'Trying to filter transactions with mutually exclusive filters',
|
||||
)
|
||||
}
|
||||
|
||||
if (hasCashInOnlyFilters) {
|
||||
promises = [
|
||||
db.any(cashInSql, [
|
||||
cashInTx.PENDING_INTERVAL,
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
offset,
|
||||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
status,
|
||||
]),
|
||||
]
|
||||
} else if (hasCashOutOnlyFilters) {
|
||||
promises = [
|
||||
db.any(cashOutSql, [
|
||||
REDEEMABLE_AGE,
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
offset,
|
||||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
status,
|
||||
swept,
|
||||
]),
|
||||
]
|
||||
} else {
|
||||
promises = [
|
||||
db.any(cashInSql, [
|
||||
cashInTx.PENDING_INTERVAL,
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
offset,
|
||||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
status,
|
||||
]),
|
||||
db.any(cashOutSql, [
|
||||
REDEEMABLE_AGE,
|
||||
from,
|
||||
until,
|
||||
limit,
|
||||
offset,
|
||||
txClass,
|
||||
deviceId,
|
||||
customerName,
|
||||
fiatCode,
|
||||
cryptoCode,
|
||||
toAddress,
|
||||
status,
|
||||
swept,
|
||||
]),
|
||||
]
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(packager)
|
||||
.then(res =>
|
||||
!isCsvExport
|
||||
? res
|
||||
: // GQL transactions and transactionsCsv both use this function and
|
||||
// if we don't check for the correct simplified value, the Transactions page polling
|
||||
// will continuously build a csv in the background
|
||||
simplified
|
||||
? simplifiedBatch(res)
|
||||
: advancedBatch(res),
|
||||
)
|
||||
}
|
||||
|
||||
function advancedBatch(data) {
|
||||
|
|
@ -239,7 +81,7 @@ function advancedBatch(data) {
|
|||
'fiatCode',
|
||||
'fee',
|
||||
'status',
|
||||
'fiatProfit',
|
||||
'profit',
|
||||
'cryptoAmount',
|
||||
'dispense',
|
||||
'notified',
|
||||
|
|
@ -300,9 +142,6 @@ function advancedBatch(data) {
|
|||
|
||||
const addAdvancedFields = _.map(it => ({
|
||||
...it,
|
||||
status: getStatus(it),
|
||||
fiatProfit: getProfit(it).toString(),
|
||||
cryptoAmount: getCryptoAmount(it).toString(),
|
||||
fixedFee: it.fixedFee ?? null,
|
||||
fee: it.fee ?? null,
|
||||
}))
|
||||
|
|
@ -328,18 +167,11 @@ function simplifiedBatch(data) {
|
|||
'dispense',
|
||||
'error',
|
||||
'status',
|
||||
'fiatProfit',
|
||||
'profit',
|
||||
'cryptoAmount',
|
||||
]
|
||||
|
||||
const addSimplifiedFields = _.map(it => ({
|
||||
...it,
|
||||
status: getStatus(it),
|
||||
fiatProfit: getProfit(it).toString(),
|
||||
cryptoAmount: getCryptoAmount(it).toString(),
|
||||
}))
|
||||
|
||||
return _.compose(_.map(_.pick(fields)), addSimplifiedFields)(data)
|
||||
return _.map(_.pick(fields))(data)
|
||||
}
|
||||
|
||||
const getCryptoAmount = it =>
|
||||
|
|
@ -363,150 +195,6 @@ const getProfit = it => {
|
|||
: calcCashOutProfit(fiat, crypto, tickerPrice)
|
||||
}
|
||||
|
||||
const getCashOutStatus = it => {
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.dispense) return 'Success'
|
||||
if (it.expired) return 'Expired'
|
||||
return 'Pending'
|
||||
}
|
||||
|
||||
const getCashInStatus = it => {
|
||||
if (it.operatorCompleted) return 'Cancelled'
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.batchError) return 'Error'
|
||||
if (it.sendConfirmed) return 'Sent'
|
||||
if (it.expired) return 'Expired'
|
||||
return 'Pending'
|
||||
}
|
||||
|
||||
const getStatus = it => {
|
||||
if (it.txClass === 'cashOut') {
|
||||
return getCashOutStatus(it)
|
||||
}
|
||||
return getCashInStatus(it)
|
||||
}
|
||||
|
||||
function getCustomerTransactionsBatch(ids) {
|
||||
const packager = _.flow(
|
||||
it => {
|
||||
return it
|
||||
},
|
||||
_.flatten,
|
||||
_.orderBy(_.property('created'), ['desc']),
|
||||
_.map(camelize),
|
||||
)
|
||||
|
||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $2)) AS expired,
|
||||
tb.error_message AS batch_error,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM cash_in_txs AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE c.id IN ($1^)
|
||||
ORDER BY created DESC limit $3`
|
||||
|
||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||
txs.*,
|
||||
actions.tx_hash,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $3) AS expired,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM cash_out_txs txs
|
||||
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
|
||||
AND actions.action = 'provisionAddress'
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
WHERE c.id IN ($1^)
|
||||
ORDER BY created DESC limit $2`
|
||||
return Promise.all([
|
||||
db.any(cashInSql, [
|
||||
_.map(pgp.as.text, ids).join(','),
|
||||
cashInTx.PENDING_INTERVAL,
|
||||
NUM_RESULTS,
|
||||
]),
|
||||
db.any(cashOutSql, [
|
||||
_.map(pgp.as.text, ids).join(','),
|
||||
NUM_RESULTS,
|
||||
REDEEMABLE_AGE,
|
||||
]),
|
||||
])
|
||||
.then(packager)
|
||||
.then(transactions => {
|
||||
const transactionMap = _.groupBy('customerId', transactions)
|
||||
return ids.map(id => transactionMap[id])
|
||||
})
|
||||
}
|
||||
|
||||
function single(txId) {
|
||||
const packager = _.flow(_.compact, _.map(camelize))
|
||||
|
||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired,
|
||||
tb.error_message AS batch_error,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM cash_in_txs AS txs
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
LEFT OUTER JOIN transaction_batches tb ON txs.batch_id = tb.id
|
||||
WHERE id=$2`
|
||||
|
||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||
txs.*,
|
||||
actions.tx_hash,
|
||||
c.phone AS customer_phone,
|
||||
c.email AS customer_email,
|
||||
c.id_card_data_number AS customer_id_card_data_number,
|
||||
c.id_card_data_expiration AS customer_id_card_data_expiration,
|
||||
c.id_card_data AS customer_id_card_data,
|
||||
c.name AS customer_name,
|
||||
c.front_camera_path AS customer_front_camera_path,
|
||||
c.id_card_photo_path AS customer_id_card_photo_path,
|
||||
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $2) AS expired,
|
||||
${DEVICE_NAME_QUERY}
|
||||
FROM cash_out_txs txs
|
||||
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
|
||||
AND actions.action = 'provisionAddress'
|
||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||
${DEVICE_NAME_JOINS}
|
||||
WHERE id=$1`
|
||||
|
||||
return Promise.all([
|
||||
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
|
||||
db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE]),
|
||||
])
|
||||
.then(packager)
|
||||
.then(_.head)
|
||||
}
|
||||
|
||||
function cancel(txId) {
|
||||
return tx.cancel(txId).then(() => single(txId))
|
||||
}
|
||||
|
||||
function getTx(txId, txClass) {
|
||||
const cashInSql = `select 'cashIn' as tx_class, txs.*,
|
||||
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
|
||||
|
|
@ -558,9 +246,6 @@ function updateTxCustomerPhoto(customerId, txId, direction, data) {
|
|||
|
||||
module.exports = {
|
||||
batch,
|
||||
single,
|
||||
cancel,
|
||||
getCustomerTransactionsBatch,
|
||||
getTx,
|
||||
getTxAssociatedData,
|
||||
updateTxCustomerPhoto,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const PUBLISH_TIME = 3 * SECONDS
|
|||
const AUTHORIZE_TIME = PUBLISH_TIME + 5 * SECONDS
|
||||
const CONFIRM_TIME = AUTHORIZE_TIME + 10 * SECONDS
|
||||
const SUPPORTED_COINS = coinUtils.cryptoCurrencies()
|
||||
const SUPPORTS_BATCHING = true
|
||||
|
||||
let t0
|
||||
|
||||
|
|
@ -162,6 +163,7 @@ function checkBlockchainStatus(cryptoCode) {
|
|||
|
||||
module.exports = {
|
||||
NAME,
|
||||
SUPPORTS_BATCHING,
|
||||
balance,
|
||||
sendCoinsBatch,
|
||||
sendCoins,
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ const compliance = require('../compliance')
|
|||
const complianceTriggers = require('../compliance-triggers')
|
||||
const configManager = require('../new-config-manager')
|
||||
const customers = require('../customers')
|
||||
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 {
|
||||
getTx,
|
||||
updateTxCustomerPhoto: txsUpdateTxCustomerPhoto,
|
||||
} = require('../new-admin/services/transactions.js')
|
||||
const machineLoader = require('../machine-loader')
|
||||
const { loadLatestConfig } = require('../new-settings-loader')
|
||||
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
|
||||
|
|
@ -207,13 +209,13 @@ function updateTxCustomerPhoto(req, res, next) {
|
|||
const tcPhotoData = req.body.tcPhotoData
|
||||
const direction = req.body.direction
|
||||
|
||||
Promise.all([customers.getById(customerId), txs.getTx(txId, direction)])
|
||||
Promise.all([customers.getById(customerId), getTx(txId, direction)])
|
||||
.then(([customer, tx]) => {
|
||||
if (!customer || !tx) return
|
||||
return customers
|
||||
.updateTxCustomerPhoto(tcPhotoData)
|
||||
.then(newPatch =>
|
||||
txs.updateTxCustomerPhoto(customerId, txId, direction, newPatch),
|
||||
txsUpdateTxCustomerPhoto(customerId, txId, direction, newPatch),
|
||||
)
|
||||
})
|
||||
.then(() => respond(req, res, {}))
|
||||
|
|
|
|||
|
|
@ -59,22 +59,6 @@ function massage(tx) {
|
|||
return mapper(tx)
|
||||
}
|
||||
|
||||
function cancel(txId) {
|
||||
const promises = [
|
||||
CashInTx.cancel(txId)
|
||||
.then(() => true)
|
||||
.catch(() => false),
|
||||
CashOutTx.cancel(txId)
|
||||
.then(() => true)
|
||||
.catch(() => false),
|
||||
]
|
||||
|
||||
return Promise.all(promises).then(r => {
|
||||
if (_.some(r)) return
|
||||
throw new Error('No such transaction')
|
||||
})
|
||||
}
|
||||
|
||||
function customerHistory(customerId, thresholdDays) {
|
||||
const sql = `SELECT ch.id, ch.created, ch.fiat, ch.direction FROM (
|
||||
SELECT txIn.id, txIn.created, txIn.fiat, 'cashIn' AS direction,
|
||||
|
|
@ -99,4 +83,4 @@ function customerHistory(customerId, thresholdDays) {
|
|||
return db.any(sql, [customerId, `${days} days`, '60 minutes', REDEEMABLE_AGE])
|
||||
}
|
||||
|
||||
module.exports = { post, cancel, customerHistory }
|
||||
module.exports = { post, customerHistory }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue