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) {
const convert = (value, key) => {
if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat' ])) {
if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat', 'fixedFee', 'fixedFeeCrypto' ])) {
return value.toString()
}

View file

@ -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
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

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

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 groupSecondHeader = elements => {
const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements)
if (!toSHeader.length) {
return [elements, THead]
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])
}
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
]
),
R.reduce(R.concat, [])
)
return [innerElements, TDoubleLevelHead]
return R.all(R.pipe(doubleHeader, R.isNil), elements)
? [elements, THead]
: [group(elements), TDoubleLevelHead]
}
const Header = () => {

View file

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

View file

@ -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
}
}
]
}

View file

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

View file

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

View file

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

View file

@ -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
}
]
}

View file

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

View file

@ -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 }) => {
</div>
<div>
<Label>Fixed fee</Label>
<div>{isCashIn ? `${cashInFee} ${tx.fiatCode}` : 'N/A'}</div>
<div>{`${fixedFee} ${tx.fiatCode}`}</div>
</div>
</div>
<div className={classes.secondRow}>

View file

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