Merge pull request #1759 from RafaelTaranto/backport/fixedfee-for-cashout

LAM-222 backport: fixedfee for cashout
This commit is contained in:
Rafael Taranto 2024-11-29 08:26:14 +00:00 committed by GitHub
commit ec66f2291e
20 changed files with 154 additions and 49 deletions

View file

@ -51,7 +51,7 @@ const mapValuesWithKey = _.mapValues.convert({cap: false})
function convertBigNumFields (obj) { function convertBigNumFields (obj) {
const convert = (value, key) => { const convert = (value, key) => {
if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat' ])) { if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat', 'fixedFee', 'fixedFeeCrypto' ])) {
return value.toString() return value.toString()
} }

View file

@ -29,6 +29,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
const cashInFee = showCommissions ? commissions.cashIn / 100 : null const cashInFee = showCommissions ? commissions.cashIn / 100 : null
const cashOutFee = showCommissions ? commissions.cashOut / 100 : null const cashOutFee = showCommissions ? commissions.cashOut / 100 : null
const cashInFixedFee = showCommissions ? commissions.fixedFee : null const cashInFixedFee = showCommissions ? commissions.fixedFee : null
const cashOutFixedFee = showCommissions ? commissions.cashOutFixedFee : null
const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null
const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null
@ -37,6 +38,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
cashInFee, cashInFee,
cashOutFee, cashOutFee,
cashInFixedFee, cashInFixedFee,
cashOutFixedFee,
cashInRate, cashInRate,
cashOutRate cashOutRate
} }

View file

@ -89,6 +89,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
'cashInCommission', 'cashInCommission',
'cashInFee', 'cashInFee',
'cashOutCommission', 'cashOutCommission',
'cashOutFee',
'cryptoCode', 'cryptoCode',
'cryptoCodeDisplay', 'cryptoCodeDisplay',
'cryptoNetwork', 'cryptoNetwork',

View file

