Skip to content
Merged
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
12 changes: 3 additions & 9 deletions src/libs/SubscriptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
};

let currentUserAccountID = -1;
Onyx.connect({

Check warning on line 52 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 @@ -57,25 +57,25 @@
});

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

Check warning on line 60 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) => (amountOwed = value),
});

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

Check warning on line 66 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_BILLING_DISPUTE_PENDING,
callback: (value) => (billingDisputePending = value),
});

let billingStatus: OnyxEntry<BillingStatus>;
Onyx.connect({

Check warning on line 72 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_BILLING_STATUS,
callback: (value) => (billingStatus = value),
});

let firstPolicyDate: OnyxEntry<string>;
Onyx.connect({

Check warning on line 78 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_FIRST_POLICY_CREATED_DATE,
callback: (value) => {
firstPolicyDate = value;
Expand All @@ -83,7 +83,7 @@
});

let hasManualTeam2025Pricing: OnyxEntry<string>;
Onyx.connect({

Check warning on line 86 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_MANUAL_TEAM_2025_PRICING,
callback: (value) => {
hasManualTeam2025Pricing = value;
Expand All @@ -91,13 +91,13 @@
});

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

Check warning on line 94 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) => (ownerBillingGraceEndPeriod = value),
});

let fundList: OnyxEntry<FundList>;
Onyx.connect({

Check warning on line 100 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.FUND_LIST,
callback: (value) => {
if (!value) {
Expand All @@ -109,7 +109,7 @@
});

let retryBillingSuccessful: OnyxEntry<boolean>;
Onyx.connect({

Check warning on line 112 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.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL,
initWithStoredValues: false,
callback: (value) => {
Expand All @@ -122,7 +122,7 @@
});

let retryBillingFailed: OnyxEntry<boolean>;
Onyx.connect({

Check warning on line 125 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.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED,
callback: (value) => {
if (value === undefined) {
Expand Down Expand Up @@ -175,12 +175,6 @@
},
});

let introSelected: OnyxEntry<IntroSelected>;
Onyx.connect({
key: ONYXKEYS.NVP_INTRO_SELECTED,
callback: (value) => (introSelected = value),
});

/**
* @returns The date when the grace period ends.
*/
Expand Down Expand Up @@ -244,7 +238,7 @@
return billingStatus?.declineReason === 'insufficient_funds' && getAmountOwed() !== 0;
}

function shouldShowPreTrialBillingBanner(): boolean {
function shouldShowPreTrialBillingBanner(introSelected: OnyxEntry<IntroSelected>): boolean {
// We don't want to show the Pre Trial banner if the user was a Test Drive Receiver that created their workspace
// with the promo code.
const wasUserTestDriveReceiver = introSelected?.previousChoices?.some((choice) => choice === CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER);
Expand Down Expand Up @@ -488,13 +482,13 @@
* @param policies - The policies collection.
* @returns The free trial badge text .
*/
function getFreeTrialText(policies: OnyxCollection<Policy> | null): string | undefined {
function getFreeTrialText(policies: OnyxCollection<Policy> | null, introSelected: OnyxEntry<IntroSelected>): string | undefined {
const ownedPaidPolicies = getOwnedPaidPolicies(policies, currentUserAccountID);
if (isEmptyObject(ownedPaidPolicies)) {
return undefined;
}

if (shouldShowPreTrialBillingBanner()) {
if (shouldShowPreTrialBillingBanner(introSelected)) {
return translateLocal('subscription.billingBanner.preTrial.title');
}
if (isUserOnFreeTrial()) {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/settings/InitialSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [stripeCustomerId] = useOnyx(ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID, {canBeMissing: true});
const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false});
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});

const {shouldUseNarrowLayout} = useResponsiveLayout();
const network = useNetwork();
Expand All @@ -111,7 +112,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr

const shouldLogout = useRef(false);

const freeTrialText = getFreeTrialText(policies);
const freeTrialText = getFreeTrialText(policies, introSelected);

const shouldDisplayLHB = !shouldUseNarrowLayout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function CardSection() {
const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
const [purchaseList] = useOnyx(ONYXKEYS.PURCHASE_LIST, {canBeMissing: true});
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const hasTeam2025Pricing = useHasTeam2025Pricing();
const subscriptionPlan = useSubscriptionPlan();
const [subscriptionRetryBillingStatusPending] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_PENDING, {canBeMissing: true});
Expand Down Expand Up @@ -129,7 +130,7 @@ function CardSection() {
let BillingBanner: React.ReactNode | undefined;
if (shouldShowDiscountBanner(hasTeam2025Pricing, subscriptionPlan)) {
BillingBanner = <EarlyDiscountBanner isSubscriptionPage />;
} else if (shouldShowPreTrialBillingBanner()) {
} else if (shouldShowPreTrialBillingBanner(introSelected)) {
BillingBanner = <PreTrialBillingBanner />;
} else if (isUserOnFreeTrial()) {
BillingBanner = <TrialStartedBillingBanner />;
Expand Down
5 changes: 3 additions & 2 deletions src/pages/settings/Subscription/FreeTrial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function FreeTrial({badgeStyles, pressable = false, addSpacing = false, success
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, {canBeMissing: true});
const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, {canBeMissing: true});
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const privateSubscription = usePrivateSubscription();

const [freeTrialText, setFreeTrialText] = useState<string | undefined>(undefined);
Expand All @@ -35,8 +36,8 @@ function FreeTrial({badgeStyles, pressable = false, addSpacing = false, success
if (!privateSubscription && !isOffline) {
return;
}
setFreeTrialText(getFreeTrialText(policies));
}, [isOffline, privateSubscription, policies, firstDayFreeTrial, lastDayFreeTrial]);
setFreeTrialText(getFreeTrialText(policies, introSelected));
}, [isOffline, privateSubscription, policies, firstDayFreeTrial, lastDayFreeTrial, introSelected]);

if (!freeTrialText) {
return null;
Expand Down
37 changes: 36 additions & 1 deletion tests/unit/SubscriptionUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {act} from '@testing-library/react-native';
import {addDays, addMinutes, format as formatDate, getUnixTime, subDays} from 'date-fns';
import Onyx from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
Expand All @@ -11,12 +12,14 @@ import {
PAYMENT_STATUS,
shouldRestrictUserBillableActions,
shouldShowDiscountBanner,
shouldShowPreTrialBillingBanner,
} from '@libs/SubscriptionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {BillingGraceEndPeriod, BillingStatus, FundList, StripeCustomerID} from '@src/types/onyx';
import type {BillingGraceEndPeriod, BillingStatus, FundList, IntroSelected, StripeCustomerID} from '@src/types/onyx';
import createRandomPolicy from '../utils/collections/policies';
import {STRIPE_CUSTOMER_ID} from '../utils/TestHelper';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';

const billingGraceEndPeriod: BillingGraceEndPeriod = {
value: 0,
Expand Down Expand Up @@ -624,4 +627,36 @@ describe('SubscriptionUtils', () => {
expect(getEarlyDiscountInfo()).toBeNull();
});
});
describe('shouldShowPreTrialBillingBanner', () => {
it('should return true if the user is NOT on a free trial and trial has not ended', async () => {
// Free trial starts in the future → user is not currently on trial
await act(async () => {
await Onyx.multiSet({
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: formatDate(addDays(new Date(), 5), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING),
[ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: formatDate(addDays(new Date(), 10), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING),
});
});

await waitForBatchedUpdatesWithAct();

const introSelected: OnyxEntry<IntroSelected> = {
choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM,
};

expect(shouldShowPreTrialBillingBanner(introSelected)).toBeTruthy();
});

it('should return false if the free trial has ended', async () => {
await Onyx.multiSet({
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: formatDate(subDays(new Date(), 20), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING),
[ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: formatDate(subDays(new Date(), 5), CONST.DATE.FNS_DATE_TIME_FORMAT_STRING),
});

const introSelected: OnyxEntry<IntroSelected> = {
choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM,
};

expect(shouldShowPreTrialBillingBanner(introSelected)).toBeFalsy();
});
});
});
Loading