Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,6 @@ const ONYXKEYS = {
/** If the approver dismissed the reject or hold explanation */
NVP_DISMISSED_REJECT_USE_EXPLANATION: 'nvp_dismissedRejectUseExplanation',

/** Whether the user is grandfathered into the free plan */
NVP_PRIVATE_GRANDFATHERED_FREE: 'nvp_private_grandfatheredFree',

/** Details on whether an account is locked or not */
NVP_PRIVATE_LOCK_ACCOUNT_DETAILS: 'nvp_private_lockAccountDetails',

Expand Down Expand Up @@ -1406,7 +1403,6 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_RECONNECT_APP_IF_FULL_RECONNECT_BEFORE]: string;
[ONYXKEYS.NVP_PRIVATE_FIRST_POLICY_CREATED_DATE]: string;
[ONYXKEYS.NVP_PRIVATE_MANUAL_TEAM_2025_PRICING]: string;
[ONYXKEYS.NVP_PRIVATE_GRANDFATHERED_FREE]: boolean;
[ONYXKEYS.NVP_PRIVATE_LOCK_ACCOUNT_DETAILS]: OnyxTypes.LockAccountDetails;
[ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[];
[ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string;
Expand Down
24 changes: 0 additions & 24 deletions src/libs/SubscriptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
};

let currentUserAccountID = -1;
Onyx.connect({

Check warning on line 56 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID;
Expand All @@ -61,26 +61,26 @@
});

let privateAmountOwed: OnyxEntry<number>;
Onyx.connect({

Check warning on line 64 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED,
callback: (value) => (privateAmountOwed = value),
});

let ownerBillingGraceEndPeriodDeprecated: OnyxEntry<number>;
Onyx.connect({

Check warning on line 70 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END,
callback: (value) => (ownerBillingGraceEndPeriodDeprecated = value),
});

let deprecatedUserBillingGraceEndPeriods: OnyxCollection<BillingGraceEndPeriod>;
Onyx.connect({

Check warning on line 76 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END,
callback: (value) => (deprecatedUserBillingGraceEndPeriods = value),
waitForCollectionCallback: true,
});

let deprecatedAllPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 83 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (value) => (deprecatedAllPolicies = value),
waitForCollectionCallback: true,
Expand Down Expand Up @@ -594,29 +594,6 @@
};
}

function shouldShowTrialEndedUI(
lastDayFreeTrial: string | undefined,
userBillingFundID: number | undefined,
policies: OnyxCollection<Policy>,
isGrandfatheredFree: boolean | undefined,
isFromInternalDomain: boolean | undefined,
privateSubscriptionType: SubscriptionType | undefined,
): boolean {
if (!getOwnedPaidPolicies(policies, currentUserAccountID)?.length) {
return false;
}
if (isGrandfatheredFree || isFromInternalDomain) {
return false;
}
if (isSubscriptionTypeOfInvoicing(privateSubscriptionType)) {
return false;
}
if (doesUserHavePaymentCardAdded(userBillingFundID)) {
return false;
}
return hasUserFreeTrialEnded(lastDayFreeTrial);
}

function isSubscriptionTypeOfInvoicing(privateSubscriptionType: SubscriptionType | undefined) {
return privateSubscriptionType === CONST.SUBSCRIPTION.TYPE.INVOICING;
}
Expand All @@ -642,7 +619,6 @@
shouldCalculateBillNewDot,
getSubscriptionPlanInfo,
getSubscriptionPrice,
shouldShowTrialEndedUI,
isSubscriptionTypeOfInvoicing,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {isFromInternalDomainSelector} from '@selectors/Account';
import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing';
import useOnyx from '@hooks/useOnyx';
import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
import {getEarlyDiscountInfo, shouldShowDiscountBanner, shouldShowTrialEndedUI} from '@libs/SubscriptionUtils';
import {doesUserHavePaymentCardAdded, getEarlyDiscountInfo, hasUserFreeTrialEnded, shouldShowDiscountBanner} from '@libs/SubscriptionUtils';
import ONYXKEYS from '@src/ONYXKEYS';

