1 import { addWeeks, fromUnixTime, isBefore } from 'date-fns';
3 import { isSplittedUser, onSessionMigrationChargebeeStatus } from '@proton/components/payments/core';
4 import type { ProductParam } from '@proton/shared/lib/apps/product';
5 import { getSupportedAddons, isIpAddon, isMemberAddon } from '@proton/shared/lib/helpers/addons';
8 FreeSubscription} from '../constants';
19 } from '../constants';
30 } from '../interfaces';
31 import { Audience, ChargebeeEnabled, External } from '../interfaces';
32 import { hasBit } from './bitset';
34 const { PLAN, ADDON } = PLAN_TYPES;
61 MEMBER_SCRIBE_MAILPLUS,
62 MEMBER_SCRIBE_MAIL_BUSINESS,
63 MEMBER_SCRIBE_DRIVEPLUS,
67 MEMBER_SCRIBE_VPN2024,
68 MEMBER_SCRIBE_VPN_PASS_BUNDLE,
69 MEMBER_SCRIBE_MAIL_PRO,
70 MEMBER_SCRIBE_BUNDLE_PRO,
71 MEMBER_SCRIBE_BUNDLE_PRO_2024,
72 MEMBER_SCRIBE_PASS_PRO,
73 MEMBER_SCRIBE_VPN_BIZ,
74 MEMBER_SCRIBE_PASS_BIZ,
75 MEMBER_SCRIBE_VPN_PRO,
80 type MaybeFreeSubscription = Subscription | FreeSubscription | undefined;
82 export const getPlan = (subscription: Subscription | FreeSubscription | undefined, service?: PLAN_SERVICES) => {
83 const result = (subscription?.Plans || []).find(
84 ({ Services, Type }) => Type === PLAN && (service === undefined ? true : hasBit(Services, service))
87 return result as SubscriptionPlan & { Name: PLANS };
92 export const getAddons = (subscription: Subscription | undefined) =>
93 (subscription?.Plans || []).filter(({ Type }) => Type === ADDON);
94 export const hasAddons = (subscription: Subscription | undefined) =>
95 (subscription?.Plans || []).some(({ Type }) => Type === ADDON);
97 export const getPlanName = (subscription: Subscription | undefined, service?: PLAN_SERVICES) => {
98 const plan = getPlan(subscription, service);
102 export const getPlanTitle = (subscription: Subscription | undefined) => {
103 const plan = getPlan(subscription);
107 export const hasSomePlan = (subscription: MaybeFreeSubscription, planName: PLANS) => {
108 if (isFreeSubscription(subscription)) {
112 return (subscription?.Plans || []).some(({ Name }) => Name === planName);
115 export const hasSomeAddonOrPlan = (
116 subscription: MaybeFreeSubscription,
117 addonName: ADDON_NAMES | PLANS | (ADDON_NAMES | PLANS)[]
119 if (isFreeSubscription(subscription)) {
123 if (Array.isArray(addonName)) {
124 return (subscription?.Plans || []).some(({ Name }) => addonName.includes(Name as ADDON_NAMES));
127 return (subscription?.Plans || []).some(({ Name }) => Name === addonName);
130 export const hasLifetime = (subscription: Subscription | undefined) => {
131 return subscription?.CouponCode === COUPON_CODES.LIFETIME;
134 export const hasMigrationDiscount = (subscription?: Subscription) => {
135 return subscription?.CouponCode?.startsWith('MIGRATION');
138 export const isManagedExternally = (
139 subscription: Subscription | FreeSubscription | Pick<Subscription, 'External'> | undefined | null
141 if (!subscription || isFreeSubscription(subscription)) {
145 return subscription.External === External.Android || subscription.External === External.iOS;
148 export const hasVisionary = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VISIONARY);
149 export const hasVPN = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VPN);
150 export const hasVPN2024 = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VPN2024);
151 export const hasVPNPassBundle = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VPN_PASS_BUNDLE);
152 export const hasMail = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, MAIL);
153 export const hasMailPro = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, MAIL_PRO);
154 export const hasMailBusiness = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, MAIL_BUSINESS);
155 export const hasDrive = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, DRIVE);
156 export const hasDrivePro = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, DRIVE_PRO);
157 export const hasPass = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, PASS);
158 export const hasWallet = (subscription: MaybeFreeSubscription) => hasSomeAddonOrPlan(subscription, WALLET);
159 export const hasEnterprise = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, ENTERPRISE);
160 export const hasBundle = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, BUNDLE);
161 export const hasBundlePro = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, BUNDLE_PRO);
162 export const hasBundlePro2024 = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, BUNDLE_PRO_2024);
163 export const hasFamily = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, FAMILY);
164 export const hasDuo = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, DUO);
165 export const hasVpnPro = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VPN_PRO);
166 export const hasVpnBusiness = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, VPN_BUSINESS);
167 export const hasPassPro = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, PASS_PRO);
168 export const hasPassBusiness = (subscription: MaybeFreeSubscription) => hasSomePlan(subscription, PASS_BUSINESS);
169 export const hasFree = (subscription: MaybeFreeSubscription) => (subscription?.Plans || []).length === 0;
171 export const hasAnyBundlePro = (subscription: MaybeFreeSubscription) =>
172 hasBundlePro(subscription) || hasBundlePro2024(subscription);
174 export const hasAIAssistant = (subscription: MaybeFreeSubscription) =>
175 hasSomeAddonOrPlan(subscription, [
176 MEMBER_SCRIBE_MAILPLUS,
177 MEMBER_SCRIBE_MAIL_BUSINESS,
178 MEMBER_SCRIBE_DRIVEPLUS,
179 MEMBER_SCRIBE_BUNDLE,
182 MEMBER_SCRIBE_VPN2024,
183 MEMBER_SCRIBE_VPN_PASS_BUNDLE,
184 MEMBER_SCRIBE_MAIL_PRO,
185 MEMBER_SCRIBE_BUNDLE_PRO,
186 MEMBER_SCRIBE_BUNDLE_PRO_2024,
187 MEMBER_SCRIBE_PASS_PRO,
188 MEMBER_SCRIBE_VPN_BIZ,
189 MEMBER_SCRIBE_PASS_BIZ,
190 MEMBER_SCRIBE_VPN_PRO,
191 MEMBER_SCRIBE_FAMILY,
195 export const PLANS_WITH_AI_INCLUDED = [VISIONARY];
197 export const hasPlanWithAIAssistantIncluded = (subscription: MaybeFreeSubscription) =>
198 hasSomeAddonOrPlan(subscription, PLANS_WITH_AI_INCLUDED);
200 export const hasAllProductsB2CPlan = (subscription: MaybeFreeSubscription) =>
201 hasDuo(subscription) || hasFamily(subscription) || hasBundle(subscription) || hasVisionary(subscription);
203 export const getUpgradedPlan = (subscription: Subscription | undefined, app: ProductParam) => {
204 if (hasFree(subscription)) {
206 case APPS.PROTONPASS:
208 case APPS.PROTONDRIVE:
210 case APPS.PROTONVPN_SETTINGS:
212 case APPS.PROTONWALLET:
215 case APPS.PROTONMAIL:
219 if (hasBundle(subscription) || hasBundlePro(subscription) || hasBundlePro2024(subscription)) {
220 return PLANS.BUNDLE_PRO_2024;
225 export const getIsB2BAudienceFromPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
230 const b2bPlans: (PLANS | ADDON_NAMES)[] = [
243 return b2bPlans.includes(planName);
246 export const canCheckItemPaidChecklist = (subscription: Subscription | undefined) => {
247 return subscription?.Plans?.some(({ Name }) => [MAIL, DRIVE, FAMILY, DUO, BUNDLE].includes(Name as any));
250 export const canCheckItemGetStarted = (subscription: Subscription | undefined) => {
251 return subscription?.Plans?.some(({ Name }) => [VPN, VPN2024, WALLET, PASS, VPN_PASS_BUNDLE].includes(Name as any));
254 export const getIsVpnB2BPlan = (planName: PLANS | ADDON_NAMES) => {
255 return [VPN_PRO, VPN_BUSINESS].includes(planName as any);
258 export const getIsVpnPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
259 return [VPN, VPN2024, VPN_PASS_BUNDLE, VPN_PRO, VPN_BUSINESS].includes(planName as any);
262 export const getIsConsumerVpnPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
263 return [VPN, VPN2024, VPN_PASS_BUNDLE].includes(planName as any);
266 export const getIsPassB2BPlan = (planName?: PLANS | ADDON_NAMES) => {
267 return [PASS_PRO, PASS_BUSINESS].includes(planName as any);
270 export const getIsPassPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
271 return [PASS, VPN_PASS_BUNDLE, PASS_PRO, PASS_BUSINESS].includes(planName as any);
274 export const getIsConsumerPassPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
275 return [PASS, VPN_PASS_BUNDLE].includes(planName as any);
278 export const getIsSentinelPlan = (planName: PLANS | ADDON_NAMES | undefined) => {
291 ].includes(planName as any);
294 export const getIsB2BAudienceFromSubscription = (subscription: Subscription | undefined) => {
295 return !!subscription?.Plans?.some(({ Name }) => getIsB2BAudienceFromPlan(Name));
298 export const getHasVpnB2BPlan = (subscription: MaybeFreeSubscription) => {
299 return hasVpnPro(subscription) || hasVpnBusiness(subscription);
302 export const getHasSomeVpnPlan = (subscription: MaybeFreeSubscription) => {
304 hasVPN(subscription) ||
305 hasVPN2024(subscription) ||
306 hasVPNPassBundle(subscription) ||
307 hasVpnPro(subscription) ||
308 hasVpnBusiness(subscription)
312 export const getHasConsumerVpnPlan = (subscription: MaybeFreeSubscription) => {
313 return hasVPN(subscription) || hasVPN2024(subscription) || hasVPNPassBundle(subscription);
316 export const getHasPassB2BPlan = (subscription: MaybeFreeSubscription) => {
317 return hasPassPro(subscription) || hasPassBusiness(subscription);
320 export const getHasVpnOrPassB2BPlan = (subscription: MaybeFreeSubscription) => {
321 return getHasVpnB2BPlan(subscription) || getHasPassB2BPlan(subscription);
324 export const getHasMailB2BPlan = (subscription: MaybeFreeSubscription) => {
325 return hasMailPro(subscription) || hasMailBusiness(subscription);
328 export const getPrimaryPlan = (subscription: Subscription | undefined) => {
333 return getPlan(subscription);
336 export const getBaseAmount = (
337 name: PLANS | ADDON_NAMES,
339 subscription: Subscription | undefined,
340 cycle = CYCLE.MONTHLY
342 const base = plansMap[name];
346 return (subscription?.Plans || [])
347 .filter(({ Name }) => Name === name)
349 const pricePerCycle = base.Pricing[cycle] || 0;
350 return acc + pricePerCycle;
354 export const getPlanIDs = (subscription: MaybeFreeSubscription | null): PlanIDs => {
355 return (subscription?.Plans || []).reduce<PlanIDs>((acc, { Name, Quantity }) => {
356 acc[Name] = (acc[Name] || 0) + Quantity;
361 export const isTrial = (subscription: Subscription | FreeSubscription | undefined, plan?: PLANS): boolean => {
362 if (isFreeSubscription(subscription)) {
367 subscription?.CouponCode === COUPON_CODES.REFERRAL ||
368 subscription?.CouponCode === COUPON_CODES.MEMBER_DOWNGRADE_TRIAL;
369 const isTrialV5 = !!subscription?.IsTrial;
370 const trial = isTrialV4 || isTrialV5;
376 return trial && getPlanName(subscription) === plan;
379 export const isTrialExpired = (subscription: Subscription | undefined) => {
380 const now = new Date();
381 return now > fromUnixTime(subscription?.PeriodEnd || 0);
384 export const willTrialExpire = (subscription: Subscription | undefined) => {
385 const now = new Date();
386 return isBefore(fromUnixTime(subscription?.PeriodEnd || 0), addWeeks(now, 1));
389 export const getHasMemberCapablePlan = (
390 organization: Organization | undefined,
391 subscription: Subscription | undefined
393 const supportedAddons = getSupportedAddons(getPlanIDs(subscription));
394 return (organization?.MaxMembers || 0) > 1 || (Object.keys(supportedAddons) as ADDON_NAMES[]).some(isMemberAddon);
397 export const hasBlackFridayDiscount = (subscription: Subscription | undefined) => {
399 COUPON_CODES.BLACK_FRIDAY_2022,
400 COUPON_CODES.MAIL_BLACK_FRIDAY_2022,
401 COUPON_CODES.VPN_BLACK_FRIDAY_2022,
402 ].includes(subscription?.CouponCode as COUPON_CODES);
405 export const getHas2023OfferCoupon = (coupon: string | undefined | null): boolean => {
406 return [COUPON_CODES.END_OF_YEAR_2023, COUPON_CODES.BLACK_FRIDAY_2023, COUPON_CODES.EOY_2023_1M_INTRO].includes(
411 export const hasVPNBlackFridayDiscount = (subscription: Subscription | undefined) => {
412 return subscription?.CouponCode === COUPON_CODES.VPN_BLACK_FRIDAY_2022;
415 export const hasMailBlackFridayDiscount = (subscription: Subscription | undefined) => {
416 return subscription?.CouponCode === COUPON_CODES.MAIL_BLACK_FRIDAY_2022;
419 export const allCycles = Object.freeze(
421 .filter((cycle): cycle is CYCLE => typeof cycle === 'number')
422 .sort((a, b) => a - b)
424 export const regularCycles = Object.freeze([CYCLE.MONTHLY, CYCLE.YEARLY, CYCLE.TWO_YEARS]);
425 export const customCycles = Object.freeze(allCycles.filter((cycle) => !regularCycles.includes(cycle)));
427 export const getValidCycle = (cycle: number): CYCLE | undefined => {
428 return allCycles.includes(cycle) ? cycle : undefined;
431 export const getValidAudience = (audience: string | undefined | null): Audience | undefined => {
432 return [Audience.B2B, Audience.B2C, Audience.FAMILY].find((realAudience) => audience === realAudience);
435 export const getIsCustomCycle = (subscription?: Subscription) => {
436 return customCycles.includes(subscription?.Cycle as any);
439 export function getNormalCycleFromCustomCycle(cycle: CYCLE): CYCLE;
440 export function getNormalCycleFromCustomCycle(cycle: undefined): undefined;
441 export function getNormalCycleFromCustomCycle(cycle: CYCLE | undefined): CYCLE | undefined;
442 export function getNormalCycleFromCustomCycle(cycle: CYCLE | undefined): CYCLE | undefined {
447 if (regularCycles.includes(cycle)) {
451 // find the closest lower regular cycle
452 for (let i = regularCycles.length - 1; i >= 0; i--) {
453 const regularCycle = regularCycles[i];
455 if (regularCycle < cycle) {
460 // well, that should be unreachable, but let it be just in case
461 return CYCLE.MONTHLY;
464 export function getLongerCycle(cycle: CYCLE): CYCLE;
465 export function getLongerCycle(cycle: CYCLE | undefined): CYCLE | undefined {
469 if (cycle === CYCLE.MONTHLY) {
472 if (cycle === CYCLE.YEARLY) {
473 return CYCLE.TWO_YEARS;
476 if (cycle === CYCLE.FIFTEEN || cycle === CYCLE.THIRTY) {
477 return CYCLE.TWO_YEARS;
483 export const hasYearly = (subscription?: Subscription) => {
484 return subscription?.Cycle === CYCLE.YEARLY;
487 export const hasMonthly = (subscription?: Subscription) => {
488 return subscription?.Cycle === CYCLE.MONTHLY;
491 export const hasTwoYears = (subscription?: Subscription) => {
492 return subscription?.Cycle === CYCLE.TWO_YEARS;
495 export const hasFifteen = (subscription?: Subscription) => {
496 return subscription?.Cycle === CYCLE.FIFTEEN;
499 export const hasThirty = (subscription?: Subscription) => {
500 return subscription?.Cycle === CYCLE.THIRTY;
503 export interface PricingForCycles {
504 [CYCLE.MONTHLY]: number;
505 [CYCLE.THREE]: number;
506 [CYCLE.YEARLY]: number;
507 [CYCLE.EIGHTEEN]: number;
508 [CYCLE.TWO_YEARS]: number;
509 [CYCLE.FIFTEEN]: number;
510 [CYCLE.THIRTY]: number;
513 export interface AggregatedPricing {
514 all: PricingForCycles;
515 defaultMonthlyPrice: number;
516 defaultMonthlyPriceWithoutAddons: number;
518 * That's pricing that counts only aggregate of cost for members. That's useful for rendering of
519 * "per user per month" pricing.
521 * - If you have a B2C plan with 1 user, then this price will be the same as `all`.
522 * - If you have Mail Plus plan with several users, then this price will be the same as `all`, because each
523 * additional member counts to the price of members.
524 * - If you have Bundle Pro with several users and with the default (minimum) number of custom domains, then
525 * this price will be the same as `all`.
527 * Here things become different:
528 * - If you have Bundle Pro with several users and with more than the default (minimum) number of custom domains,
529 * then this price will be `all - extra custom domains price`.
530 * - For VPN Business the behavior is more complex. It also has two addons: member and IPs/servers. By default it
531 * has 2 members and 1 IP. The price for members should exclude price for the 1 default IP.
533 members: PricingForCycles;
534 membersNumber: number;
535 plans: PricingForCycles;
538 function isMultiUserPersonalPlan(plan: Plan) {
539 // even though Duo, Family and Visionary plans can have up to 6 users in the org,
540 // for the price displaying purposes we count it as 1 member.
541 return plan.Name === PLANS.DUO || plan.Name === PLANS.FAMILY || plan.Name === PLANS.VISIONARY;
544 export function getPlanMembers(plan: Plan, quantity: number, view = true): number {
545 const hasMembers = plan.Type === PLAN_TYPES.PLAN || (plan.Type === PLAN_TYPES.ADDON && isMemberAddon(plan.Name));
547 let membersNumberInPlan = 0;
548 if (isMultiUserPersonalPlan(plan) && view) {
549 membersNumberInPlan = 1;
550 } else if (hasMembers) {
551 membersNumberInPlan = plan.MaxMembers || 1;
554 return membersNumberInPlan * quantity;
557 export function getMembersFromPlanIDs(planIDs: PlanIDs, plansMap: PlansMap, view = true): number {
558 return (Object.entries(planIDs) as [PLANS | ADDON_NAMES, number][]).reduce((acc, [name, quantity]) => {
559 const plan = plansMap[name];
564 return acc + getPlanMembers(plan, quantity, view);
568 export const INCLUDED_IP_PRICING = {
569 [CYCLE.MONTHLY]: 4999,
570 [CYCLE.YEARLY]: 3999 * CYCLE.YEARLY,
571 [CYCLE.TWO_YEARS]: 3599 * CYCLE.TWO_YEARS,
574 function getIpPrice(cycle: CYCLE): number {
575 if (cycle === CYCLE.MONTHLY) {
576 return INCLUDED_IP_PRICING[CYCLE.MONTHLY];
579 if (cycle === CYCLE.YEARLY) {
580 return INCLUDED_IP_PRICING[CYCLE.YEARLY];
583 if (cycle === CYCLE.TWO_YEARS) {
584 return INCLUDED_IP_PRICING[CYCLE.TWO_YEARS];
590 export function getIpPricePerMonth(cycle: CYCLE): number {
591 return getIpPrice(cycle) / cycle;
595 * The purpose of this overridden price is to show a coupon discount in the cycle selector. If that would be supported
596 * this would not be needed.
598 export const getPricePerCycle = (plan: Plan | undefined, cycle: CYCLE) => {
599 return plan?.Pricing?.[cycle];
602 export function getPricePerMember(plan: Plan, cycle: CYCLE): number {
603 const totalPrice = getPricePerCycle(plan, cycle) || 0;
605 if (plan.Name === PLANS.VPN_BUSINESS) {
606 // For VPN business, we exclude IP price from calculation. And we also divide by 2,
607 // because it has 2 members by default too.
608 const IP_PRICE = getIpPrice(cycle);
609 return (totalPrice - IP_PRICE) / (plan.MaxMembers || 1);
612 if (isMultiUserPersonalPlan(plan)) {
616 // Some plans have 0 MaxMembers. That's because they don't have access to mail.
617 // In reality, they still get 1 member.
618 return totalPrice / (plan.MaxMembers || 1);
621 export function getPricingPerMember(plan: Plan): Pricing {
622 return allCycles.reduce((acc, cycle) => {
623 acc[cycle] = getPricePerMember(plan, cycle);
625 // If the plan doesn't have custom cycles, we need to remove it from the resulting Pricing object
626 const isNonDefinedCycle = acc[cycle] === undefined || acc[cycle] === null || acc[cycle] === 0;
627 if (customCycles.includes(cycle) && isNonDefinedCycle) {
635 interface OfferResult {
641 export const getPlanOffer = (plan: Plan) => {
642 const result = [CYCLE.MONTHLY, CYCLE.YEARLY, CYCLE.TWO_YEARS].reduce<OfferResult>(
644 acc.pricing[cycle] = (plan.DefaultPricing?.[cycle] ?? 0) - (getPricePerCycle(plan, cycle) ?? 0);
654 [CYCLE.TWO_YEARS]: 0,
661 const sortedResults = (Object.entries(result.pricing) as unknown as [CYCLE, number][]).sort((a, b) => b[1] - a[1]);
662 result.cycles = sortedResults.map(([cycle]) => cycle);
663 if (sortedResults[0][1] > 0) {
670 * Currently there is no convenient way to get the number of IPs for a VPN subscription.
671 * There is no dedicated field for that in the API.
672 * That's a hack that counts the number of IP addons.
674 export const getVPNDedicatedIPs = (subscription: Subscription | undefined) => {
675 const planName = getPlanName(subscription, PLAN_SERVICES.VPN);
677 // If you have other VPN plans, they don't have dedicated IPs
682 // Some plans might have included IPs without any indication on the backend.
683 // For example, 1 IP is included in the Business plan
684 const includedIPs = IPS_INCLUDED_IN_PLAN[planName] || 0;
686 return (subscription as Subscription).Plans.reduce(
687 (acc, { Name: addonOrPlanName, Quantity }) => acc + (isIpAddon(addonOrPlanName) ? Quantity : 0),
692 export const getHasCoupon = (subscription: Subscription | undefined, coupon: string) => {
693 return [subscription?.CouponCode, subscription?.UpcomingSubscription?.CouponCode].includes(coupon);
697 * Checks if subscription can be cancelled by a user. Cancellation means that the user will be downgraded at the end
698 * of the current billing cycle. In contrast, "Downgrade subscription" button means that the user will be downgraded
699 * immediately. Note that B2B subscriptions also have "Cancel subscription" button, but it behaves differently, so
700 * we don't consider B2B subscriptions cancellable for the purpose of this function.
702 export const hasCancellablePlan = (subscription: Subscription | undefined, user: UserModel) => {
703 // These plans are can be cancelled inhouse too
704 const cancellablePlan = getHasConsumerVpnPlan(subscription) || hasPass(subscription);
706 // In Chargebee, all plans are cancellable
707 const chargebeeForced = onSessionMigrationChargebeeStatus(user, subscription) === ChargebeeEnabled.CHARGEBEE_FORCED;
709 // Splitted users should go to PUT v4 renew because they still have an active subscription in inhouse system
710 // And we force them to do the renew cancellation instead of subscription deletion because this case is much
712 const splittedUser = isSplittedUser(user.ChargebeeUser, user.ChargebeeUserExists, subscription?.BillingPlatform);
714 return cancellablePlan || chargebeeForced || splittedUser;
717 export function hasMaximumCycle(subscription?: SubscriptionModel | FreeSubscription): boolean {
719 subscription?.Cycle === CYCLE.TWO_YEARS ||
720 subscription?.Cycle === CYCLE.THIRTY ||
721 subscription?.UpcomingSubscription?.Cycle === CYCLE.TWO_YEARS ||
722 subscription?.UpcomingSubscription?.Cycle === CYCLE.THIRTY