diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index a98f7a31..cae14a0d 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -51,7 +51,7 @@ const mapValuesWithKey = _.mapValues.convert({cap: false}) function convertBigNumFields (obj) { const convert = (value, key) => { - if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat' ])) { + if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat', 'fixedFee', 'fixedFeeCrypto' ])) { return value.toString() } diff --git a/lib/coinatmradar/coinatmradar.js b/lib/coinatmradar/coinatmradar.js index f6ead34a..88b9eaf4 100644 --- a/lib/coinatmradar/coinatmradar.js +++ b/lib/coinatmradar/coinatmradar.js @@ -29,6 +29,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) { const cashInFee = showCommissions ? commissions.cashIn / 100 : null const cashOutFee = showCommissions ? commissions.cashOut / 100 : null const cashInFixedFee = showCommissions ? commissions.fixedFee : null + const cashOutFixedFee = showCommissions ? commissions.cashOutFixedFee : null const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null @@ -37,6 +38,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) { cashInFee, cashOutFee, cashInFixedFee, + cashOutFixedFee, cashInRate, cashOutRate } diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index cf4f4da0..1270f1f3 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -89,6 +89,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings 'cashInCommission', 'cashInFee', 'cashOutCommission', + 'cashOutFee', 'cryptoCode', 'cryptoCodeDisplay', 'cryptoNetwork', diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 466391f9..89296c6c 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -6,6 +6,7 @@ type Coin { display: String! minimumTx: String! cashInFee: String! + cashOutFee: String! cashInCommission: String! cashOutCommission: String! cryptoNetwork: String! diff --git a/lib/new-admin/graphql/types/transaction.type.js b/lib/new-admin/graphql/types/transaction.type.js index 8c43f49e..ae57a365 100644 --- a/lib/new-admin/graphql/types/transaction.type.js +++ b/lib/new-admin/graphql/types/transaction.type.js @@ -23,7 +23,7 @@ const typeDef = gql` errorCode: String operatorCompleted: Boolean sendPending: Boolean - cashInFee: String + fixedFee: String minimumTx: Float customerId: ID isAnonymous: Boolean diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js index 733fcad9..546ef0b3 100644 --- a/lib/new-admin/services/transactions.js +++ b/lib/new-admin/services/transactions.js @@ -50,7 +50,19 @@ function batch ( excludeTestingCustomers = false, simplified ) { - const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addProfits, addNames) + const packager = _.flow( + _.flatten, + _.orderBy(_.property('created'), ['desc']), + _.map(_.flow( + camelize, + _.mapKeys(k => + k == 'cashInFee' ? 'fixedFee' : + k + ) + )), + addProfits, + addNames + ) const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*, c.phone AS customer_phone, @@ -153,7 +165,7 @@ function batch ( function advancedBatch (data) { const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms', 'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount', - 'dispense', 'notified', 'redeem', 'phone', 'error', + 'dispense', 'notified', 'redeem', 'phone', 'error', 'fixedFee', 'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout', 'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4', 'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6', @@ -169,7 +181,9 @@ function advancedBatch (data) { ...it, status: getStatus(it), fiatProfit: getProfit(it).toString(), - cryptoAmount: getCryptoAmount(it).toString() + cryptoAmount: getCryptoAmount(it).toString(), + fixedFee: it.fixedFee ?? null, + fee: it.fee ?? null, })) return _.compose(_.map(_.pick(fields)), addAdvancedFields)(data) diff --git a/lib/plugins.js b/lib/plugins.js index f1587f07..d5bfcb4f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -249,6 +249,7 @@ function plugins (settings, deviceId) { const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const minimumTx = new BN(commissions.minimumTx) const cashInFee = new BN(commissions.fixedFee) + const cashOutFee = new BN(commissions.cashOutFixedFee) const cashInCommission = new BN(commissions.cashIn) const cashOutCommission = _.isNumber(commissions.cashOut) ? new BN(commissions.cashOut) : null const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) @@ -261,6 +262,7 @@ function plugins (settings, deviceId) { isCashInOnly: Boolean(cryptoRec.isCashinOnly), minimumTx: BN.max(minimumTx, cashInFee), cashInFee, + cashOutFee, cashInCommission, cashOutCommission, cryptoNetwork, diff --git a/lib/tx.js b/lib/tx.js index 4a1d9b3c..034b8f3b 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -40,6 +40,7 @@ function massage (tx, pi) { : { cryptoAtoms: new BN(r.cryptoAtoms), fiat: new BN(r.fiat), + fixedFee: new BN(r.fixedFee), rawTickerPrice: r.rawTickerPrice ? new BN(r.rawTickerPrice) : null, commissionPercentage: new BN(r.commissionPercentage) } diff --git a/migrations/1732790112740-add-cashout-fee-to-cash_out_txs.js b/migrations/1732790112740-add-cashout-fee-to-cash_out_txs.js new file mode 100644 index 00000000..ad5df91b --- /dev/null +++ b/migrations/1732790112740-add-cashout-fee-to-cash_out_txs.js @@ -0,0 +1,7 @@ +const db = require('./db') + +exports.up = next => db.multi([ + 'ALTER TABLE cash_out_txs ADD COLUMN fixed_fee numeric(14, 5) NOT NULL DEFAULT 0;' +], next) + +exports.down = next => next() diff --git a/migrations/1732790112741-add-cashout-fee-to-user_config.js b/migrations/1732790112741-add-cashout-fee-to-user_config.js new file mode 100644 index 00000000..92296e38 --- /dev/null +++ b/migrations/1732790112741-add-cashout-fee-to-user_config.js @@ -0,0 +1,7 @@ +const { saveConfig } = require('../lib/new-settings-loader') + +exports.up = next => saveConfig({ 'commissions_cashOutFixedFee': 0 }) + .then(next) + .catch(next) + +exports.down = next => next() diff --git a/new-lamassu-admin/src/components/editableTable/Header.js b/new-lamassu-admin/src/components/editableTable/Header.js index b6ca9b2c..2c422e43 100644 --- a/new-lamassu-admin/src/components/editableTable/Header.js +++ b/new-lamassu-admin/src/components/editableTable/Header.js @@ -22,22 +22,27 @@ const styles = { const useStyles = makeStyles(styles) const groupSecondHeader = elements => { - const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements) - - if (!toSHeader.length) { - return [elements, THead] - } - - const index = R.indexOf(toSHeader[0], elements) - const width = R.compose(R.sum, R.map(R.path(['width'])))(toSHeader) - - const innerElements = R.insert( - index, - { width, elements: toSHeader, name: toSHeader[0].doubleHeader }, - noSHeader + const doubleHeader = R.prop('doubleHeader') + const sameDoubleHeader = (a, b) => doubleHeader(a) === doubleHeader(b) + const group = R.pipe( + R.groupWith(sameDoubleHeader), + R.map(group => + R.isNil(doubleHeader(group[0])) // No doubleHeader + ? group + : [ + { + width: R.sum(R.map(R.prop('width'), group)), + elements: group, + name: doubleHeader(group[0]) + } + ] + ), + R.reduce(R.concat, []) ) - return [innerElements, TDoubleLevelHead] + return R.all(R.pipe(doubleHeader, R.isNil), elements) + ? [elements, THead] + : [group(elements), TDoubleLevelHead] } const Header = () => { diff --git a/new-lamassu-admin/src/pages/Analytics/Analytics.js b/new-lamassu-admin/src/pages/Analytics/Analytics.js index 46d85572..34711b36 100644 --- a/new-lamassu-admin/src/pages/Analytics/Analytics.js +++ b/new-lamassu-admin/src/pages/Analytics/Analytics.js @@ -77,7 +77,7 @@ const GET_TRANSACTIONS = gql` hasError: error deviceId fiat - cashInFee + fixedFee fiatCode cryptoAtoms cryptoCode diff --git a/new-lamassu-admin/src/pages/Commissions/helper.js b/new-lamassu-admin/src/pages/Commissions/helper.js index 6418afa4..5447d2c7 100644 --- a/new-lamassu-admin/src/pages/Commissions/helper.js +++ b/new-lamassu-admin/src/pages/Commissions/helper.js @@ -91,7 +91,7 @@ const getOverridesFields = (getData, currency, auxElements) => { }, { name: 'cryptoCurrencies', - width: 280, + width: 145, size: 'sm', view: displayCodeArray(cryptoData), input: Autocomplete, @@ -108,7 +108,7 @@ const getOverridesFields = (getData, currency, auxElements) => { header: cashInHeader, name: 'cashIn', display: 'Cash-in', - width: 130, + width: 123, input: NumberInput, textAlign: 'right', suffix: '%', @@ -121,7 +121,7 @@ const getOverridesFields = (getData, currency, auxElements) => { header: cashOutHeader, name: 'cashOut', display: 'Cash-out', - width: 130, + width: 127, input: NumberInput, textAlign: 'right', suffix: '%', @@ -133,7 +133,7 @@ const getOverridesFields = (getData, currency, auxElements) => { { name: 'fixedFee', display: 'Fixed fee', - width: 144, + width: 126, input: NumberInput, doubleHeader: 'Cash-in only', textAlign: 'right', @@ -146,7 +146,7 @@ const getOverridesFields = (getData, currency, auxElements) => { { name: 'minimumTx', display: 'Minimum Tx', - width: 169, + width: 140, doubleHeader: 'Cash-in only', textAlign: 'center', editingAlign: 'right', @@ -156,6 +156,20 @@ const getOverridesFields = (getData, currency, auxElements) => { inputProps: { decimalPlaces: 2 } + }, + { + name: 'cashOutFixedFee', + display: 'Fixed fee', + width: 134, + doubleHeader: 'Cash-out only', + textAlign: 'center', + editingAlign: 'right', + input: NumberInput, + suffix: currency, + bold: bold, + inputProps: { + decimalPlaces: 2 + } } ] } @@ -218,6 +232,21 @@ const mainFields = currency => [ inputProps: { decimalPlaces: 2 } + }, + { + name: 'cashOutFixedFee', + display: 'Fixed fee', + width: 169, + size: 'lg', + doubleHeader: 'Cash-out only', + textAlign: 'center', + editingAlign: 'right', + input: NumberInput, + suffix: currency, + bold: bold, + inputProps: { + decimalPlaces: 2 + } } ] @@ -245,7 +274,7 @@ const getSchema = locale => { .max(percentMax) .required(), fixedFee: Yup.number() - .label('Fixed fee') + .label('Cash-in fixed fee') .min(0) .max(highestBill) .required(), @@ -253,6 +282,11 @@ const getSchema = locale => { .label('Minimum Tx') .min(0) .max(highestBill) + .required(), + cashOutFixedFee: Yup.number() + .label('Cash-out fixed fee') + .min(0) + .max(highestBill) .required() }) } @@ -340,7 +374,7 @@ const getOverridesSchema = (values, rawData, locale) => { .max(percentMax) .required(), fixedFee: Yup.number() - .label('Fixed fee') + .label('Cash-in fixed fee') .min(0) .max(highestBill) .required(), @@ -348,6 +382,11 @@ const getOverridesSchema = (values, rawData, locale) => { .label('Minimum Tx') .min(0) .max(highestBill) + .required(), + cashOutFixedFee: Yup.number() + .label('Cash-out fixed fee') + .min(0) + .max(highestBill) .required() }) } @@ -356,7 +395,8 @@ const defaults = { cashIn: '', cashOut: '', fixedFee: '', - minimumTx: '' + minimumTx: '', + cashOutFixedFee: '' } const overridesDefaults = { @@ -365,7 +405,8 @@ const overridesDefaults = { cashIn: '', cashOut: '', fixedFee: '', - minimumTx: '' + minimumTx: '', + cashOutFixedFee: '' } const getOrder = ({ machine, cryptoCurrencies }) => { @@ -385,6 +426,7 @@ const createCommissions = (cryptoCode, deviceId, isDefault, config) => { fixedFee: config.fixedFee, cashOut: config.cashOut, cashIn: config.cashIn, + cashOutFixedFee: config.cashOutFixedFee, machine: deviceId, cryptoCurrencies: [cryptoCode], default: isDefault, @@ -451,7 +493,7 @@ const getListCommissionsSchema = locale => { .max(percentMax) .required(), fixedFee: Yup.number() - .label('Fixed fee') + .label('Cash-in fixed fee') .min(0) .max(highestBill) .required(), @@ -459,6 +501,11 @@ const getListCommissionsSchema = locale => { .label('Minimum Tx') .min(0) .max(highestBill) + .required(), + cashOutFixedFee: Yup.number() + .label('Cash-out fixed fee') + .min(0) + .max(highestBill) .required() }) } @@ -487,7 +534,7 @@ const getListCommissionsFields = (getData, currency, defaults) => { { name: 'cryptoCurrencies', display: 'Crypto Currency', - width: 255, + width: 150, view: R.prop(0), size: 'sm', editable: false @@ -496,7 +543,7 @@ const getListCommissionsFields = (getData, currency, defaults) => { header: cashInHeader, name: 'cashIn', display: 'Cash-in', - width: 130, + width: 120, input: NumberInput, textAlign: 'right', suffix: '%', @@ -509,7 +556,7 @@ const getListCommissionsFields = (getData, currency, defaults) => { header: cashOutHeader, name: 'cashOut', display: 'Cash-out', - width: 140, + width: 126, input: NumberInput, textAlign: 'right', greenText: true, @@ -522,7 +569,7 @@ const getListCommissionsFields = (getData, currency, defaults) => { { name: 'fixedFee', display: 'Fixed fee', - width: 144, + width: 140, input: NumberInput, doubleHeader: 'Cash-in only', textAlign: 'right', @@ -535,7 +582,7 @@ const getListCommissionsFields = (getData, currency, defaults) => { { name: 'minimumTx', display: 'Minimum Tx', - width: 144, + width: 140, input: NumberInput, doubleHeader: 'Cash-in only', textAlign: 'right', @@ -544,6 +591,20 @@ const getListCommissionsFields = (getData, currency, defaults) => { inputProps: { decimalPlaces: 2 } + }, + { + name: 'cashOutFixedFee', + display: 'Fixed fee', + width: 140, + input: NumberInput, + doubleHeader: 'Cash-out only', + textAlign: 'center', + editingAlign: 'right', + suffix: currency, + textStyle: obj => getTextStyle(obj), + inputProps: { + decimalPlaces: 2 + } } ] } diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js index b9f82f44..8fafd220 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js @@ -4,12 +4,7 @@ import React, { useEffect, useRef, useCallback } from 'react' import { backgroundColor, zircon, primaryColor } from 'src/styling/variables' -const transactionProfit = tx => { - const cashInFee = tx.cashInFee ? Number.parseFloat(tx.cashInFee) : 0 - const commission = - Number.parseFloat(tx.commissionPercentage) * Number.parseFloat(tx.fiat) - return commission + cashInFee -} +const transactionProfit = R.prop('profit') const mockPoint = (tx, offsetMs, profit) => { const date = new Date(new Date(tx.created).getTime() + offsetMs).toISOString() diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js index 457869ce..4ae6ec87 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js @@ -36,7 +36,7 @@ const GET_DATA = gql` transactions(excludeTestingCustomers: $excludeTestingCustomers) { fiatCode fiat - cashInFee + fixedFee commissionPercentage created txClass diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/Commissions.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/Commissions.js index caf876e9..a9f8766b 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/Commissions.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/Commissions.js @@ -64,10 +64,11 @@ const Commissions = ({ name: SCREEN_KEY, id: deviceId }) => { cashIn: config.cashIn, cashOut: config.cashOut, fixedFee: config.fixedFee, - minimumTx: config.minimumTx + minimumTx: config.minimumTx, + cashOutFixedFee: config.cashOutFixedFee }, R.project( - ['cashIn', 'cashOut', 'fixedFee', 'minimumTx'], + ['cashIn', 'cashOut', 'fixedFee', 'minimumTx', 'cashOutFixedFee'], R.filter( o => R.includes(coin.code, o.cryptoCurrencies) || diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/helper.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/helper.js index 649979db..cbc47265 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/helper.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Commissions/helper.js @@ -61,6 +61,14 @@ const getOverridesFields = currency => { doubleHeader: 'Cash-in only', textAlign: 'right', suffix: currency + }, + { + name: 'cashOutFixedFee', + display: 'Fixed fee', + width: 155, + doubleHeader: 'Cash-out only', + textAlign: 'right', + suffix: currency } ] } diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js index bb360152..7c13ffa6 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js @@ -40,7 +40,7 @@ const GET_TRANSACTIONS = gql` hasError: error deviceId fiat - cashInFee + fixedFee fiatCode cryptoAtoms cryptoCode diff --git a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js index 7e20a52f..9bb4c96a 100644 --- a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js +++ b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js @@ -133,9 +133,9 @@ const DetailsRow = ({ it: tx, timezone }) => { const commission = BigNumber(tx.profit).toFixed(2, 1) // ROUND_DOWN const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2) * 100 - const cashInFee = isCashIn ? Number.parseFloat(tx.cashInFee) : 0 + const fixedFee = Number.parseFloat(tx.fixedFee) || 0 const fiat = BigNumber(tx.fiat) - .minus(cashInFee) + .minus(fixedFee) .toFixed(2, 1) // ROUND_DOWN const crypto = getCryptoAmount(tx) const cryptoFee = tx.fee ? `${getCryptoFeeAmount(tx)} ${tx.fiatCode}` : 'N/A' @@ -357,7 +357,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
-
{isCashIn ? `${cashInFee} ${tx.fiatCode}` : 'N/A'}
+
{`${fixedFee} ${tx.fiatCode}`}
diff --git a/new-lamassu-admin/src/pages/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Transactions/Transactions.js index 4b3b9326..8fe9c986 100644 --- a/new-lamassu-admin/src/pages/Transactions/Transactions.js +++ b/new-lamassu-admin/src/pages/Transactions/Transactions.js @@ -107,7 +107,7 @@ const GET_TRANSACTIONS = gql` deviceId fiat fee - cashInFee + fixedFee fiatCode cryptoAtoms cryptoCode