function useTimeSensitiveOffers() {
Expand All @@ -12,9 +11,6 @@ function useTimeSensitiveOffers() {
const hasTeam2025Pricing = useHasTeam2025Pricing();
const subscriptionPlan = useSubscriptionPlan();
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [isGrandfatheredFree] = useOnyx(ONYXKEYS.NVP_PRIVATE_GRANDFATHERED_FREE);
const [isFromInternalDomain] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isFromInternalDomainSelector});
const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);

// Use the same logic as the subscription page to determine if discount banner should be shown
const shouldShowDiscount = shouldShowDiscountBanner(hasTeam2025Pricing, subscriptionPlan, firstDayFreeTrial, lastDayFreeTrial, userBillingFundID, allPolicies);
Expand All @@ -25,8 +21,7 @@ function useTimeSensitiveOffers() {
const shouldShow25off = shouldShowDiscount && discountInfo?.discountType === 25;

// Show add payment card for users whose trial ended and haven't added a payment card
const shouldShowAddPaymentCard =
hasTeam2025Pricing && shouldShowTrialEndedUI(lastDayFreeTrial, userBillingFundID, allPolicies, isGrandfatheredFree, isFromInternalDomain, privateSubscription?.type);
const shouldShowAddPaymentCard = hasUserFreeTrialEnded(lastDayFreeTrial) && !doesUserHavePaymentCardAdded(userBillingFundID);

return {
shouldShow50off,
Expand Down
5 changes: 2 additions & 3 deletions src/pages/settings/Subscription/CardSection/CardSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import DateUtils from '@libs/DateUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getPaymentMethodDescription} from '@libs/PaymentUtils';
import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils';
import {hasCardAuthenticatedError, isUserOnFreeTrial, shouldShowDiscountBanner, shouldShowPreTrialBillingBanner, shouldShowTrialEndedUI} from '@libs/SubscriptionUtils';
import {hasCardAuthenticatedError, hasUserFreeTrialEnded, isUserOnFreeTrial, shouldShowDiscountBanner, shouldShowPreTrialBillingBanner} from '@libs/SubscriptionUtils';
import {verifySetupIntent} from '@userActions/PaymentMethods';
import {clearOutstandingBalance} from '@userActions/Subscription';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -75,7 +75,6 @@ function CardSection() {
const [amountOwed = 0] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [isGrandfatheredFree] = useOnyx(ONYXKEYS.NVP_PRIVATE_GRANDFATHERED_FREE);
const requestRefund = useCallback(() => {
requestRefundByUser();
Navigation.goBackToHome();
Expand Down Expand Up @@ -194,7 +193,7 @@ function CardSection() {
BillingBanner = <PreTrialBillingBanner />;
} else if (isUserOnFreeTrial(firstDayFreeTrial, lastDayFreeTrial)) {
BillingBanner = <TrialStartedBillingBanner />;
} else if (shouldShowTrialEndedUI(lastDayFreeTrial, userBillingFundID, allPolicies, isGrandfatheredFree, account?.isFromInternalDomain, privateSubscription?.type)) {
} else if (hasUserFreeTrialEnded(lastDayFreeTrial)) {
BillingBanner = <TrialEndedBillingBanner />;
}
if (billingStatus) {
Expand Down
3 changes: 0 additions & 3 deletions src/selectors/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ const requiresTwoFactorAuthSelector = (data: OnyxEntry<Account>) => data?.requir

const accountGuideDetailsSelector = (account: OnyxEntry<Account>) => account?.guideDetails;

const isFromInternalDomainSelector = (account: OnyxEntry<Account>) => account?.isFromInternalDomain;

export {
isActingAsDelegateSelector,
isUserValidatedSelector,
Expand All @@ -28,5 +26,4 @@ export {
isAccountLoadingSelector,
requiresTwoFactorAuthSelector,
accountGuideDetailsSelector,
isFromInternalDomainSelector,
};
3 changes: 0 additions & 3 deletions src/types/onyx/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,6 @@ type Account = {
/** Whether or not the user is on a public domain email account or not */
isFromPublicDomain?: boolean;

/** Whether the user's email domain is an internal Expensify domain (e.g. expensify.com) */
isFromInternalDomain?: boolean;

/** Whether or not the user uses expensify card */
isUsingExpensifyCard?: boolean;

Expand Down
54 changes: 0 additions & 54 deletions tests/unit/SubscriptionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
shouldRestrictUserBillableActions,
shouldShowDiscountBanner,
shouldShowPreTrialBillingBanner,
shouldShowTrialEndedUI,
} from '@libs/SubscriptionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -1167,57 +1166,4 @@ describe('SubscriptionUtils', () => {
expect(shouldCalculateBillNewDot(true, {})).toBeFalsy();
});
});

describe('shouldShowTrialEndedUI', () => {
const ownerAccountID = 345;
const policyID = '200012';
const lastDayFreeTrialEnded = formatDate(subDays(new Date(), 2), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING);
const lastDayFreeTrialActive = formatDate(addDays(new Date(), 5), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING);

let policies: Record<string, ReturnType<typeof createRandomPolicy>>;

beforeEach(async () => {
await Onyx.clear();
policies = {
[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]: {
...createRandomPolicy(Number(policyID)),
ownerAccountID,
type: CONST.POLICY.TYPE.CORPORATE,
},
};
await Onyx.set(ONYXKEYS.SESSION, {accountID: ownerAccountID});
});

it('should return true for a regular user whose trial ended, no card, with owned workspace', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, undefined, policies, undefined, undefined, undefined)).toBeTruthy();
});

it('should return false if the user has no owned paid policies', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, undefined, {}, undefined, undefined, undefined)).toBeFalsy();
});

