From 69b3295f2d828f1a7b68a125f0b4acf693d527a5 Mon Sep 17 00:00:00 2001 From: anthonydaoud Date: Tue, 6 Jun 2023 11:16:55 -0400 Subject: [PATCH] feat(quote): make settlement time dynamic (#3834) ### Description Currently, the time estimation for a FiatConnect quote is set to fixed values. This PR makes it so that the time estimation is dynamic and based on data sent from the quote provider. ![SelectPaymentDynamicTime](https://github.com/valora-inc/wallet/assets/20508929/6e7a3fac-dd1f-4928-b572-3078c29fcf86) ### Test plan Added unit tests and modified existing unit test ### Related issues - Fixes ACT-366 ### Backwards compatibility Yes. --- locales/base/translation.json | 13 +- .../PaymentMethodSection.test.tsx | 10 +- src/fiatExchanges/PaymentMethodSection.tsx | 19 ++- .../quotes/ExternalQuote.test.ts | 10 +- src/fiatExchanges/quotes/ExternalQuote.ts | 12 +- .../quotes/FiatConnectQuote.test.ts | 149 +++++++++++++++++- src/fiatExchanges/quotes/FiatConnectQuote.ts | 91 ++++++++++- src/fiatExchanges/quotes/NormalizedQuote.ts | 4 +- src/fiatExchanges/quotes/constants.ts | 38 ++++- src/fiatExchanges/quotes/utils.ts | 31 ++++ src/fiatconnect/TransferStatusScreen.test.tsx | 54 +++++-- src/fiatconnect/TransferStatusScreen.tsx | 27 ++-- 12 files changed, 397 insertions(+), 61 deletions(-) create mode 100644 src/fiatExchanges/quotes/utils.ts diff --git a/locales/base/translation.json b/locales/base/translation.json index cab4acf2c0e..d5d285fe949 100644 --- a/locales/base/translation.json +++ b/locales/base/translation.json @@ -1385,7 +1385,10 @@ "idRequired": "ID Required", "numDays": "1-3 Days", "oneHour": "Less Than 1 Hour", - "lessThan24Hours": "Less Than 24 Hours", + "xHours": "Less Than {{upperBound}} Hours", + "xToYHours": "{{lowerBound}}-{{upperBound}} Hours", + "xDays": "Less Than {{upperBound}} Days", + "xToYDays": "{{lowerBound}}-{{upperBound}} Days", "bestRate": "Best Rate", "header": "Select Payment Method", "newLabel": "NEW", @@ -1490,8 +1493,12 @@ "header": "Success", "title": "Your funds are on their way!", "description": "Your transaction request has been sent & your funds will arrive in {{duration}}.", - "description1to3Days": "Your transaction request has been sent & your funds will arrive in 1-3 days.", - "description24Hours": "Your transaction request has been sent & your funds will arrive within 24 hours.", + "descriptionWithin1Hour": "Your transaction request has been sent & your funds will arrive within 1 hour.", + "descriptionWithinXHours": "Your transaction request has been sent & your funds will arrive within {{upperBound}} hours.", + "descriptionInXtoYHours": "Your transaction request has been sent & your funds will arrive in {{lowerBound}}-{{upperBound}} hours.", + "descriptionWithinXDays": "Your transaction request has been sent & your funds will arrive within {{upperBound}} days.", + "descriptionXtoYDays": "Your transaction request has been sent & your funds will arrive in {{lowerBound}}-{{upperBound}} days.", + "baseDescription": "Your transaction request has been sent.", "txDetails": "View on CeloExplorer", "continue": "Continue" }, diff --git a/src/fiatExchanges/PaymentMethodSection.test.tsx b/src/fiatExchanges/PaymentMethodSection.test.tsx index 24957ba9663..6855ecad7b8 100644 --- a/src/fiatExchanges/PaymentMethodSection.test.tsx +++ b/src/fiatExchanges/PaymentMethodSection.test.tsx @@ -198,7 +198,7 @@ describe('PaymentMethodSection', () => { const infoElement = queryByTestId('Bank/provider-0/info') expect(infoElement).toBeTruthy() expect(infoElement).toHaveTextContent( - 'selectProviderScreen.idRequired | selectProviderScreen.numDays' + 'selectProviderScreen.idRequired | selectProviderScreen.xToYDays, {"lowerBound":1,"upperBound":3}' ) }) @@ -217,7 +217,9 @@ describe('PaymentMethodSection', () => { ) const infoElement = queryByTestId('Bank/provider-0/info') expect(infoElement).toBeTruthy() - expect(infoElement).toHaveTextContent('selectProviderScreen.numDays') + expect(infoElement).toHaveTextContent( + 'selectProviderScreen.xToYHours, {"lowerBound":1,"upperBound":2}' + ) expect(infoElement).not.toHaveTextContent('selectProviderScreen.idRequired') }) @@ -237,7 +239,7 @@ describe('PaymentMethodSection', () => { CiCoCurrency.cUSD ), 'bank', - 'numDays', + 'xToYHours', ], [ PaymentMethod.FiatConnectMobileMoney as const, @@ -248,7 +250,7 @@ describe('PaymentMethodSection', () => { CiCoCurrency.cUSD ), 'mobileMoney', - 'lessThan24Hours', + 'xHours', ], ])('shows appropriate title and settlement time for %s', (paymentMethod, quotes, title, info) => { props.normalizedQuotes = quotes diff --git a/src/fiatExchanges/PaymentMethodSection.tsx b/src/fiatExchanges/PaymentMethodSection.tsx index 3f597c4553a..54da064c14e 100644 --- a/src/fiatExchanges/PaymentMethodSection.tsx +++ b/src/fiatExchanges/PaymentMethodSection.tsx @@ -8,8 +8,9 @@ import Dialog from 'src/components/Dialog' import Expandable from 'src/components/Expandable' import TokenDisplay from 'src/components/TokenDisplay' import Touchable from 'src/components/Touchable' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { SettlementEstimation, SettlementTime } from 'src/fiatExchanges/quotes/constants' import NormalizedQuote from 'src/fiatExchanges/quotes/NormalizedQuote' +import { getSettlementTimeString } from 'src/fiatExchanges/quotes/utils' import { ProviderSelectionAnalyticsData } from 'src/fiatExchanges/types' import { CICOFlow, PaymentMethod } from 'src/fiatExchanges/utils' import InfoIcon from 'src/icons/InfoIcon' @@ -21,8 +22,10 @@ import { CiCoCurrency } from 'src/utils/currencies' const SETTLEMENT_TIME_STRINGS: Record = { [SettlementTime.LESS_THAN_ONE_HOUR]: 'selectProviderScreen.oneHour', - [SettlementTime.LESS_THAN_24_HOURS]: 'selectProviderScreen.lessThan24Hours', - [SettlementTime.ONE_TO_THREE_DAYS]: 'selectProviderScreen.numDays', + [SettlementTime.LESS_THAN_X_HOURS]: 'selectProviderScreen.xHours', + [SettlementTime.X_TO_Y_HOURS]: 'selectProviderScreen.xToYHours', + [SettlementTime.LESS_THAN_X_DAYS]: 'selectProviderScreen.xDays', + [SettlementTime.X_TO_Y_DAYS]: 'selectProviderScreen.xToYDays', } export interface PaymentMethodSectionProps { @@ -172,10 +175,18 @@ export function PaymentMethodSection({ ) + const getPaymentMethodSettlementTimeString = (settlementEstimation: SettlementEstimation) => { + const { timeString, ...args } = getSettlementTimeString( + settlementEstimation, + SETTLEMENT_TIME_STRINGS + ) + return timeString ? t(timeString, args) : t('selectProviderScreen.numDays') + } + const renderInfoText = (quote: NormalizedQuote) => { const kycInfo = quote.getKycInfo() const kycString = kycInfo ? `${kycInfo} | ` : '' - return `${kycString}${t(SETTLEMENT_TIME_STRINGS[quote.getTimeEstimation()])}` + return `${kycString}${getPaymentMethodSettlementTimeString(quote.getTimeEstimation())}` } const renderFeeAmount = (normalizedQuote: NormalizedQuote, postFix: string) => { diff --git a/src/fiatExchanges/quotes/ExternalQuote.test.ts b/src/fiatExchanges/quotes/ExternalQuote.test.ts index bc337d4bbd3..59fc2227eaa 100644 --- a/src/fiatExchanges/quotes/ExternalQuote.test.ts +++ b/src/fiatExchanges/quotes/ExternalQuote.test.ts @@ -160,7 +160,11 @@ describe('ExternalQuote', () => { provider: mockProviders[1], flow: CICOFlow.CashIn, }) - expect(quote.getTimeEstimation()).toEqual(SettlementTime.ONE_TO_THREE_DAYS) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.X_TO_Y_DAYS, + lowerBound: 1, + upperBound: 3, + }) }) it('returns oneHour for Card', () => { const quote = new ExternalQuote({ @@ -168,7 +172,9 @@ describe('ExternalQuote', () => { provider: mockProviders[0], flow: CICOFlow.CashIn, }) - expect(quote.getTimeEstimation()).toEqual(SettlementTime.LESS_THAN_ONE_HOUR) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, + }) }) }) diff --git a/src/fiatExchanges/quotes/ExternalQuote.ts b/src/fiatExchanges/quotes/ExternalQuote.ts index b8d27b4014e..93fe2eaa498 100644 --- a/src/fiatExchanges/quotes/ExternalQuote.ts +++ b/src/fiatExchanges/quotes/ExternalQuote.ts @@ -1,5 +1,9 @@ import BigNumber from 'bignumber.js' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { + DEFAULT_BANK_SETTLEMENT_ESTIMATION, + DEFAULT_CARD_SETTLEMENT_ESTIMATION, + SettlementEstimation, +} from 'src/fiatExchanges/quotes/constants' import NormalizedQuote from 'src/fiatExchanges/quotes/NormalizedQuote' import { CICOFlow, @@ -96,11 +100,11 @@ export default class ExternalQuote extends NormalizedQuote { return strings.idRequired } - getTimeEstimation(): SettlementTime { + getTimeEstimation(): SettlementEstimation { // payment method can only be bank or card return this.getPaymentMethod() === PaymentMethod.Bank - ? SettlementTime.ONE_TO_THREE_DAYS - : SettlementTime.LESS_THAN_ONE_HOUR + ? DEFAULT_BANK_SETTLEMENT_ESTIMATION + : DEFAULT_CARD_SETTLEMENT_ESTIMATION } navigate(): void { diff --git a/src/fiatExchanges/quotes/FiatConnectQuote.test.ts b/src/fiatExchanges/quotes/FiatConnectQuote.test.ts index ff5aed14cd1..77cfa620e02 100644 --- a/src/fiatExchanges/quotes/FiatConnectQuote.test.ts +++ b/src/fiatExchanges/quotes/FiatConnectQuote.test.ts @@ -10,7 +10,11 @@ import { FiatExchangeEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { FiatConnectQuoteSuccess } from 'src/fiatconnect' import { selectFiatConnectQuote } from 'src/fiatconnect/slice' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { + DEFAULT_BANK_SETTLEMENT_ESTIMATION, + DEFAULT_MOBILE_MONEY_SETTLEMENT_ESTIMATION, + SettlementTime, +} from 'src/fiatExchanges/quotes/constants' import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' import { CICOFlow, PaymentMethod } from 'src/fiatExchanges/utils' import { Currency } from 'src/utils/currencies' @@ -229,22 +233,155 @@ describe('FiatConnectQuote', () => { }) describe('.getTimeEstimation', () => { - it('returns 1-3 days for bank account', () => { + it('returns default for bank account when no bounds are present', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: undefined, + settlementTimeUpperBound: undefined, + } const quote = new FiatConnectQuote({ flow: CICOFlow.CashIn, - quote: mockFiatConnectQuotes[1] as FiatConnectQuoteSuccess, + quote: quoteData, fiatAccountType: FiatAccountType.BankAccount, }) - expect(quote.getTimeEstimation()).toEqual(SettlementTime.ONE_TO_THREE_DAYS) + expect(quote.getTimeEstimation()).toEqual(DEFAULT_BANK_SETTLEMENT_ESTIMATION) }) - it('returns 24 hours for mobile money', () => { + it('returns default for mobile money when no bounds are present', () => { const quote = new FiatConnectQuote({ flow: CICOFlow.CashIn, quote: mockFiatConnectQuotes[4] as FiatConnectQuoteSuccess, fiatAccountType: FiatAccountType.MobileMoney, }) - expect(quote.getTimeEstimation()).toEqual(SettlementTime.LESS_THAN_24_HOURS) + expect(quote.getTimeEstimation()).toEqual(DEFAULT_MOBILE_MONEY_SETTLEMENT_ESTIMATION) + }) + + it('when upper bound is less than one hour, "less than one hour" is shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: '300', // 5 minutes + settlementTimeUpperBound: '600', // 10 minutes + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, + }) + }) + + it('when lower bound is in minutes and upper bound is greater than one hour, "{lowerBound} to {upperBound} hours" is shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: '300', // 5 minutes + settlementTimeUpperBound: '7200', // 2 hours + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.X_TO_Y_HOURS, + lowerBound: 1, + upperBound: 2, + }) + }) + + it('when lower bound is not present, "less than {upperBound}" is shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: undefined, + settlementTimeUpperBound: '7200', // 2 hours + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: 2, + }) + }) + + it('when lower bound equals upper bound, "less than {upperBound}" is shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: '7200', // 2 hours + settlementTimeUpperBound: '7200', // 2 hours + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: 2, + }) + }) + + it('when upper bound equals 24 hours, "less than 24 hours" is shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: undefined, + settlementTimeUpperBound: '86400', // 1 day + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: 24, + }) + }) + + it('when upper bound is greater than 24 hours, but lower bound is less than day, "1 to {upperBound} days" shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: '300', // 5 minutes + settlementTimeUpperBound: '86401', // over 1 day (rounds up to two days) + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.X_TO_Y_DAYS, + lowerBound: 1, + upperBound: 2, + }) + }) + + it('when upper bound is greater than 24 hours and lower bound equals upper, "less than {upperBound} days" shown', () => { + const quoteData = _.cloneDeep(mockFiatConnectQuotes[1]) as FiatConnectQuoteSuccess + quoteData.fiatAccount.BankAccount = { + ...quoteData.fiatAccount.BankAccount!, + settlementTimeLowerBound: '86401', // over 1 day (rounds up to two days) + settlementTimeUpperBound: '86401', // over 1 day (rounds up to two days) + } + const quote = new FiatConnectQuote({ + flow: CICOFlow.CashIn, + quote: quoteData, + fiatAccountType: FiatAccountType.BankAccount, + }) + expect(quote.getTimeEstimation()).toEqual({ + settlementTime: SettlementTime.LESS_THAN_X_DAYS, + upperBound: 2, + }) }) }) diff --git a/src/fiatExchanges/quotes/FiatConnectQuote.ts b/src/fiatExchanges/quotes/FiatConnectQuote.ts index e2deee235c3..a4117733983 100644 --- a/src/fiatExchanges/quotes/FiatConnectQuote.ts +++ b/src/fiatExchanges/quotes/FiatConnectQuote.ts @@ -10,7 +10,13 @@ import BigNumber from 'bignumber.js' import { Dispatch } from 'redux' import { FiatConnectProviderInfo, FiatConnectQuoteSuccess } from 'src/fiatconnect' import { selectFiatConnectQuote } from 'src/fiatconnect/slice' -import { DEFAULT_ALLOWED_VALUES, SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { + DEFAULT_ALLOWED_VALUES, + DEFAULT_BANK_SETTLEMENT_ESTIMATION, + DEFAULT_MOBILE_MONEY_SETTLEMENT_ESTIMATION, + SettlementEstimation, + SettlementTime, +} from 'src/fiatExchanges/quotes/constants' import NormalizedQuote from 'src/fiatExchanges/quotes/NormalizedQuote' import { CICOFlow, PaymentMethod } from 'src/fiatExchanges/utils' import i18n from 'src/i18n' @@ -40,6 +46,56 @@ const SUPPORTED_KYC_SCHEMAS = new Set([ KycSchema.PersonalDataAndDocumentsDetailed, ]) +const SECONDS_IN_HOUR = 60 * 60 + +const hoursToSettlementEstimation = ({ + lowerBoundInHours, + upperBoundInHours, +}: { + lowerBoundInHours: number + upperBoundInHours: number +}): SettlementEstimation => { + if (upperBoundInHours <= 1) { + return { + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, + } + } + + if (lowerBoundInHours === upperBoundInHours || lowerBoundInHours === 0) { + return { + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: upperBoundInHours, + } + } + + return { + settlementTime: SettlementTime.X_TO_Y_HOURS, + lowerBound: lowerBoundInHours, + upperBound: upperBoundInHours, + } +} + +const daysToSettlementEstimation = ({ + lowerBoundInDays, + upperBoundInDays, +}: { + lowerBoundInDays: number + upperBoundInDays: number +}): SettlementEstimation => { + if (lowerBoundInDays === upperBoundInDays || lowerBoundInDays === 0) { + return { + settlementTime: SettlementTime.LESS_THAN_X_DAYS, + upperBound: upperBoundInDays, + } + } + + return { + settlementTime: SettlementTime.X_TO_Y_DAYS, + lowerBound: lowerBoundInDays, + upperBound: upperBoundInDays, + } +} + export default class FiatConnectQuote extends NormalizedQuote { quote: FiatConnectQuoteSuccess fiatAccountType: FiatAccountType @@ -152,12 +208,33 @@ export default class FiatConnectQuote extends NormalizedQuote { return this.quoteResponseKycSchema?.kycSchema } - // TODO: Dynamically generate time estimation strings - getTimeEstimation(): SettlementTime { - // payment method can only be bank or fc mobile money - return this.getPaymentMethod() === PaymentMethod.Bank - ? SettlementTime.ONE_TO_THREE_DAYS - : SettlementTime.LESS_THAN_24_HOURS + getTimeEstimation(): SettlementEstimation { + const fiatAccountInfo = this.quote.fiatAccount[this.getFiatAccountType()] + + const lowerBound = fiatAccountInfo?.settlementTimeLowerBound + ? parseInt(fiatAccountInfo?.settlementTimeLowerBound) + : 0 + const upperBound = fiatAccountInfo?.settlementTimeUpperBound + ? parseInt(fiatAccountInfo?.settlementTimeUpperBound) + : 0 + if (lowerBound < 0 || upperBound <= 0 || lowerBound > upperBound) { + // payment method can only be bank or fc mobile money + // TODO: ensure that this gets updated once more payment types are possible + return this.getPaymentMethod() === PaymentMethod.Bank + ? DEFAULT_BANK_SETTLEMENT_ESTIMATION + : DEFAULT_MOBILE_MONEY_SETTLEMENT_ESTIMATION + } + + const lowerBoundInHours = Math.ceil(lowerBound / SECONDS_IN_HOUR) + const upperBoundInHours = Math.ceil(upperBound / SECONDS_IN_HOUR) + if (upperBoundInHours <= 24) { + return hoursToSettlementEstimation({ lowerBoundInHours, upperBoundInHours }) + } else { + return daysToSettlementEstimation({ + lowerBoundInDays: Math.ceil(lowerBoundInHours / 24), + upperBoundInDays: Math.ceil(upperBoundInHours / 24), + }) + } } navigate(dispatch: Dispatch): void { diff --git a/src/fiatExchanges/quotes/NormalizedQuote.ts b/src/fiatExchanges/quotes/NormalizedQuote.ts index 646fce1db5e..bb85ca926dc 100644 --- a/src/fiatExchanges/quotes/NormalizedQuote.ts +++ b/src/fiatExchanges/quotes/NormalizedQuote.ts @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js' import { Dispatch } from 'redux' import { FiatExchangeEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { SettlementEstimation } from 'src/fiatExchanges/quotes/constants' import { ProviderSelectionAnalyticsData } from 'src/fiatExchanges/types' import { CICOFlow, PaymentMethod } from 'src/fiatExchanges/utils' import { TokenBalance } from 'src/tokens/slice' @@ -20,7 +20,7 @@ export default abstract class NormalizedQuote { ): BigNumber | null abstract getCryptoType(): CiCoCurrency abstract getKycInfo(): string | null - abstract getTimeEstimation(): SettlementTime + abstract getTimeEstimation(): SettlementEstimation abstract getProviderName(): string abstract getProviderLogo(): string abstract getProviderId(): string diff --git a/src/fiatExchanges/quotes/constants.ts b/src/fiatExchanges/quotes/constants.ts index d7aa0296ecd..7f2181f642d 100644 --- a/src/fiatExchanges/quotes/constants.ts +++ b/src/fiatExchanges/quotes/constants.ts @@ -19,7 +19,39 @@ export const DEFAULT_ALLOWED_VALUES: DefaultAllowedValues = { export enum SettlementTime { // List of available settlement time strings for SelectProvider and // TransferStatus screens - LESS_THAN_ONE_HOUR = 'LESS_THAN_ONE_HOUR', // only for SelectProvider - LESS_THAN_24_HOURS = 'LESS_THAN_24_HOURS', - ONE_TO_THREE_DAYS = 'ONE_TO_THREE_DAYS', + LESS_THAN_ONE_HOUR = 'LESS_THAN_ONE_HOUR', + LESS_THAN_X_HOURS = 'LESS_THAN_X_HOURS', + X_TO_Y_HOURS = 'X_TO_Y_HOURS', + LESS_THAN_X_DAYS = 'LESS_THAN_X_DAYS', + X_TO_Y_DAYS = 'X_TO_Y_DAYS', +} + +export type SettlementEstimation = + // Less than one hour is necessary because the text string uses the word "hour" instead of "hours" + | { + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR + } + | { + settlementTime: SettlementTime.LESS_THAN_X_HOURS | SettlementTime.LESS_THAN_X_DAYS + upperBound: number + } + | { + settlementTime: SettlementTime.X_TO_Y_HOURS | SettlementTime.X_TO_Y_DAYS + lowerBound: number + upperBound: number + } + +export const DEFAULT_BANK_SETTLEMENT_ESTIMATION: SettlementEstimation = { + settlementTime: SettlementTime.X_TO_Y_DAYS, + lowerBound: 1, + upperBound: 3, +} + +export const DEFAULT_MOBILE_MONEY_SETTLEMENT_ESTIMATION: SettlementEstimation = { + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: 24, +} + +export const DEFAULT_CARD_SETTLEMENT_ESTIMATION: SettlementEstimation = { + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, } diff --git a/src/fiatExchanges/quotes/utils.ts b/src/fiatExchanges/quotes/utils.ts new file mode 100644 index 00000000000..56f647f464f --- /dev/null +++ b/src/fiatExchanges/quotes/utils.ts @@ -0,0 +1,31 @@ +import { SettlementEstimation, SettlementTime } from 'src/fiatExchanges/quotes/constants' +import Logger from 'src/utils/Logger' + +const TAG = 'fiatExchanges/quotes/utils' + +export const getSettlementTimeString = ( + settlementEstimation: SettlementEstimation, + settlementTimeStrings: Record +) => { + switch (settlementEstimation.settlementTime) { + case SettlementTime.LESS_THAN_ONE_HOUR: + return { timeString: settlementTimeStrings[SettlementTime.LESS_THAN_ONE_HOUR] } + case SettlementTime.LESS_THAN_X_HOURS: + case SettlementTime.LESS_THAN_X_DAYS: + return { + timeString: settlementTimeStrings[settlementEstimation.settlementTime], + upperBound: settlementEstimation.upperBound, + } + case SettlementTime.X_TO_Y_HOURS: + case SettlementTime.X_TO_Y_DAYS: + return { + timeString: settlementTimeStrings[settlementEstimation.settlementTime], + lowerBound: settlementEstimation.lowerBound, + upperBound: settlementEstimation.upperBound, + } + default: + // Should not be reachable + Logger.warn(TAG, 'Unexpected settlement time value in settlement estimation') + return {} + } +} diff --git a/src/fiatconnect/TransferStatusScreen.test.tsx b/src/fiatconnect/TransferStatusScreen.test.tsx index b448435c66a..9e0584a47b0 100644 --- a/src/fiatconnect/TransferStatusScreen.test.tsx +++ b/src/fiatconnect/TransferStatusScreen.test.tsx @@ -8,7 +8,7 @@ import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { FiatConnectQuoteSuccess } from 'src/fiatconnect' import { FiatConnectTransfer, SendingTransferStatus } from 'src/fiatconnect/slice' import TransferStatusScreen from 'src/fiatconnect/TransferStatusScreen' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { SettlementEstimation, SettlementTime } from 'src/fiatExchanges/quotes/constants' import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' import { CICOFlow } from 'src/fiatExchanges/utils' import { navigate, navigateHome } from 'src/navigator/NavigationService' @@ -103,22 +103,44 @@ describe('TransferStatusScreen', () => { } ) }) + + const lessThanOneHour: SettlementEstimation = { + settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, + } + const lessThan24Hours: SettlementEstimation = { + settlementTime: SettlementTime.LESS_THAN_X_HOURS, + upperBound: 24, + } + const oneToThreeDays: SettlementEstimation = { + settlementTime: SettlementTime.X_TO_Y_DAYS, + lowerBound: 1, + upperBound: 3, + } it.each([ - [SettlementTime.LESS_THAN_24_HOURS, 'description24Hours'], - [SettlementTime.ONE_TO_THREE_DAYS, 'description1to3Days'], - ])('shows appropriate description for settlement time %s', (settlementTime, stringSuffix) => { - const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) - const props = mockScreenProps(CICOFlow.CashOut) - jest - .spyOn(props.route.params.normalizedQuote, 'getTimeEstimation') - .mockReturnValue(settlementTime) - const { getByText } = render( - - - - ) - expect(getByText(`fiatConnectStatusScreen.success.${stringSuffix}`)).toBeTruthy() - }) + [lessThanOneHour, 'descriptionWithin1Hour', ''], + [lessThan24Hours, 'descriptionWithinXHours', '{"upperBound":24}'], + [oneToThreeDays, 'descriptionXtoYDays', '{"lowerBound":1,"upperBound":3}'], + ])( + 'shows appropriate description for settlement time %s', + (settlementTime, stringSuffix, stringArgs) => { + const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) + const props = mockScreenProps(CICOFlow.CashOut) + jest + .spyOn(props.route.params.normalizedQuote, 'getTimeEstimation') + .mockReturnValue(settlementTime) + const { getByText } = render( + + + + ) + + const translationKey = `fiatConnectStatusScreen.success.${stringSuffix}` + const translationKeyAndArgs = stringArgs + ? `${translationKey}, ${stringArgs}` + : translationKey + expect(getByText(translationKeyAndArgs)).toBeTruthy() + } + ) it('shows TX details on Celo Explorer on success for transfer outs', () => { const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) const { queryByTestId, getByTestId } = render( diff --git a/src/fiatconnect/TransferStatusScreen.tsx b/src/fiatconnect/TransferStatusScreen.tsx index 75357815b21..69eaeca1f8a 100644 --- a/src/fiatconnect/TransferStatusScreen.tsx +++ b/src/fiatconnect/TransferStatusScreen.tsx @@ -11,8 +11,9 @@ import TextButton from 'src/components/TextButton' import Touchable from 'src/components/Touchable' import { fiatConnectTransferSelector } from 'src/fiatconnect/selectors' import { FiatAccount, SendingTransferStatus } from 'src/fiatconnect/slice' -import { SettlementTime } from 'src/fiatExchanges/quotes/constants' +import { SettlementEstimation, SettlementTime } from 'src/fiatExchanges/quotes/constants' import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' +import { getSettlementTimeString } from 'src/fiatExchanges/quotes/utils' import { CICOFlow } from 'src/fiatExchanges/utils' import CheckmarkCircle from 'src/icons/CheckmarkCircle' import CircledIcon from 'src/icons/CircledIcon' @@ -32,12 +33,12 @@ import { walletAddressSelector } from 'src/web3/selectors' const LOADING_DESCRIPTION_TIMEOUT_MS = 8000 -// FC quotes don't return <1h -type SupportedSettlementTimes = Exclude - -const DESCRIPTION_STRINGS: Record = { - [SettlementTime.LESS_THAN_24_HOURS]: 'fiatConnectStatusScreen.success.description24Hours', - [SettlementTime.ONE_TO_THREE_DAYS]: 'fiatConnectStatusScreen.success.description1to3Days', +const DESCRIPTION_STRINGS: Record = { + [SettlementTime.LESS_THAN_ONE_HOUR]: 'fiatConnectStatusScreen.success.descriptionWithin1Hour', + [SettlementTime.LESS_THAN_X_HOURS]: 'fiatConnectStatusScreen.success.descriptionWithinXHours', + [SettlementTime.X_TO_Y_HOURS]: 'fiatConnectStatusScreen.success.descriptionInXtoYHours', + [SettlementTime.LESS_THAN_X_DAYS]: 'fiatConnectStatusScreen.success.descriptionWithinXDays', + [SettlementTime.X_TO_Y_DAYS]: 'fiatConnectStatusScreen.success.descriptionXtoYDays', } type Props = NativeStackScreenProps @@ -133,12 +134,18 @@ function SuccessOrProcessingSection({ | FiatExchangeEvents.cico_fc_transfer_success_view_tx | FiatExchangeEvents.cico_fc_transfer_processing_view_tx + const getTransferSettlementTimeString = (settlementEstimation: SettlementEstimation) => { + const { timeString, ...args } = getSettlementTimeString( + settlementEstimation, + DESCRIPTION_STRINGS + ) + return timeString ? t(timeString, args) : t('fiatConnectStatusScreen.success.baseDescription') + } + if (status === SendingTransferStatus.Completed) { icon = title = t('fiatConnectStatusScreen.success.title') - description = t( - DESCRIPTION_STRINGS[normalizedQuote.getTimeEstimation() as SupportedSettlementTimes] - ) + description = getTransferSettlementTimeString(normalizedQuote.getTimeEstimation()) continueEvent = FiatExchangeEvents.cico_fc_transfer_success_complete txDetailsEvent = FiatExchangeEvents.cico_fc_transfer_success_view_tx } else {