diff --git a/components/Chart.tsx b/components/Chart.tsx index 04509fccb..c444c89fd 100644 --- a/components/Chart.tsx +++ b/components/Chart.tsx @@ -1,7 +1,6 @@ import type { NextPage } from 'next' import React from 'react' import { formatQuoteValue } from 'utils/index' -import { USD_QUOTE_ID } from 'constants/index' import { Chart as ChartJS, CategoryScale, @@ -13,6 +12,9 @@ import { Legend } from 'chart.js' import { Line } from 'react-chartjs-2' +import { ChartData } from 'redis/types' +import { USD_QUOTE_ID } from 'constants/index' +import { QuoteValues } from 'services/priceService' ChartJS.register( CategoryScale, @@ -25,13 +27,28 @@ ChartJS.register( ) interface Props { - data: object - usd: boolean + chartData: ChartData + currencyId?: number } -const Chart: NextPage = ({ data, usd }) => { - const chartData = data +function getQuoteOption (quoteValues: QuoteValues[], currencyId: number): number[] { + return quoteValues.map(qv => Number(currencyId === USD_QUOTE_ID ? qv.usd : qv.cad)) +} +const Chart: NextPage = ({ chartData, currencyId }) => { + const data = chartData + if (chartData.isMultiQuote && currencyId !== undefined) { + if (data.datasets.length > 0) { + data.datasets = [ + { + ...data.datasets[0], + data: Array.isArray(data.datasets[0].data) && typeof data.datasets[0].data[0] === 'object' + ? getQuoteOption(data.datasets[0].data as QuoteValues[], currencyId) + : data.datasets[0].data + } + ] + } + } function cssvar (name): string { /* if (typeof window !== "undefined") { @@ -54,7 +71,8 @@ const Chart: NextPage = ({ data, usd }) => { displayColors: false, callbacks: { label: function (context) { - return usd ? '$' + formatQuoteValue(context.raw, USD_QUOTE_ID) : formatQuoteValue(context.raw) + const prefix = currencyId !== undefined ? '$' : '' + return prefix + formatQuoteValue(context.raw, currencyId) } }, mode: 'nearest', @@ -83,7 +101,7 @@ const Chart: NextPage = ({ data, usd }) => { ticks: { color: cssvar('--primary-text-color'), callback: function (value: string) { - return usd ? '$' + formatQuoteValue(value, USD_QUOTE_ID) : value + return currencyId !== undefined ? '$' + formatQuoteValue(value, currencyId) : value } }, position: 'right' diff --git a/components/Dashboard/Leaderboard.tsx b/components/Dashboard/Leaderboard.tsx index 122d72d73..c7e2704ae 100644 --- a/components/Dashboard/Leaderboard.tsx +++ b/components/Dashboard/Leaderboard.tsx @@ -3,16 +3,16 @@ import { useMemo } from 'react' import { PaymentDataByButton } from 'redis/types' import style from '../Transaction/transaction.module.css' import moment from 'moment' -import { USD_QUOTE_ID } from 'constants/index' import { compareNumericString } from 'utils/index' import { formatQuoteValue } from 'utils' interface IProps { buttons: PaymentDataByButton totalString: string + currencyId: number } -export default ({ buttons, totalString }: IProps): JSX.Element => { +export default ({ buttons, totalString, currencyId }: IProps): JSX.Element => { const columns = useMemo( () => [ { @@ -39,7 +39,7 @@ export default ({ buttons, totalString }: IProps): JSX.Element => { sortType: compareNumericString, Cell: (cellProps) => { return
- {'$'.concat(formatQuoteValue(cellProps.cell.value, USD_QUOTE_ID))} + {'$'.concat(formatQuoteValue(cellProps.cell.value, currencyId))}
} }, diff --git a/pages/api/paybutton/download/transactions/[paybuttonId].ts b/pages/api/paybutton/download/transactions/[paybuttonId].ts index 546aaaefb..7d07f4bac 100644 --- a/pages/api/paybutton/download/transactions/[paybuttonId].ts +++ b/pages/api/paybutton/download/transactions/[paybuttonId].ts @@ -10,8 +10,7 @@ import { NetworkTickersType, NETWORK_TICKERS, NETWORK_IDS, - SUPPORTED_QUOTES_FROM_ID, - USD_QUOTE_ID + SUPPORTED_QUOTES_FROM_ID } from 'constants/index' import { TransactionWithAddressAndPrices, fetchTransactionsByPaybuttonId, getTransactionValueInCurrency } from 'services/transactionService' import { PaybuttonWithAddresses, fetchPaybuttonById } from 'services/paybuttonService' @@ -20,6 +19,7 @@ import { setSession } from 'utils/setSession' import { NextApiResponse } from 'next' import { Decimal } from '@prisma/client/runtime' import { getNetworkIdFromSlug } from 'services/networkService' +import { fetchUserProfileFromId } from 'services/userService' export interface TransactionFileData { amount: Decimal @@ -144,13 +144,14 @@ export default async (req: any, res: any): Promise => { await setSession(req, res) const userId = req.session.userId + const user = await fetchUserProfileFromId(userId) const paybuttonId = req.query.paybuttonId as string const networkTickerReq = req.query.network as string const networkTicker = (networkTickerReq !== '' && isNetworkValid(networkTickerReq as NetworkTickersType)) ? networkTickerReq.toUpperCase() as NetworkTickersType : undefined let quoteId: number if (req.query.currency === undefined || req.query.currency === '' || Number.isNaN(req.query.currency)) { - quoteId = USD_QUOTE_ID + quoteId = user.preferredCurrencyId } else { quoteId = req.query.currency as number } diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index b12b41120..3e3f65dab 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -6,7 +6,7 @@ import Session from 'supertokens-node/recipe/session' import { GetServerSideProps } from 'next' import style from './dashboard.module.css' import { formatQuoteValue, removeUnserializableFields } from 'utils/index' -import { COOKIE_NAMES, USD_QUOTE_ID } from 'constants/index' +import { COOKIE_NAMES } from 'constants/index' import Leaderboard from 'components/Dashboard/Leaderboard' import { DashboardData, PeriodData } from 'redis/types' import { loadStateFromCookie, saveStateToCookie } from 'utils/cookies' @@ -123,7 +123,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen <>
- +
@@ -137,10 +137,10 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
Revenue
-
{totalString}: ${formatQuoteValue(activePeriod.totalRevenue, USD_QUOTE_ID)}
+
{totalString}: ${formatQuoteValue(activePeriod.totalRevenue, user.userProfile.preferredCurrencyId)}
- +
@@ -149,7 +149,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
{totalString}: {formatQuoteValue(activePeriod.totalPayments)}
- +
@@ -157,7 +157,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
Button Leaderboard
- + diff --git a/pages/payments/index.tsx b/pages/payments/index.tsx index a3595a40a..d3abcf0fb 100644 --- a/pages/payments/index.tsx +++ b/pages/payments/index.tsx @@ -11,7 +11,7 @@ import XECIcon from 'assets/xec-logo.png' import BCHIcon from 'assets/bch-logo.png' import EyeIcon from 'assets/eye-icon.png' import { formatQuoteValue, compareNumericString, removeUnserializableFields } from 'utils/index' -import { XEC_NETWORK_ID, USD_QUOTE_ID, BCH_TX_EXPLORER_URL, XEC_TX_EXPLORER_URL } from 'constants/index' +import { XEC_NETWORK_ID, BCH_TX_EXPLORER_URL, XEC_TX_EXPLORER_URL } from 'constants/index' import moment from 'moment' import TopBar from 'components/TopBar' import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService' @@ -71,10 +71,10 @@ export default function Payments ({ user }: PaybuttonsProps): React.ReactElement }, { Header: () => (
Amount
), - accessor: 'value', + accessor: 'values', sortType: compareNumericString, Cell: (cellProps) => { - return
${formatQuoteValue(cellProps.cell.value, USD_QUOTE_ID)}
+ return
${formatQuoteValue(cellProps.cell.value, user.userProfile.preferredCurrencyId)}
} }, { diff --git a/redis/balanceCache.ts b/redis/balanceCache.ts index a7455b379..3ab908043 100644 --- a/redis/balanceCache.ts +++ b/redis/balanceCache.ts @@ -1,8 +1,7 @@ import { Prisma } from '@prisma/client' -import { AddressPaymentInfo, generateAddressPaymentInfo } from 'services/addressService' +import { AddressPaymentInfo, AddressWithTransactionsWithPrices, generateAddressPaymentInfo } from 'services/addressService' import { TransactionWithAddressAndPrices } from 'services/transactionService' import { redis } from './clientInstance' -import { Payment } from './types' // ADDRESS:balance const getBalanceKey = (addressString: string): string => { @@ -14,13 +13,16 @@ export const cacheAddressPaymentInfo = async (addressString: string, info: Addre await redis.set(balanceKey, JSON.stringify(info)) } -export const cacheBalanceFromPayments = async (addressString: string, payments: Payment[]): Promise => { - const paymentsSum = payments.reduce((prev, curr) => prev.plus(curr.value), new Prisma.Decimal(0)) +export const cacheBalanceForAddress = async (address: AddressWithTransactionsWithPrices): Promise => { + let balance = new Prisma.Decimal(0) + for (const tx of address.transactions) { + balance = balance.plus(tx.amount) + } const info: AddressPaymentInfo = { - balance: paymentsSum, - paymentCount: payments.length + balance, + paymentCount: address.transactions.filter(tx => tx.amount.gt(0)).length } - await cacheAddressPaymentInfo(addressString, info) + await cacheAddressPaymentInfo(address.address, info) } export const getCachedBalanceForAddress = async (addressString: string): Promise => { diff --git a/redis/dashboardCache.ts b/redis/dashboardCache.ts index caff66df8..d5fdd4994 100644 --- a/redis/dashboardCache.ts +++ b/redis/dashboardCache.ts @@ -5,6 +5,7 @@ import { Prisma } from '@prisma/client' import moment, { DurationInputArg2 } from 'moment' import { XEC_NETWORK_ID, BCH_NETWORK_ID } from 'constants/index' import { fetchPaybuttonArrayByUserId } from 'services/paybuttonService' +import { QuoteValues } from 'services/priceService' // USERID:dashboard const getDashboardSummaryKey = (userId: string): string => { @@ -15,18 +16,21 @@ const getChartLabels = function (n: number, periodString: string, formatString = return [...new Array(n)].map((_, idx) => moment().startOf('day').subtract(idx, periodString as DurationInputArg2).format(formatString)).reverse() } -const getChartRevenuePaymentData = function (n: number, periodString: string, paymentList: Payment[]): any { - const revenueArray: Prisma.Decimal[] = [] +interface RevenuePaymentData { + revenue: QuoteValues[] + payments: number[] +} + +const getChartRevenuePaymentData = function (n: number, periodString: string, paymentList: Payment[]): RevenuePaymentData { + const revenueArray: QuoteValues[] = [] const paymentsArray: number[] = [] const _ = [...new Array(n)] _.forEach((_, idx) => { const lowerThreshold = moment().subtract(idx, periodString as DurationInputArg2).startOf(periodString === 'months' ? 'month' : 'day') const upperThreshold = moment().subtract(idx, periodString as DurationInputArg2).endOf(periodString === 'months' ? 'month' : 'day') - const periodTransactionAmountArray = filterLastPayments(lowerThreshold, upperThreshold, paymentList).map((p) => p.value) - const revenue = periodTransactionAmountArray.reduce((a, b) => { - return a.plus(b) - }, new Prisma.Decimal(0)) - const paymentCount = periodTransactionAmountArray.length + const periodPaymentList = filterLastPayments(lowerThreshold, upperThreshold, paymentList) + const revenue = sumPaymentsValue(periodPaymentList) + const paymentCount = periodPaymentList.length revenueArray.push(revenue) paymentsArray.push(paymentCount) }) @@ -43,8 +47,13 @@ const filterLastPayments = function (lowerThreshold: moment.Moment, upperThresho }) } -const getChartData = function (n: number, periodString: string, dataArray: number[] | Prisma.Decimal[], borderColor: string, formatString = 'M/D'): ChartData { +const getChartData = function (n: number, periodString: string, dataArray: number[] | QuoteValues[], borderColor: string, formatString = 'M/D'): ChartData { + let isMultiQuote = false + if (dataArray.length > 0 && typeof dataArray[0] !== 'number') { + isMultiQuote = true + } return { + isMultiQuote, labels: getChartLabels(n, periodString, formatString), datasets: [ { @@ -60,12 +69,14 @@ const getPeriodData = function (n: number, periodString: string, paymentList: Pa const revenue = getChartData(n, periodString, revenuePaymentData.revenue, borderColor.revenue, formatString) const payments = getChartData(n, periodString, revenuePaymentData.payments, borderColor.payments, formatString) const buttons = getButtonPaymentData(n, periodString, paymentList) + const totalRevenue = (revenue.datasets[0].data as QuoteValues[]).reduce(sumQuoteValues, { usd: new Prisma.Decimal(0), cad: new Prisma.Decimal(0) }) + const totalPayments = (payments.datasets[0].data as any).reduce((a: number, b: number) => a + b, 0) return { revenue, payments, - totalRevenue: (revenue.datasets[0].data as any).reduce((a: Prisma.Decimal, b: Prisma.Decimal) => a.plus(b), new Prisma.Decimal(0)), - totalPayments: (payments.datasets[0].data as any).reduce((a: number, b: number) => a + b, 0), + totalRevenue, + totalPayments, buttons } } @@ -100,14 +111,14 @@ export const getButtonPaymentData = (n: number, periodString: string, paymentLis }, total: { payments: 1, - revenue: new Prisma.Decimal(p.value) + revenue: p.values } } buttonPaymentData[b.id] = newEntry return } prevObj.total.payments += 1 - prevObj.total.revenue = prevObj.total.revenue.plus(p.value) + prevObj.total.revenue = sumQuoteValues(prevObj.total.revenue, p.values) prevObj.displayData.isXec = prevObj.displayData.isXec === true || (p.networkId === XEC_NETWORK_ID) prevObj.displayData.isBch = prevObj.displayData.isBch === true || (p.networkId === BCH_NETWORK_ID) const lastPayment = prevObj.displayData.lastPayment as number @@ -116,6 +127,25 @@ export const getButtonPaymentData = (n: number, periodString: string, paymentLis } return buttonPaymentData } +export const sumQuoteValues = function (a: QuoteValues, b: QuoteValues): QuoteValues { + return { + usd: (new Prisma.Decimal(a.usd)).plus(new Prisma.Decimal(b.usd)), + cad: (new Prisma.Decimal(a.cad)).plus(new Prisma.Decimal(b.cad)) + } +} + +export const sumPaymentsValue = function (paymentList: Payment[]): QuoteValues { + const ret: QuoteValues = { + usd: new Prisma.Decimal(0), + cad: new Prisma.Decimal(0) + } + + for (const p of paymentList) { + ret.usd = ret.usd.plus(p.values.usd) + ret.cad = ret.cad.plus(p.values.cad) + } + return ret +} export const getUserDashboardData = async function (userId: string): Promise { let dashboardData = await getCachedDashboardData(userId) @@ -123,7 +153,7 @@ export const getUserDashboardData = async function (userId: string): Promise p.value).reduce((a, b) => a.plus(b), new Prisma.Decimal(0)) + const totalRevenue = sumPaymentsValue(paymentList) const nMonthsTotal = getNumberOfMonths(paymentList) const thirtyDays: PeriodData = getPeriodData(30, 'days', paymentList, { revenue: '#66fe91', payments: '#669cfe' }) diff --git a/redis/index.ts b/redis/index.ts index 24d27b600..0a3800cef 100644 --- a/redis/index.ts +++ b/redis/index.ts @@ -2,7 +2,7 @@ import { AddressPaymentInfo, AddressWithTransactionsWithPrices } from 'services/ import { PaybuttonWithAddresses } from 'services/paybuttonService' import { TransactionWithAddressAndPrices } from 'services/transactionService' import { fetchUsersForAddress } from 'services/userService' -import { cacheBalanceFromPayments, clearBalanceCache, getBalanceForAddress, updateBalanceCacheFromTx } from './balanceCache' +import { cacheBalanceForAddress, clearBalanceCache, getBalanceForAddress, updateBalanceCacheFromTx } from './balanceCache' import { clearDashboardCache, getUserDashboardData } from './dashboardCache' import { appendPaybuttonToAddressesCache, cacheGroupedPayments, cacheManyTxs, generateGroupedPaymentsForAddress, getPaymentList, initPaymentCache, removePaybuttonToAddressesCache } from './paymentCache' import { DashboardData, Payment } from './types' @@ -20,7 +20,7 @@ export const CacheSet = { addressCreation: async (address: AddressWithTransactionsWithPrices): Promise => { const paymentsGroupedByKey = await generateGroupedPaymentsForAddress(address) await cacheGroupedPayments(paymentsGroupedByKey) - await cacheBalanceFromPayments(address.address, Object.values(paymentsGroupedByKey).reduce((prev, curr) => prev.concat(curr), [])) + await cacheBalanceForAddress(address) }, txCreation: async (tx: TransactionWithAddressAndPrices): Promise => { const addressString = tx.address.address diff --git a/redis/paymentCache.ts b/redis/paymentCache.ts index af4bbe536..38d59b9bd 100755 --- a/redis/paymentCache.ts +++ b/redis/paymentCache.ts @@ -46,12 +46,12 @@ const getCachedWeekKeysForUser = async (userId: string): Promise => { } export const generatePaymentFromTx = async (tx: TransactionWithPrices): Promise => { - const value = (await getTransactionValue(tx)).usd + const values = (await getTransactionValue(tx)) const txAddress = await fetchAddressById(tx.addressId, true) as AddressWithPaybuttons if (txAddress === undefined) throw new Error(RESPONSE_MESSAGES.NO_ADDRESS_FOUND_FOR_TRANSACTION_404.message) return { timestamp: tx.timestamp, - value, + values, networkId: txAddress.networkId, hash: tx.hash, buttonDisplayDataList: txAddress.paybuttons.map( @@ -85,7 +85,7 @@ export const generateGroupedPaymentsForAddress = async (address: AddressWithTran const payment = await generatePaymentFromTx(tx) paymentList.push(payment) } - paymentList = paymentList.filter((p) => p.value > new Prisma.Decimal(0)) + paymentList = paymentList.filter((p) => p.values.usd > new Prisma.Decimal(0)) return getPaymentsByWeek(address.address, paymentList) } @@ -154,7 +154,7 @@ export const cacheManyTxs = async (txs: TransactionWithAddressAndPrices[]): Prom const zero = new Prisma.Decimal(0) for (const tx of txs.filter(tx => tx.amount > zero)) { const payment = await generatePaymentFromTx(tx) - if (payment.value !== new Prisma.Decimal(0)) { + if (payment.values.usd !== new Prisma.Decimal(0)) { const paymentsGroupedByKey = getPaymentsByWeek(tx.address.address, [payment]) void await cacheGroupedPaymentsAppend(paymentsGroupedByKey) } diff --git a/redis/types.ts b/redis/types.ts index 51f4b2acf..fba0e324d 100644 --- a/redis/types.ts +++ b/redis/types.ts @@ -1,4 +1,4 @@ -import { Prisma } from '@prisma/client' +import { QuoteValues } from 'services/priceService' export interface ChartColor { revenue: string @@ -6,10 +6,11 @@ export interface ChartColor { } export interface ChartData { + isMultiQuote: boolean labels: string[] datasets: [ { - data: number[] | Prisma.Decimal[] + data: number[] | QuoteValues[] borderColor: string } ] @@ -18,7 +19,7 @@ export interface ChartData { export interface PeriodData { revenue: ChartData payments: ChartData - totalRevenue: Prisma.Decimal + totalRevenue: QuoteValues totalPayments: number buttons: PaymentDataByButton } @@ -30,7 +31,7 @@ export interface DashboardData { all: PeriodData paymentList?: Payment[] total: { - revenue: Prisma.Decimal + revenue: QuoteValues payments: number buttons: number } @@ -46,7 +47,7 @@ export interface ButtonDisplayData { export interface Payment { timestamp: number - value: Prisma.Decimal + values: QuoteValues networkId: number hash: string buttonDisplayDataList: ButtonDisplayData[] @@ -55,7 +56,7 @@ export interface Payment { export interface ButtonData { displayData: ButtonDisplayData total: { - revenue: Prisma.Decimal + revenue: QuoteValues payments: number } } diff --git a/tests/integration-tests/api.test.ts b/tests/integration-tests/api.test.ts index 60400b534..98bb7ad86 100644 --- a/tests/integration-tests/api.test.ts +++ b/tests/integration-tests/api.test.ts @@ -1355,7 +1355,10 @@ describe('GET /api/dashboard', () => { all: expectedPeriodData, paymentList: expect.any(Array), total: { - revenue: expect.any(String), + revenue: { + usd: expect.any(String), + cad: expect.any(String) + }, payments: expect.any(Number), buttons: expect.any(Number) } diff --git a/tests/unittests/validators.test.ts b/tests/unittests/validators.test.ts index f74bf91ba..b405e99ee 100644 --- a/tests/unittests/validators.test.ts +++ b/tests/unittests/validators.test.ts @@ -204,15 +204,10 @@ describe('parsePaybuttonPATCHRequest', () => { }) }) -export interface PaybuttonTriggerPOSTParameters { - userId?: string - postURL?: string - postData?: string -} - -describe('parsePaybuttonTriggerPOSTRequest', () => { - const data: PaybuttonTriggerPOSTParameters = { - userId: '12345' +describe('parsePaybuttonTriggerPOSTRequest non email', () => { + const data: v.PaybuttonTriggerPOSTParameters = { + userId: '12345', + isEmailTrigger: false } it('Invalid postData JSON throws error', () => { @@ -250,6 +245,18 @@ describe('parsePaybuttonTriggerPOSTRequest', () => { v.parsePaybuttonTriggerPOSTRequest({ ...data }) }).toThrow(RESPONSE_MESSAGES.POST_URL_AND_DATA_MUST_BE_SET_TOGETHER_400.message) }) + + it('Invalid if is email but no email', () => { + expect(() => { + v.parsePaybuttonTriggerPOSTRequest({ ...data, isEmailTrigger: true }) + }).toThrow(RESPONSE_MESSAGES.MISSING_EMAIL_FOR_TRIGGER_400.message) + }) + + it('Invalid if is email but invalid email', () => { + expect(() => { + v.parsePaybuttonTriggerPOSTRequest({ ...data, isEmailTrigger: true, emails: 'thisisnotvalid@email' }) + }).toThrow(RESPONSE_MESSAGES.INVALID_EMAIL_400.message) + }) }) describe('parseStringToArray', () => { diff --git a/utils/index.ts b/utils/index.ts index b4cdf14a1..b0099a584 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,9 +1,10 @@ import xecaddr from 'xecaddrjs' import { Address, Prisma, UserProfile } from '@prisma/client' -import { RESPONSE_MESSAGES, NETWORK_SLUGS, USD_QUOTE_ID, KeyValueT, NetworkSlugsType } from '../constants/index' +import { RESPONSE_MESSAGES, NETWORK_SLUGS, KeyValueT, NetworkSlugsType, USD_QUOTE_ID } from '../constants/index' import * as bitcoinjs from 'bitcoinjs-lib' import { NextApiRequest, NextApiResponse } from 'next' import { URL } from 'url' +import { QuoteValues } from 'services/priceService' export const removeAddressPrefix = function (addressString: string): string { if (addressString.includes(':')) { @@ -82,11 +83,20 @@ const findFirstSignificantDigit = (floatNumber: number): number | undefined => { } return ret } -export const formatQuoteValue = (numberString: string, quoteId?: number): string => { - const parsedFloat = parseFloat(numberString) + +export const formatQuoteValue = (numberString: string | QuoteValues | number, quoteId?: number): string => { + let parsedFloat: number + if (typeof numberString === 'object' && 'usd' in numberString) { + parsedFloat = quoteId === USD_QUOTE_ID ? Number(numberString.usd) : Number(numberString.cad) + } else if (typeof numberString === 'string') { + parsedFloat = parseFloat(numberString) + } else { + parsedFloat = numberString + } + let minDigits: number let maxDigits: number - if (quoteId === USD_QUOTE_ID) { + if (quoteId !== undefined) { minDigits = 2 if (parsedFloat < 0.01) { maxDigits = findFirstSignificantDigit(parsedFloat) ?? 2 diff --git a/utils/validators.ts b/utils/validators.ts index 4c2966f07..45e9ab395 100644 --- a/utils/validators.ts +++ b/utils/validators.ts @@ -340,6 +340,13 @@ export const parsePaybuttonTriggerPOSTRequest = function (params: PaybuttonTrigg throw new Error(RESPONSE_MESSAGES.POST_URL_AND_DATA_MUST_BE_SET_TOGETHER_400.message) } + if ( + params.isEmailTrigger && + (params.emails === '' || params.emails === undefined) + ) { + throw new Error(RESPONSE_MESSAGES.MISSING_EMAIL_FOR_TRIGGER_400.message) + } + return { emails: params.emails, postURL,