diff --git a/lib/commission-math.js b/lib/commission-math.js new file mode 100644 index 00000000..a3fc89c0 --- /dev/null +++ b/lib/commission-math.js @@ -0,0 +1,41 @@ +const BN = require('./bn') +const configManager = require('./new-config-manager') +const coinUtils = require('./coin-utils') + +function truncateCrypto (cryptoAtoms, cryptoCode) { + const DECIMAL_PLACES = 3 + if (cryptoAtoms.eq(0)) return cryptoAtoms + + const scale = 5 // TODO: change this to coins.displayScale when coins have that attribute + const scaleFactor = BN(10).pow(scale) + + return BN(cryptoAtoms).truncated().div(scaleFactor) + .round(DECIMAL_PLACES).times(scaleFactor) +} + +function fiatToCrypto (tx, rec, deviceId, config) { + const usableFiat = rec.fiat - rec.cashInFee + + const commissions = configManager.getCommissions(tx.cryptoCode, deviceId, config) + const tickerRate = BN(tx.rawTickerPrice) + const discount = getDiscountRate(tx.discount, commissions[tx.direction]) + const rate = tickerRate.mul(discount).round(5) + const unitScale = coinUtils.getCryptoCurrency(tx.cryptoCode).unitScale + const unitScaleFactor = BN(10).pow(unitScale) + + return truncateCrypto(BN(usableFiat).div(rate.div(unitScaleFactor)), tx.cryptoCode) +} + +function getDiscountRate (discount, commission) { + const bnDiscount = discount ? BN(discount) : BN(0) + const bnCommission = BN(commission) + const percentageDiscount = BN(1).sub(bnDiscount.div(100)) + const percentageCommission = bnCommission.div(100) + return BN(1).add(percentageDiscount.mul(percentageCommission)) +} + +module.exports = { + truncateCrypto, + fiatToCrypto, + getDiscountRate +} diff --git a/lib/coupon-manager.js b/lib/coupons.js similarity index 77% rename from lib/coupon-manager.js rename to lib/coupons.js index 7963484e..f6bb1254 100644 --- a/lib/coupon-manager.js +++ b/lib/coupons.js @@ -21,4 +21,9 @@ function softDeleteCoupon (couponId) { return db.none(sql, [couponId]) } -module.exports = { getAvailableCoupons, getCoupon, createCoupon, softDeleteCoupon } +function getNumberOfAvailableCoupons () { + const sql = `SELECT COUNT(id) FROM coupons WHERE soft_deleted=false` + return db.one(sql).then(res => res.count) +} + +module.exports = { getAvailableCoupons, getCoupon, createCoupon, softDeleteCoupon, getNumberOfAvailableCoupons } diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index 529da459..5648e229 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -13,7 +13,7 @@ const settingsLoader = require('../../new-settings-loader') // const tokenManager = require('../../token-manager') const blacklist = require('../../blacklist') const machineEventsByIdBatch = require("../../postgresql_interface").machineEventsByIdBatch -const couponManager = require('../../coupon-manager') +const couponManager = require('../../coupons') const serverVersion = require('../../../package.json').version @@ -179,7 +179,6 @@ const typeDefs = gql` id: ID! code: String! discount: Int! - soft_deleted: Boolean } type Transaction { @@ -295,7 +294,7 @@ const typeDefs = gql` deleteBlacklistRow(cryptoCode: String!, address: String!): Blacklist insertBlacklistRow(cryptoCode: String!, address: String!): Blacklist createCoupon(code: String!, discount: Int!): Coupon - softDeleteCoupon(couponId: ID!): Coupon + deleteCoupon(couponId: ID!): Coupon } ` @@ -370,7 +369,7 @@ const resolvers = { blacklist.insertIntoBlacklist(cryptoCode, address), // revokeToken: (...[, { token }]) => tokenManager.revokeToken(token) createCoupon: (...[, { code, discount }]) => couponManager.createCoupon(code, discount), - softDeleteCoupon: (...[, { couponId }]) => couponManager.softDeleteCoupon(couponId) + deleteCoupon: (...[, { couponId }]) => couponManager.deleteCoupon(couponId) } } diff --git a/lib/plugins.js b/lib/plugins.js index c567b358..139a6a34 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -21,6 +21,8 @@ const cashOutHelper = require('./cash-out/cash-out-helper') const machineLoader = require('./machine-loader') const customers = require('./customers') const coinUtils = require('./coin-utils') +const commissionMath = require('./commission-math') +const coupons = require('./coupons') const mapValuesWithKey = _.mapValues.convert({ cap: false @@ -190,11 +192,6 @@ function plugins (settings, deviceId) { .then(row => row.id) } - function getNumberOfAvailableCoupons () { - const sql = `SELECT COUNT(id) FROM coupons WHERE soft_deleted=false` - return db.one(sql).then(res => res.count) - } - function mapCoinSettings (coinParams) { const cryptoCode = coinParams[0] const cryptoNetwork = coinParams[1] @@ -227,7 +224,7 @@ function plugins (settings, deviceId) { const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c)) const pingPromise = recordPing(deviceTime, machineVersion, machineModel) const currentConfigVersionPromise = fetchCurrentConfigVersion() - const currentAvailableCoupons = getNumberOfAvailableCoupons() + const currentAvailableCoupons = coupons.getNumberOfAvailableCoupons() const promises = [ buildAvailableCassettes(), @@ -459,7 +456,7 @@ function plugins (settings, deviceId) { function buyAndSell (rec, doBuy, tx) { const cryptoCode = rec.cryptoCode const fiatCode = rec.fiatCode - const cryptoAtoms = doBuy ? fiatToCrypto(tx, rec) : rec.cryptoAtoms.neg() + const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.neg() const market = [fiatCode, cryptoCode].join('') @@ -475,38 +472,6 @@ function plugins (settings, deviceId) { }) } - function truncateCrypto (cryptoAtoms, cryptoCode) { - const DECIMAL_PLACES = 3 - if (cryptoAtoms.eq(0)) return cryptoAtoms - - const scale = 5 // TODO: change this to coins.displayScale when coins have that attribute - const scaleFactor = BN(10).pow(scale) - - return BN(cryptoAtoms).truncated().div(scaleFactor) - .round(DECIMAL_PLACES).times(scaleFactor) - } - - function fiatToCrypto (tx, rec) { - const usableFiat = rec.fiat - rec.cashInFee - - const commissions = configManager.getCommissions(tx.cryptoCode, deviceId, settings.config) - const tickerRate = BN(tx.rawTickerPrice) - const discount = getDiscountRate(tx.discount, commissions[tx.direction]) - const rate = tickerRate.mul(discount).round(5) - const unitScale = coinUtils.getCryptoCurrency(tx.cryptoCode).unitScale - const unitScaleFactor = BN(10).pow(unitScale) - - return truncateCrypto(BN(usableFiat).div(rate.div(unitScaleFactor)), tx.cryptoCode) - } - - function getDiscountRate (discount, commission) { - const bnDiscount = discount ? BN(discount) : BN(0) - const bnCommission = BN(commission) - const percentageDiscount = BN(1).sub(bnDiscount.div(100)) - const percentageCommission = bnCommission.div(100) - return BN(1).add(percentageDiscount.mul(percentageCommission)) - } - function consolidateTrades (cryptoCode, fiatCode) { const market = [fiatCode, cryptoCode].join('') diff --git a/lib/routes.js b/lib/routes.js index e57f6a23..71fc86e3 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -25,8 +25,9 @@ const E = require('./error') const customers = require('./customers') const logs = require('./logs') const compliance = require('./compliance') -const couponManager = require('./coupon-manager') +const couponManager = require('./coupons') const BN = require('./bn') +const commissionMath = require('./commission-math') const version = require('../package.json').version @@ -223,7 +224,7 @@ function verifyCoupon (req, res, next) { const transaction = req.body.tx const commissions = configManager.getCommissions(transaction.cryptoCode, req.deviceId, req.settings.config) const tickerRate = BN(transaction.rawTickerPrice) - const discount = getDiscountRate(coupon.discount, commissions[transaction.direction]) + const discount = commissionMath.getDiscountRate(coupon.discount, commissions[transaction.direction]) const rates = { [transaction.cryptoCode]: { [transaction.direction]: (transaction.direction === 'cashIn') @@ -240,12 +241,6 @@ function verifyCoupon (req, res, next) { .catch(next) } -function getDiscountRate (discount, commission) { - const percentageDiscount = BN(1).sub(BN(discount).div(100)) - const percentageCommission = BN(commission).div(100) - return BN(1).add(percentageDiscount.mul(percentageCommission)) -} - function addOrUpdateCustomer (req) { const customerData = req.body const machineVersion = req.query.version diff --git a/migrations/1603886141913-coupon-codes.js b/migrations/1603886141913-coupon-codes.js index 442fd335..e3180715 100644 --- a/migrations/1603886141913-coupon-codes.js +++ b/migrations/1603886141913-coupon-codes.js @@ -8,7 +8,7 @@ exports.up = function (next) { code TEXT NOT NULL, discount SMALLINT NOT NULL, soft_deleted BOOLEAN DEFAULT false )`, - `CREATE UNIQUE INDEX uq_code ON coupons USING btree(code) WHERE NOT soft_deleted` + `CREATE UNIQUE INDEX uq_code ON coupons (code) WHERE NOT soft_deleted` ] db.multi(sql, next) diff --git a/migrations/1606910357208-change-trades.js b/migrations/1606910357208-change-trades.js deleted file mode 100644 index a7a402c8..00000000 --- a/migrations/1606910357208-change-trades.js +++ /dev/null @@ -1,16 +0,0 @@ -const db = require('./db') - -exports.up = function (next) { - var sql = [ - 'ALTER TABLE trades ADD COLUMN tx_in_id UUID UNIQUE', - 'ALTER TABLE trades ADD CONSTRAINT fk_tx_in FOREIGN KEY (tx_in_id) REFERENCES cash_in_txs (id)', - 'ALTER TABLE trades ADD COLUMN tx_out_id UUID UNIQUE', - 'ALTER TABLE trades ADD CONSTRAINT fk_tx_out FOREIGN KEY (tx_in_id) REFERENCES cash_out_txs (id)' - ] - - db.multi(sql, next) -} - -exports.down = function (next) { - next() -} diff --git a/new-lamassu-admin/src/pages/LoyaltyPanel/CouponCodes.js b/new-lamassu-admin/src/pages/LoyaltyPanel/CouponCodes.js index 16ebfbd4..98ee1f4a 100644 --- a/new-lamassu-admin/src/pages/LoyaltyPanel/CouponCodes.js +++ b/new-lamassu-admin/src/pages/LoyaltyPanel/CouponCodes.js @@ -21,14 +21,13 @@ const GET_COUPONS = gql` id code discount - soft_deleted } } ` -const SOFT_DELETE_COUPON = gql` - mutation softDeleteCoupon($couponId: ID!) { - softDeleteCoupon(couponId: $couponId) { +const DELETE_COUPON = gql` + mutation deleteCoupon($couponId: ID!) { + deleteCoupon(couponId: $couponId) { id } } @@ -40,7 +39,6 @@ const CREATE_COUPON = gql` id code discount - soft_deleted } } ` @@ -53,7 +51,7 @@ const Coupons = () => { const { data: couponResponse, loading } = useQuery(GET_COUPONS) - const [softDeleteCoupon] = useMutation(SOFT_DELETE_COUPON, { + const [softDeleteCoupon] = useMutation(DELETE_COUPON, { refetchQueries: () => ['coupons'] }) diff --git a/new-lamassu-admin/src/pages/LoyaltyPanel/LoyaltyPanel.js b/new-lamassu-admin/src/pages/LoyaltyPanel/LoyaltyPanel.js deleted file mode 100644 index 0b624386..00000000 --- a/new-lamassu-admin/src/pages/LoyaltyPanel/LoyaltyPanel.js +++ /dev/null @@ -1,97 +0,0 @@ -import { makeStyles } from '@material-ui/core' -import Grid from '@material-ui/core/Grid' -import React from 'react' -import { - Route, - Switch, - Redirect, - useLocation, - useHistory -} from 'react-router-dom' - -import Sidebar from 'src/components/layout/Sidebar' -import TitleSection from 'src/components/layout/TitleSection' - -import CouponCodes from './CouponCodes' -import IndividualDiscounts from './IndividualDiscounts' -import LoyaltyDiscounts from './LoyaltyDiscounts' - -const styles = { - grid: { - flex: 1, - height: '100%' - }, - content: { - flex: 1, - marginLeft: 48, - paddingTop: 15 - } -} - -const useStyles = makeStyles(styles) - -const innerRoutes = [ - { - key: 'individual-discounts', - label: 'Individual Discounts', - route: '/compliance/loyalty/individual-discounts', - component: IndividualDiscounts - }, - { - key: 'loyalty-discounts', - label: 'Loyalty Discounts', - route: '/compliance/loyalty/discounts', - component: LoyaltyDiscounts - }, - { - key: 'coupon-codes', - label: 'Coupon Codes', - route: '/compliance/loyalty/coupons', - component: CouponCodes - } -] - -const Routes = ({ wizard }) => ( - - - - {innerRoutes.map(({ route, component: Page, key }) => ( - - - - ))} - -) - -const LoyaltyPanel = ({ wizard = false }) => { - const classes = useStyles() - const history = useHistory() - const location = useLocation() - - const isSelected = it => location.pathname === it.route - - const onClick = it => history.push(it.route) - - return ( - <> - - - it.label} - onClick={onClick} - /> -
- -
-
- - ) -} - -export default LoyaltyPanel