From bae5e1871cb2d3ee8bcfefda2c4c3b890b4626ff Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sat, 24 Jan 2026 12:08:50 +0700 Subject: [PATCH 1/2] refactor hasCardAuthenticatedError to use amountedOwed --- .../useNavigationTabBarIndicatorChecks.ts | 3 + src/libs/SubscriptionUtils.ts | 29 ++-- src/pages/settings/InitialSettingsPage.tsx | 4 +- .../Subscription/CardSection/CardSection.tsx | 6 +- .../Subscription/CardSection/utils.ts | 8 +- tests/unit/CardsSectionUtilsTest.ts | 15 ++- tests/unit/SubscriptionUtilsTest.ts | 125 ++++++++++++++++-- 7 files changed, 158 insertions(+), 32 deletions(-) diff --git a/src/hooks/useNavigationTabBarIndicatorChecks.ts b/src/hooks/useNavigationTabBarIndicatorChecks.ts index 16448379a732a..a778135e10f25 100644 --- a/src/hooks/useNavigationTabBarIndicatorChecks.ts +++ b/src/hooks/useNavigationTabBarIndicatorChecks.ts @@ -37,6 +37,7 @@ function useNavigationTabBarIndicatorChecks(): NavigationTabBarChecksResult { const [billingDisputePending] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING, {canBeMissing: true}); const [retryBillingFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED, {canBeMissing: true}); const [billingStatus] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, {canBeMissing: true}); + const [amountOwed = 0] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, {canBeMissing: true}); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true}); const hasBrokenFeedConnection = checkIfFeedConnectionIsBroken(allCards, CONST.EXPENSIFY_CARD.BANK); @@ -69,6 +70,7 @@ function useNavigationTabBarIndicatorChecks(): NavigationTabBarChecksResult { retryBillingFailed, fundList, billingStatus, + amountOwed, ), [CONST.INDICATOR_STATUS.HAS_REIMBURSEMENT_ACCOUNT_ERRORS]: Object.keys(reimbursementAccount?.errors ?? {}).length > 0, [CONST.INDICATOR_STATUS.HAS_LOGIN_LIST_ERROR]: !!loginList && hasLoginListError(loginList), @@ -88,6 +90,7 @@ function useNavigationTabBarIndicatorChecks(): NavigationTabBarChecksResult { retryBillingFailed, fundList, billingStatus, + amountOwed, ), [CONST.INDICATOR_STATUS.HAS_PARTIALLY_SETUP_BANK_ACCOUNT_INFO]: hasPartiallySetupBankAccount(bankAccountList), }; diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 0493ecb0378b7..be6a75134721b 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -60,10 +60,10 @@ Onyx.connect({ }, }); -let amountOwed: OnyxEntry; +let privateAmountOwed: OnyxEntry; Onyx.connect({ key: ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, - callback: (value) => (amountOwed = value), + callback: (value) => (privateAmountOwed = value), }); let ownerBillingGraceEndPeriod: OnyxEntry; @@ -110,22 +110,22 @@ function hasGracePeriodOverdue(): boolean { /** * @returns The amount owed by the workspace owner. */ -function getAmountOwed(): number { - return amountOwed ?? 0; +function getAmountOwed(amountOwed?: OnyxEntry): number { + return amountOwed ?? privateAmountOwed ?? 0; } /** * @returns Whether there is an amount owed by the workspace owner. */ function hasAmountOwed(): boolean { - return !!amountOwed; + return !!privateAmountOwed; } /** * @returns Whether there is a card authentication error. */ -function hasCardAuthenticatedError(stripeCustomerId: OnyxEntry) { - return stripeCustomerId?.status === 'authentication_required' && getAmountOwed() === 0; +function hasCardAuthenticatedError(stripeCustomerId: OnyxEntry, amountOwed: number) { + return stripeCustomerId?.status === 'authentication_required' && amountOwed === 0; } /** @@ -139,7 +139,7 @@ function hasBillingDisputePending(billingDisputePending: number | undefined) { * @returns Whether there is a card expired error. */ function hasCardExpiredError(billingStatus: OnyxEntry) { - return billingStatus?.declineReason === 'expired_card' && amountOwed !== 0; + return billingStatus?.declineReason === 'expired_card' && privateAmountOwed !== 0; } /** @@ -278,6 +278,7 @@ function getSubscriptionStatus( retryBillingFailed: boolean | undefined, fundList: OnyxEntry, billingStatus: OnyxEntry, + amountOwed: number, ): SubscriptionStatus | undefined { if (hasOverdueGracePeriod()) { if (hasAmountOwed()) { @@ -323,7 +324,7 @@ function getSubscriptionStatus( } // 6. Card not authenticated - if (hasCardAuthenticatedError(stripeCustomerId)) { + if (hasCardAuthenticatedError(stripeCustomerId, amountOwed)) { return { status: PAYMENT_STATUS.CARD_AUTHENTICATION_REQUIRED, isError: true, @@ -382,8 +383,9 @@ function hasSubscriptionRedDotError( retryBillingFailed: boolean | undefined, fundList: OnyxEntry, billingStatus: OnyxEntry, + amountOwed: number, ): boolean { - return getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus)?.isError ?? false; + return getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus, amountOwed)?.isError ?? false; } /** @@ -396,8 +398,9 @@ function hasSubscriptionGreenDotInfo( retryBillingFailed: boolean | undefined, fundList: OnyxEntry, billingStatus: OnyxEntry, + amountOwed: number, ): boolean { - return getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus)?.isError === false; + return getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus, amountOwed)?.isError === false; } /** @@ -509,8 +512,8 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { if ( isPolicyOwner(policy, currentUserAccountID) && ownerBillingGraceEndPeriod && - amountOwed !== undefined && - amountOwed > 0 && + privateAmountOwed !== undefined && + privateAmountOwed > 0 && isAfter(currentDate, fromUnixTime(ownerBillingGraceEndPeriod)) ) { return true; diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 6165b60e050f0..59fe266fffb33 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -119,6 +119,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr const [billingDisputePending] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING, {canBeMissing: true}); const [retryBillingFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED, {canBeMissing: true}); const [billingStatus] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, {canBeMissing: true}); + const [amountOwed = 0] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, {canBeMissing: true}); const {shouldUseNarrowLayout} = useResponsiveLayout(); const network = useNetwork(); const theme = useTheme(); @@ -239,7 +240,8 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr icon: icons.CreditCard, screenName: SCREENS.SETTINGS.SUBSCRIPTION.ROOT, brickRoadIndicator: - !!privateSubscription?.errors || hasSubscriptionRedDotError(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus) + !!privateSubscription?.errors || + hasSubscriptionRedDotError(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus, amountOwed) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, badgeText: freeTrialText, diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 34d9b55c84b4a..5ad4d3b93de09 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -72,6 +72,7 @@ function CardSection() { const [billingDisputePending] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING, {canBeMissing: true}); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID, {canBeMissing: true}); const [billingStatusOnyx] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, {canBeMissing: true}); + const [amountOwed = 0] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, {canBeMissing: true}); const requestRefund = useCallback(() => { requestRefundByUser(); setIsRequestRefundModalVisible(false); @@ -101,6 +102,7 @@ function CardSection() { billingStatus: billingStatusOnyx, creditCardEyesIcon: illustrations.CreditCardEyes, fundList, + amountOwed, }), ); @@ -125,6 +127,7 @@ function CardSection() { billingStatus: billingStatusOnyx, creditCardEyesIcon: illustrations.CreditCardEyes, fundList, + amountOwed, }), ); }, [ @@ -139,6 +142,7 @@ function CardSection() { billingStatusOnyx, illustrations.CreditCardEyes, fundList, + amountOwed, ]); const handleRetryPayment = () => { @@ -230,7 +234,7 @@ function CardSection() { large /> )} - {hasCardAuthenticatedError(privateStripeCustomerID) && ( + {hasCardAuthenticatedError(privateStripeCustomerID, amountOwed) && ( ; creditCardEyesIcon?: IconAsset; fundList: OnyxEntry; + amountOwed: number; }; function getBillingStatus({ @@ -48,12 +49,11 @@ function getBillingStatus({ billingStatus, creditCardEyesIcon, fundList, + amountOwed, }: GetBillingStatusProps): BillingStatusResult | undefined { const cardEnding = (accountData?.cardNumber ?? '')?.slice(-4); - const amountOwed = getAmountOwed(); - - const subscriptionStatus = getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus); + const subscriptionStatus = getSubscriptionStatus(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus, amountOwed); const endDate = getOverdueGracePeriodDate(); diff --git a/tests/unit/CardsSectionUtilsTest.ts b/tests/unit/CardsSectionUtilsTest.ts index 248c85c473bd0..ec7b582faacaf 100644 --- a/tests/unit/CardsSectionUtilsTest.ts +++ b/tests/unit/CardsSectionUtilsTest.ts @@ -30,7 +30,6 @@ const mockGetSubscriptionStatus = jest.fn(); jest.mock('@libs/SubscriptionUtils', () => ({ ...jest.requireActual('@libs/SubscriptionUtils'), - getAmountOwed: () => AMOUNT_OWED, getOverdueGracePeriodDate: () => GRACE_PERIOD_DATE, getSubscriptionStatus: () => mockGetSubscriptionStatus() as BillingStatusResult, })); @@ -106,6 +105,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toBeUndefined(); }); @@ -125,6 +125,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.policyOwnerAmountOwed.title', @@ -162,6 +163,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.policyOwnerAmountOwedOverdue.title', @@ -187,6 +189,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.policyOwnerAmountOwedOverdue.title', @@ -212,6 +215,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.policyOwnerUnderInvoicing.title', @@ -237,6 +241,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.policyOwnerUnderInvoicingOverdue.title', @@ -262,6 +267,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.billingDisputePending.title', @@ -287,6 +293,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.cardAuthenticationRequired.title', @@ -312,6 +319,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.insufficientFunds.title', @@ -337,6 +345,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.cardExpired.title', @@ -356,6 +365,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.cardExpired.title', @@ -381,6 +391,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.cardExpireSoon.title', @@ -406,6 +417,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.retryBillingSuccess.title', @@ -431,6 +443,7 @@ describe('CardSectionUtils', () => { creditCardEyesIcon, fundList: undefined, billingStatus: undefined, + amountOwed: AMOUNT_OWED, }), ).toEqual({ title: 'subscription.billingBanner.retryBillingError.title', diff --git a/tests/unit/SubscriptionUtilsTest.ts b/tests/unit/SubscriptionUtilsTest.ts index 40bfc311b1190..a0fd12982572a 100644 --- a/tests/unit/SubscriptionUtilsTest.ts +++ b/tests/unit/SubscriptionUtilsTest.ts @@ -5,8 +5,12 @@ import type {OnyxEntry} from 'react-native-onyx'; import { calculateRemainingFreeTrialDays, doesUserHavePaymentCardAdded, + getAmountOwed, getEarlyDiscountInfo, getSubscriptionStatus, + hasCardAuthenticatedError, + hasSubscriptionGreenDotInfo, + hasSubscriptionRedDotError, hasUserFreeTrialEnded, isUserOnFreeTrial, PAYMENT_STATUS, @@ -354,7 +358,7 @@ describe('SubscriptionUtils', () => { it('should return undefined by default', () => { const stripeCustomerIdForDefault: Partial> = {}; // @ts-expect-error - This is a test case - expect(getSubscriptionStatus(stripeCustomerIdForDefault, false, undefined, undefined, undefined, undefined)).toBeUndefined(); + expect(getSubscriptionStatus(stripeCustomerIdForDefault, false, undefined, undefined, undefined, undefined, 0)).toBeUndefined(); }); it('should return POLICY_OWNER_WITH_AMOUNT_OWED status', async () => { @@ -363,7 +367,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: AMOUNT_OWED, }); - expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined, AMOUNT_OWED)).toEqual({ status: PAYMENT_STATUS.POLICY_OWNER_WITH_AMOUNT_OWED, isError: true, }); @@ -375,7 +379,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: AMOUNT_OWED, }); - expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined, AMOUNT_OWED)).toEqual({ status: PAYMENT_STATUS.POLICY_OWNER_WITH_AMOUNT_OWED_OVERDUE, isError: true, }); @@ -387,7 +391,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: 0, }); - expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined, 0)).toEqual({ status: PAYMENT_STATUS.OWNER_OF_POLICY_UNDER_INVOICING_OVERDUE, isError: true, }); @@ -398,7 +402,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: GRACE_PERIOD_DATE, }); - expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, undefined, 0)).toEqual({ status: PAYMENT_STATUS.OWNER_OF_POLICY_UNDER_INVOICING, isError: true, }); @@ -410,7 +414,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_BILLING_DISPUTE_PENDING]: 1, }); - expect(getSubscriptionStatus(stripeCustomerId, false, 1, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, 1, undefined, undefined, undefined, 0)).toEqual({ status: PAYMENT_STATUS.BILLING_DISPUTE_PENDING, isError: true, }); @@ -423,7 +427,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID]: STRIPE_CUSTOMER_ID, }); - expect(getSubscriptionStatus(stripeCustomerId, false, 0, undefined, undefined, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, 0, undefined, undefined, undefined, 0)).toEqual({ status: PAYMENT_STATUS.CARD_AUTHENTICATION_REQUIRED, isError: true, }); @@ -436,7 +440,7 @@ describe('SubscriptionUtils', () => { [ONYXKEYS.NVP_PRIVATE_BILLING_STATUS]: BILLING_STATUS_INSUFFICIENT_FUNDS, }); - expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, BILLING_STATUS_INSUFFICIENT_FUNDS)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerId, false, undefined, undefined, undefined, BILLING_STATUS_INSUFFICIENT_FUNDS, AMOUNT_OWED)).toEqual({ status: PAYMENT_STATUS.INSUFFICIENT_FUNDS, isError: true, }); @@ -445,6 +449,7 @@ describe('SubscriptionUtils', () => { it('should return CARD_EXPIRED status', async () => { await Onyx.multiSet({ [ONYXKEYS.NVP_PRIVATE_BILLING_STATUS]: BILLING_STATUS_EXPIRED_CARD, + [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: AMOUNT_OWED, }); const stripeCustomerIdForCardExpired: Partial> = { @@ -454,7 +459,7 @@ describe('SubscriptionUtils', () => { }; // @ts-expect-error - This is a test case - expect(getSubscriptionStatus(stripeCustomerIdForCardExpired, false, undefined, undefined, undefined, BILLING_STATUS_EXPIRED_CARD)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerIdForCardExpired, false, undefined, undefined, undefined, BILLING_STATUS_EXPIRED_CARD, AMOUNT_OWED)).toEqual({ status: PAYMENT_STATUS.CARD_EXPIRED, isError: true, }); @@ -474,7 +479,7 @@ describe('SubscriptionUtils', () => { }; // @ts-expect-error - This is a test case - expect(getSubscriptionStatus(stripeCustomerIdForCardExpireSoon, false, undefined, undefined, FUND_LIST, {})).toEqual({ + expect(getSubscriptionStatus(stripeCustomerIdForCardExpireSoon, false, undefined, undefined, FUND_LIST, {}, 0)).toEqual({ status: PAYMENT_STATUS.CARD_EXPIRE_SOON, }); }); @@ -491,7 +496,7 @@ describe('SubscriptionUtils', () => { currency: 'USD', }; // @ts-expect-error - This is a test case - expect(getSubscriptionStatus(stripeCustomerIdForRetryBillingSuccess, true, undefined, undefined, {}, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerIdForRetryBillingSuccess, true, undefined, undefined, {}, undefined, 0)).toEqual({ status: PAYMENT_STATUS.RETRY_BILLING_SUCCESS, isError: false, }); @@ -510,13 +515,109 @@ describe('SubscriptionUtils', () => { currency: 'USD', }; // @ts-expect-error - This is a test case - expect(getSubscriptionStatus(stripeCustomerIdForRetryBillingError, false, undefined, true, {}, undefined)).toEqual({ + expect(getSubscriptionStatus(stripeCustomerIdForRetryBillingError, false, undefined, true, {}, undefined, 0)).toEqual({ status: PAYMENT_STATUS.RETRY_BILLING_ERROR, isError: true, }); }); }); + describe('getAmountOwed', () => { + it('should return 0 when no value is provided and no module variable is set', () => { + expect(getAmountOwed(undefined)).toBe(0); + }); + + it('should return the provided value when a value is passed', () => { + expect(getAmountOwed(AMOUNT_OWED)).toBe(AMOUNT_OWED); + }); + + it('should return 0 when undefined is provided', () => { + expect(getAmountOwed(undefined)).toBe(0); + }); + }); + + describe('hasCardAuthenticatedError', () => { + it('should return true when status is authentication_required and amountOwed is 0', () => { + expect(hasCardAuthenticatedError(stripeCustomerId, 0)).toBeTruthy(); + }); + + it('should return false when status is authentication_required but amountOwed is not 0', () => { + expect(hasCardAuthenticatedError(stripeCustomerId, AMOUNT_OWED)).toBeFalsy(); + }); + + it('should return false when status is not authentication_required', () => { + const stripeCustomerIdWithDifferentStatus: StripeCustomerID = { + paymentMethodID: '1', + intentsID: '2', + currency: 'USD', + status: 'succeeded', + }; + expect(hasCardAuthenticatedError(stripeCustomerIdWithDifferentStatus, 0)).toBeFalsy(); + }); + + it('should return false when stripeCustomerId is undefined', () => { + expect(hasCardAuthenticatedError(undefined, 0)).toBeFalsy(); + }); + }); + + describe('hasSubscriptionRedDotError', () => { + afterEach(async () => { + await Onyx.clear(); + await Onyx.multiSet({ + [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: null, + [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: null, + }); + }); + + it('should return true when there is a subscription status with isError true', async () => { + await Onyx.multiSet({ + [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: GRACE_PERIOD_DATE, + [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: AMOUNT_OWED, + }); + + expect(hasSubscriptionRedDotError(stripeCustomerId, false, undefined, undefined, undefined, undefined, AMOUNT_OWED)).toBeTruthy(); + }); + + it('should return false when there is no subscription status error', () => { + const stripeCustomerIdWithNoError: StripeCustomerID = { + paymentMethodID: '1', + intentsID: '2', + currency: 'USD', + status: 'succeeded', + }; + expect(hasSubscriptionRedDotError(stripeCustomerIdWithNoError, false, undefined, undefined, undefined, undefined, 0)).toBeFalsy(); + }); + }); + + describe('hasSubscriptionGreenDotInfo', () => { + afterEach(async () => { + await Onyx.clear(); + await Onyx.multiSet({ + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL]: null, + [ONYXKEYS.FUND_LIST]: null, + }); + }); + + it('should return true when there is a subscription status with isError false', async () => { + await Onyx.multiSet({ + [ONYXKEYS.FUND_LIST]: {}, + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL]: true, + }); + + const stripeCustomerIdForRetryBillingSuccess: Partial> = { + paymentMethodID: '1', + intentsID: '2', + currency: 'USD', + }; + // @ts-expect-error - This is a test case + expect(hasSubscriptionGreenDotInfo(stripeCustomerIdForRetryBillingSuccess, true, undefined, undefined, {}, undefined, 0)).toBeTruthy(); + }); + + it('should return false when there is no subscription status or isError is true', () => { + expect(hasSubscriptionGreenDotInfo(stripeCustomerId, false, undefined, undefined, undefined, undefined, 0)).toBeFalsy(); + }); + }); + describe('shouldShowDiscountBanner', () => { const ownerAccountID = 234; const policyID = '100012'; From 944200b17651337af843dc384a6c71f69fd0667c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 11 Feb 2026 11:41:34 +0700 Subject: [PATCH 2/2] prettier --- src/pages/settings/InitialSettingsPage.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index de3d83645a663..65f7438290804 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -277,7 +277,16 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr screenName: SCREENS.SETTINGS.SUBSCRIPTION.ROOT, brickRoadIndicator: !!privateSubscription?.errors || - hasSubscriptionRedDotError(stripeCustomerId, retryBillingSuccessful, billingDisputePending, retryBillingFailed, fundList, billingStatus, amountOwed, ownerBillingGraceEndPeriod) + hasSubscriptionRedDotError( + stripeCustomerId, + retryBillingSuccessful, + billingDisputePending, + retryBillingFailed, + fundList, + billingStatus, + amountOwed, + ownerBillingGraceEndPeriod, + ) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, badgeText: freeTrialText,