it('should return false if the user is grandfathered free', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, undefined, policies, true, undefined, undefined)).toBeFalsy();
});

it('should return false if the user is from an internal domain', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, undefined, policies, undefined, true, undefined)).toBeFalsy();
});

it('should return false if the user is on invoiced billing', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, undefined, policies, undefined, undefined, CONST.SUBSCRIPTION.TYPE.INVOICING)).toBeFalsy();
});

it('should return false if the user has a payment card added', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialEnded, 8010, policies, undefined, undefined, undefined)).toBeFalsy();
});

it('should return false if the trial has not ended yet', () => {
expect(shouldShowTrialEndedUI(lastDayFreeTrialActive, undefined, policies, undefined, undefined, undefined)).toBeFalsy();
});

it('should return false if lastDayFreeTrial is undefined', () => {
expect(shouldShowTrialEndedUI(undefined, undefined, policies, undefined, undefined, undefined)).toBeFalsy();
});
});
});
47 changes: 30 additions & 17 deletions tests/unit/hooks/useTimeSensitiveOffers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Onyx from 'react-native-onyx';
// Import mocks after they're defined
import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing';
import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
import {getEarlyDiscountInfo, shouldShowDiscountBanner, shouldShowTrialEndedUI} from '@libs/SubscriptionUtils';
import {doesUserHavePaymentCardAdded, getEarlyDiscountInfo, hasUserFreeTrialEnded, shouldShowDiscountBanner} from '@libs/SubscriptionUtils';
import useTimeSensitiveOffers from '@pages/home/TimeSensitiveSection/hooks/useTimeSensitiveOffers';
import ONYXKEYS from '@src/ONYXKEYS';
import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
Expand All @@ -23,14 +23,16 @@ jest.mock('@hooks/useSubscriptionPlan', () => ({
jest.mock('@libs/SubscriptionUtils', () => ({
shouldShowDiscountBanner: jest.fn(() => false),
getEarlyDiscountInfo: jest.fn(() => null),
shouldShowTrialEndedUI: jest.fn(() => false),
hasUserFreeTrialEnded: jest.fn(() => false),
doesUserHavePaymentCardAdded: jest.fn(() => true),
}));

const mockedUseHasTeam2025Pricing = useHasTeam2025Pricing as jest.Mock;
const mockedUseSubscriptionPlan = useSubscriptionPlan as jest.Mock;
const mockedShouldShowDiscountBanner = shouldShowDiscountBanner as jest.Mock;
const mockedGetEarlyDiscountInfo = getEarlyDiscountInfo as jest.Mock;
const mockedShouldShowTrialEndedUI = shouldShowTrialEndedUI as jest.Mock;
const mockedHasUserFreeTrialEnded = hasUserFreeTrialEnded as jest.Mock;
const mockedDoesUserHavePaymentCardAdded = doesUserHavePaymentCardAdded as jest.Mock;

describe('useTimeSensitiveOffers', () => {
beforeAll(() => {
Expand Down Expand Up @@ -214,18 +216,18 @@ describe('useTimeSensitiveOffers', () => {
});

describe('when add payment card should not be shown', () => {
it('should return shouldShowAddPaymentCard as false when shouldShowTrialEndedUI returns false', () => {
mockedUseHasTeam2025Pricing.mockReturnValue(true);
mockedShouldShowTrialEndedUI.mockReturnValue(false);
it('should return shouldShowAddPaymentCard as false when trial has not ended', () => {
mockedHasUserFreeTrialEnded.mockReturnValue(false);
mockedDoesUserHavePaymentCardAdded.mockReturnValue(false);

const {result} = renderHook(() => useTimeSensitiveOffers());

expect(result.current.shouldShowAddPaymentCard).toBe(false);
});

it('should return shouldShowAddPaymentCard as false when hasTeam2025Pricing is false', () => {
mockedUseHasTeam2025Pricing.mockReturnValue(false);
mockedShouldShowTrialEndedUI.mockReturnValue(true);
it('should return shouldShowAddPaymentCard as false when user already has payment card', () => {
mockedHasUserFreeTrialEnded.mockReturnValue(true);
mockedDoesUserHavePaymentCardAdded.mockReturnValue(true);

const {result} = renderHook(() => useTimeSensitiveOffers());

Expand All @@ -234,9 +236,9 @@ describe('useTimeSensitiveOffers', () => {
});

describe('when add payment card should be shown', () => {
it('should return shouldShowAddPaymentCard as true when hasTeam2025Pricing and shouldShowTrialEndedUI both return true', () => {
mockedUseHasTeam2025Pricing.mockReturnValue(true);
mockedShouldShowTrialEndedUI.mockReturnValue(true);
it('should return shouldShowAddPaymentCard as true when trial ended and user has no payment card', () => {
mockedHasUserFreeTrialEnded.mockReturnValue(true);
mockedDoesUserHavePaymentCardAdded.mockReturnValue(false);

const {result} = renderHook(() => useTimeSensitiveOffers());

Expand All @@ -245,19 +247,30 @@ describe('useTimeSensitiveOffers', () => {
});

describe('add payment card hook dependencies', () => {
it('should call shouldShowTrialEndedUI with Onyx data', async () => {
it('should call hasUserFreeTrialEnded with lastDayFreeTrial from Onyx', async () => {
const lastDayFreeTrial = '2025-01-15';
const userBillingFundID = 12345;
await Onyx.merge(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, lastDayFreeTrial);
await waitForBatchedUpdates();

mockedHasUserFreeTrialEnded.mockReturnValue(false);
mockedDoesUserHavePaymentCardAdded.mockReturnValue(true);

renderHook(() => useTimeSensitiveOffers());

expect(mockedHasUserFreeTrialEnded).toHaveBeenCalledWith(lastDayFreeTrial);
});

it('should call doesUserHavePaymentCardAdded with userBillingFundID from Onyx', async () => {
const userBillingFundID = 12345;
await Onyx.merge(ONYXKEYS.NVP_BILLING_FUND_ID, userBillingFundID);
await waitForBatchedUpdates();

mockedUseHasTeam2025Pricing.mockReturnValue(true);
mockedShouldShowTrialEndedUI.mockReturnValue(false);
mockedHasUserFreeTrialEnded.mockReturnValue(true);
mockedDoesUserHavePaymentCardAdded.mockReturnValue(true);

renderHook(() => useTimeSensitiveOffers());

expect(mockedShouldShowTrialEndedUI).toHaveBeenCalledWith(lastDayFreeTrial, userBillingFundID, {}, undefined, undefined, undefined);
expect(mockedDoesUserHavePaymentCardAdded).toHaveBeenCalledWith(userBillingFundID);
});
});
});
Loading