@ -6,6 +6,7 @@ type Coin {
display: String! display: String!
minimumTx: String! minimumTx: String!
cashInFee: String! cashInFee: String!
cashOutFee: String!
cashInCommission: String! cashInCommission: String!
cashOutCommission: String! cashOutCommission: String!
cryptoNetwork: String! cryptoNetwork: String!

View file

@ -23,7 +23,7 @@ const typeDef = gql`
errorCode: String errorCode: String
operatorCompleted: Boolean operatorCompleted: Boolean
sendPending: Boolean sendPending: Boolean
cashInFee: String fixedFee: String
minimumTx: Float minimumTx: Float
customerId: ID customerId: ID
isAnonymous: Boolean isAnonymous: Boolean

View file

@ -50,7 +50,19 @@ function batch (
excludeTestingCustomers = false, excludeTestingCustomers = false,
simplified 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.*, const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
c.phone AS customer_phone, c.phone AS customer_phone,
@ -153,7 +165,7 @@ function batch (
function advancedBatch (data) { function advancedBatch (data) {
const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms', const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms',
'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount', 'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount',
'dispense', 'notified', 'redeem', 'phone', 'error', 'dispense', 'notified', 'redeem', 'phone', 'error', 'fixedFee',
'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout', 'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout',
'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4', 'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4',
'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6', 'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6',
@ -169,7 +181,9 @@ function advancedBatch (data) {
...it, ...it,
status: getStatus(it), status: getStatus(it),
fiatProfit: getProfit(it).toString(), 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) return _.compose(_.map(_.pick(fields)), addAdvancedFields)(data)

View file

@ -249,6 +249,7 @@ function plugins (settings, deviceId) {
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
const minimumTx = new BN(commissions.minimumTx) const minimumTx = new BN(commissions.minimumTx)
const cashInFee = new BN(commissions.fixedFee) const cashInFee = new BN(commissions.fixedFee)
const cashOutFee = new BN(commissions.cashOutFixedFee)
const cashInCommission = new BN(commissions.cashIn) const cashInCommission = new BN(commissions.cashIn)
const cashOutCommission = _.isNumber(commissions.cashOut) ? new BN(commissions.cashOut) : null const cashOutCommission = _.isNumber(commissions.cashOut) ? new BN(commissions.cashOut) : null
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
@ -261,6 +262,7 @@ function plugins (settings, deviceId) {
isCashInOnly: Boolean(cryptoRec.isCashinOnly), isCashInOnly: Boolean(cryptoRec.isCashinOnly),
minimumTx: BN.max(minimumTx, cashInFee), minimumTx: BN.max(minimumTx, cashInFee),
cashInFee, cashInFee,
cashOutFee,
cashInCommission, cashInCommission,
cashOutCommission, cashOutCommission,
cryptoNetwork, cryptoNetwork,

View file

@ -40,6 +40,7 @@ function massage (tx, pi) {
: { : {
cryptoAtoms: new BN(r.cryptoAtoms), cryptoAtoms: new BN(r.cryptoAtoms),
fiat: new BN(r.fiat), fiat: new BN(r.fiat),
fixedFee: new BN(r.fixedFee),
rawTickerPrice: r.rawTickerPrice ? new BN(r.rawTickerPrice) : null, rawTickerPrice: r.rawTickerPrice ? new BN(r.rawTickerPrice) : null,
commissionPercentage: new BN(r.commissionPercentage) commissionPercentage: new BN(r.commissionPercentage)
} }

View file

@ -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()

View file

@ -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()

View file

@ -22,22 +22,27 @@ const styles = {
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const groupSecondHeader = elements => { const groupSecondHeader = elements => {
const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements) const doubleHeader = R.prop('doubleHeader')
const sameDoubleHeader = (a, b) => doubleHeader(a) === doubleHeader(b)
if (!toSHeader.length) { const group = R.pipe(
return [elements, THead] R.groupWith(sameDoubleHeader),
} R.map(group =>
R.isNil(doubleHeader(group[0])) // No doubleHeader
const index = R.indexOf(toSHeader[0], elements) ? group
const width = R.compose(R.sum, R.map(R.path(['width'])))(toSHeader) : [
{
const innerElements = R.insert( width: R.sum(R.map(R.prop('width'), group)),
index, elements: group,
{ width, elements: toSHeader, name: toSHeader[0].doubleHeader }, name: doubleHeader(group[0])
noSHeader }
]
),
R.reduce(R.concat, [])
) )
return [innerElements, TDoubleLevelHead] return R.all(R.pipe(doubleHeader, R.isNil), elements)
? [elements, THead]
: [group(elements), TDoubleLevelHead]
} }
const Header = () => { const Header = () => {

View file

@ -77,7 +77,7 @@ const GET_TRANSACTIONS = gql`
hasError: error hasError: error
deviceId deviceId
fiat fiat
cashInFee fixedFee
fiatCode fiatCode
cryptoAtoms cryptoAtoms
cryptoCode cryptoCode

View file

@ -91,7 +91,7 @@ const getOverridesFields = (getData, currency, auxElements) => {
}, },
{ {
name: 'cryptoCurrencies', name: 'cryptoCurrencies',
width: 280, width: 145,
size: 'sm', size: 'sm',
view: displayCodeArray(cryptoData), view: displayCodeArray(cryptoData),
input: Autocomplete, input: Autocomplete,
@ -108,7 +108,7 @@ const getOverridesFields = (getData, currency, auxElements) => {
header: cashInHeader, header: cashInHeader,
name: 'cashIn', name: 'cashIn',
display: 'Cash-in', display: 'Cash-in',
width: 130, width: 123,
input: NumberInput, input: NumberInput,
textAlign: 'right', textAlign: 'right',
suffix: '%', suffix: '%',
@ -121,7 +121,7 @@ const getOverridesFields = (getData, currency, auxElements) => {
header: cashOutHeader, header: cashOutHeader,
name: 'cashOut', name: 'cashOut',
display: 'Cash-out', display: 'Cash-out',
width: 130, width: 127,
input: NumberInput, input: NumberInput,
textAlign: 'right', textAlign: 'right',
suffix: '%', suffix: '%',
@ -133,7 +133,7 @@ const getOverridesFields = (getData, currency, auxElements) => {
{ {
name: 'fixedFee', name: 'fixedFee',
display: 'Fixed fee', display: 'Fixed fee',
width: 144, width: 126,
input: NumberInput, input: NumberInput,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
@ -146,7 +146,7 @@ const getOverridesFields = (getData, currency, auxElements) => {
{ {
name: 'minimumTx', name: 'minimumTx',
display: 'Minimum Tx', display: 'Minimum Tx',
width: 169, width: 140,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'center', textAlign: 'center',
editingAlign: 'right', editingAlign: 'right',
@ -156,6 +156,20 @@ const getOverridesFields = (getData, currency, auxElements) => {
inputProps: { inputProps: {
decimalPlaces: 2 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: { inputProps: {
decimalPlaces: 2 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) .max(percentMax)
.required(), .required(),
fixedFee: Yup.number() fixedFee: Yup.number()
.label('Fixed fee') .label('Cash-in fixed fee')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(), .required(),
@ -253,6 +282,11 @@ const getSchema = locale => {
.label('Minimum Tx') .label('Minimum Tx')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(),
cashOutFixedFee: Yup.number()
.label('Cash-out fixed fee')
.min(0)
.max(highestBill)
.required() .required()
}) })
} }
@ -340,7 +374,7 @@ const getOverridesSchema = (values, rawData, locale) => {
.max(percentMax) .max(percentMax)
.required(), .required(),
fixedFee: Yup.number() fixedFee: Yup.number()
.label('Fixed fee') .label('Cash-in fixed fee')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(), .required(),
@ -348,6 +382,11 @@ const getOverridesSchema = (values, rawData, locale) => {
.label('Minimum Tx') .label('Minimum Tx')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(),
cashOutFixedFee: Yup.number()
.label('Cash-out fixed fee')
.min(0)
.max(highestBill)
.required() .required()
}) })
} }
@ -356,7 +395,8 @@ const defaults = {
cashIn: '', cashIn: '',
cashOut: '', cashOut: '',
fixedFee: '', fixedFee: '',
minimumTx: '' minimumTx: '',
cashOutFixedFee: ''
} }
const overridesDefaults = { const overridesDefaults = {
@ -365,7 +405,8 @@ const overridesDefaults = {
cashIn: '', cashIn: '',
cashOut: '', cashOut: '',
fixedFee: '', fixedFee: '',
minimumTx: '' minimumTx: '',
cashOutFixedFee: ''
} }
const getOrder = ({ machine, cryptoCurrencies }) => { const getOrder = ({ machine, cryptoCurrencies }) => {
@ -385,6 +426,7 @@ const createCommissions = (cryptoCode, deviceId, isDefault, config) => {
fixedFee: config.fixedFee, fixedFee: config.fixedFee,
cashOut: config.cashOut, cashOut: config.cashOut,
cashIn: config.cashIn, cashIn: config.cashIn,
cashOutFixedFee: config.cashOutFixedFee,
machine: deviceId, machine: deviceId,
cryptoCurrencies: [cryptoCode], cryptoCurrencies: [cryptoCode],
default: isDefault, default: isDefault,
@ -451,7 +493,7 @@ const getListCommissionsSchema = locale => {
.max(percentMax) .max(percentMax)
.required(), .required(),
fixedFee: Yup.number() fixedFee: Yup.number()
.label('Fixed fee') .label('Cash-in fixed fee')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(), .required(),
@ -459,6 +501,11 @@ const getListCommissionsSchema = locale => {
.label('Minimum Tx') .label('Minimum Tx')
.min(0) .min(0)
.max(highestBill) .max(highestBill)
.required(),
cashOutFixedFee: Yup.number()
.label('Cash-out fixed fee')
.min(0)
.max(highestBill)
.required() .required()
}) })
} }
@ -487,7 +534,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
{ {
name: 'cryptoCurrencies', name: 'cryptoCurrencies',
display: 'Crypto Currency', display: 'Crypto Currency',
width: 255, width: 150,
view: R.prop(0), view: R.prop(0),
size: 'sm', size: 'sm',
editable: false editable: false
@ -496,7 +543,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
header: cashInHeader, header: cashInHeader,
name: 'cashIn', name: 'cashIn',
display: 'Cash-in', display: 'Cash-in',
width: 130, width: 120,
input: NumberInput, input: NumberInput,
textAlign: 'right', textAlign: 'right',
suffix: '%', suffix: '%',
@ -509,7 +556,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
header: cashOutHeader, header: cashOutHeader,
name: 'cashOut', name: 'cashOut',
display: 'Cash-out', display: 'Cash-out',
width: 140, width: 126,
input: NumberInput, input: NumberInput,
textAlign: 'right', textAlign: 'right',
greenText: true, greenText: true,
@ -522,7 +569,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
{ {
name: 'fixedFee', name: 'fixedFee',
display: 'Fixed fee', display: 'Fixed fee',
width: 144, width: 140,
input: NumberInput, input: NumberInput,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
@ -535,7 +582,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
{ {
name: 'minimumTx', name: 'minimumTx',
display: 'Minimum Tx', display: 'Minimum Tx',
width: 144, width: 140,
input: NumberInput, input: NumberInput,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
@ -544,6 +591,20 @@ const getListCommissionsFields = (getData, currency, defaults) => {
inputProps: { inputProps: {
decimalPlaces: 2 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
}
} }
] ]
} }

View file

@ -4,12 +4,7 @@ import React, { useEffect, useRef, useCallback } from 'react'
import { backgroundColor, zircon, primaryColor } from 'src/styling/variables' import { backgroundColor, zircon, primaryColor } from 'src/styling/variables'
const transactionProfit = tx => { const transactionProfit = R.prop('profit')
const cashInFee = tx.cashInFee ? Number.parseFloat(tx.cashInFee) : 0
const commission =
Number.parseFloat(tx.commissionPercentage) * Number.parseFloat(tx.fiat)
return commission + cashInFee
}
const mockPoint = (tx, offsetMs, profit) => { const mockPoint = (tx, offsetMs, profit) => {
const date = new Date(new Date(tx.created).getTime() + offsetMs).toISOString() const date = new Date(new Date(tx.created).getTime() + offsetMs).toISOString()

View file

@ -36,7 +36,7 @@ const GET_DATA = gql`
transactions(excludeTestingCustomers: $excludeTestingCustomers) { transactions(excludeTestingCustomers: $excludeTestingCustomers) {
fiatCode fiatCode
fiat fiat
cashInFee fixedFee
commissionPercentage commissionPercentage
created created
txClass txClass

View file

@ -64,10 +64,11 @@ const Commissions = ({ name: SCREEN_KEY, id: deviceId }) => {
cashIn: config.cashIn, cashIn: config.cashIn,
cashOut: config.cashOut, cashOut: config.cashOut,
fixedFee: config.fixedFee, fixedFee: config.fixedFee,
minimumTx: config.minimumTx minimumTx: config.minimumTx,
cashOutFixedFee: config.cashOutFixedFee
}, },
R.project( R.project(
['cashIn', 'cashOut', 'fixedFee', 'minimumTx'], ['cashIn', 'cashOut', 'fixedFee', 'minimumTx', 'cashOutFixedFee'],
R.filter( R.filter(
o => o =>
R.includes(coin.code, o.cryptoCurrencies) || R.includes(coin.code, o.cryptoCurrencies) ||

View file

@ -61,6 +61,14 @@ const getOverridesFields = currency => {
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
suffix: currency suffix: currency
},
{
name: 'cashOutFixedFee',
display: 'Fixed fee',
width: 155,
doubleHeader: 'Cash-out only',
textAlign: 'right',
suffix: currency
} }
] ]
} }

View file

@ -40,7 +40,7 @@ const GET_TRANSACTIONS = gql`
hasError: error hasError: error
deviceId deviceId
fiat fiat
cashInFee fixedFee
fiatCode fiatCode
cryptoAtoms cryptoAtoms
cryptoCode cryptoCode

View file

@ -133,9 +133,9 @@ const DetailsRow = ({ it: tx, timezone }) => {
const commission = BigNumber(tx.profit).toFixed(2, 1) // ROUND_DOWN const commission = BigNumber(tx.profit).toFixed(2, 1) // ROUND_DOWN
const commissionPercentage = const commissionPercentage =
Number.parseFloat(tx.commissionPercentage, 2) * 100 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) const fiat = BigNumber(tx.fiat)
.minus(cashInFee) .minus(fixedFee)
.toFixed(2, 1) // ROUND_DOWN .toFixed(2, 1) // ROUND_DOWN
const crypto = getCryptoAmount(tx) const crypto = getCryptoAmount(tx)
const cryptoFee = tx.fee ? `${getCryptoFeeAmount(tx)} ${tx.fiatCode}` : 'N/A' const cryptoFee = tx.fee ? `${getCryptoFeeAmount(tx)} ${tx.fiatCode}` : 'N/A'
@ -357,7 +357,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
</div> </div>
<div> <div>
<Label>Fixed fee</Label> <Label>Fixed fee</Label>
<div>{isCashIn ? `${cashInFee} ${tx.fiatCode}` : 'N/A'}</div> <div>{`${fixedFee} ${tx.fiatCode}`}</div>
</div> </div>
</div> </div>
<div className={classes.secondRow}> <div className={classes.secondRow}>

View file

@ -107,7 +107,7 @@ const GET_TRANSACTIONS = gql`
deviceId deviceId
fiat fiat
fee fee
cashInFee fixedFee
fiatCode fiatCode
cryptoAtoms cryptoAtoms
cryptoCode cryptoCode