@automattic/calypso-products
Version:
This module contains information about the various products sold within calypso, such as product slugs, plan intervals, etc.
982 lines (840 loc) • 28.1 kB
text/typescript
import {
FEATURE_JETPACK_SEARCH,
FEATURE_JETPACK_SEARCH_MONTHLY,
GROUP_JETPACK,
GROUP_P2,
GROUP_WPCOM,
JETPACK_RESET_PLANS,
PLAN_HOSTING_TRIAL_MONTHLY,
PLAN_MIGRATION_TRIAL_MONTHLY,
PLAN_WOOEXPRESS_MEDIUM,
PLAN_WOOEXPRESS_MEDIUM_MONTHLY,
PLAN_WOOEXPRESS_PLUS,
PLAN_WOOEXPRESS_SMALL,
PLAN_WOOEXPRESS_SMALL_MONTHLY,
TERM_ANNUALLY,
TERM_BIENNIALLY,
TERM_CENTENNIALLY,
TERM_DECENNIALLY,
TERM_MONTHLY,
TERM_NOVENNIALLY,
TERM_OCTENNIALLY,
TERM_QUADRENNIALLY,
TERM_QUINQUENNIALLY,
TERM_SEPTENNIALLY,
TERM_SEXENNIALLY,
TERM_TRIENNIALLY,
TYPE_100_YEAR,
TYPE_ALL,
TYPE_BLOGGER,
TYPE_BUSINESS,
TYPE_ECOMMERCE,
TYPE_ENTERPRISE_GRID_WPCOM,
TYPE_FLEXIBLE,
TYPE_FREE,
TYPE_JETPACK_STARTER,
TYPE_P2_PLUS,
TYPE_PERSONAL,
TYPE_PREMIUM,
TYPE_PRO,
TYPE_SECURITY_DAILY,
TYPE_SECURITY_REALTIME,
TYPE_SECURITY_T1,
TYPE_SECURITY_T2,
TYPE_STARTER,
TYPE_JETPACK_GROWTH,
WOO_EXPRESS_PLANS,
} from './constants';
import {
resolveFeatureGroupsForComparisonGrid,
resolveFeatureGroupsForFeaturesGrid,
resolveWooExpressFeatureGroupsForComparisonGrid,
} from './feature-group-plan-map';
import { FEATURES_LIST } from './features-list';
import { PLANS_LIST } from './plans-list';
import {
getProductFromSlug,
isBusiness,
isEcommerce,
isEnterprise,
isJetpackBusiness,
isPro,
isVipPlan,
} from '.';
import type {
FeatureGroupMap,
FeatureList,
JetpackPlan,
Plan,
PlanMatchesQuery,
PlanSlug,
Product,
WithCamelCaseSlug,
WithSnakeCaseSlug,
WPComPlan,
} from './types';
import type { TranslateResult } from 'i18n-calypso';
export function getPlans(): Record< string, Plan > {
return PLANS_LIST;
}
export function getSimplifiedPlanFeaturesGroupedForFeaturesGrid() {
return resolveFeatureGroupsForFeaturesGrid( { showSimplifiedFeatures: true } );
}
export function getPlanFeaturesGroupedForFeaturesGrid(): Partial< FeatureGroupMap > {
return resolveFeatureGroupsForFeaturesGrid();
}
export function getPlanFeaturesGroupedForComparisonGrid(): Partial< FeatureGroupMap > {
return resolveFeatureGroupsForComparisonGrid();
}
export function getWooExpressFeaturesGroupedForFeaturesGrid(): Partial< FeatureGroupMap > {
// Same as getPlanFeaturesGroupedForFeaturesGrid() for now
return getPlanFeaturesGroupedForFeaturesGrid();
}
export function getWooExpressFeaturesGroupedForComparisonGrid(): Partial< FeatureGroupMap > {
return resolveWooExpressFeatureGroupsForComparisonGrid();
}
export function getPlansSlugs(): string[] {
return Object.keys( getPlans() );
}
export function getPlan( planKey: string | Plan ): Plan | JetpackPlan | WPComPlan | undefined {
if ( typeof planKey !== 'string' ) {
if ( Object.values( PLANS_LIST ).includes( planKey ) ) {
return planKey;
}
return undefined;
}
return PLANS_LIST[ planKey ];
}
export function getPlanByPathSlug( pathSlug: string, group?: string ): Plan | undefined {
let plans: Plan[] = Object.values( PLANS_LIST );
plans = plans.filter( ( p ) => ( group ? p.group === group : true ) );
return plans.find( ( p ) => typeof p.getPathSlug === 'function' && p.getPathSlug() === pathSlug );
}
export function getPlanPath( plan: string ): string | undefined {
const retrievedPlan = getPlan( plan );
const slug = retrievedPlan?.getPathSlug || ( () => undefined );
return slug();
}
export function getPlanClass( planKey: string ): string {
if ( isFreePlan( planKey ) ) {
return 'is-free-plan';
}
if ( isFlexiblePlan( planKey ) ) {
return 'is-flexible-plan';
}
if ( isBloggerPlan( planKey ) ) {
return 'is-blogger-plan';
}
if ( isPersonalPlan( planKey ) ) {
return 'is-personal-plan';
}
if ( isPremiumPlan( planKey ) ) {
return 'is-premium-plan';
}
if ( isBusinessPlan( planKey ) ) {
return 'is-business-plan';
}
if ( isWooExpressPlusPlan( planKey ) ) {
return 'is-wooexpress-plus-plan';
}
if ( isWooExpressMediumPlan( planKey ) ) {
return 'is-wooexpress-medium-plan';
}
if ( isWooExpressSmallPlan( planKey ) ) {
return 'is-wooexpress-small-plan';
}
if ( isEcommercePlan( planKey ) ) {
return 'is-ecommerce-plan';
}
if ( isWpcomEnterpriseGridPlan( planKey ) ) {
return 'is-wpcom-enterprise-grid-plan';
}
if ( isProPlan( planKey ) ) {
return 'is-pro-plan';
}
if ( isSecurityDailyPlan( planKey ) ) {
return 'is-daily-security-plan';
}
if ( isSecurityRealTimePlan( planKey ) ) {
return 'is-realtime-security-plan';
}
if ( isSecurityT1Plan( planKey ) ) {
return 'is-security-t1';
}
if ( isSecurityT2Plan( planKey ) ) {
return 'is-security-t2';
}
if ( isCompletePlan( planKey ) ) {
return 'is-complete-plan';
}
if ( isJetpackGrowthPlan( planKey ) ) {
return 'is-jetpack-growth-plan';
}
if ( isFreeHostingTrial( planKey ) ) {
return 'is-free-hosting-trial';
}
if ( isP2PlusPlan( planKey ) ) {
return 'is-p2-plus-plan';
}
return '';
}
/**
* Determines if a plan has a specific feature.
*
* Collects features for a plan by calling all possible feature methods for the plan.
*/
export function planHasFeature( plan: string | Plan, feature: string ): boolean {
const allFeatures = getAllFeaturesForPlan( plan );
return allFeatures.includes( feature );
}
/**
* Determine if a plan has at least one of several features.
*/
export function planHasAtLeastOneFeature( plan: string | Plan, features: string[] ): boolean {
const allFeatures = getAllFeaturesForPlan( plan );
return features.some( ( feature ) => allFeatures.includes( feature ) );
}
/**
* Get all features for a plan
*
* Collects features for a plan by calling all possible feature methods for the plan.
*
* Returns an array of all the plan features (may have duplicates)
*/
export function getAllFeaturesForPlan( plan: Plan | string ): TranslateResult[] {
const planObj = getPlan( plan );
if ( ! planObj ) {
return [];
}
return [
...( 'getPlanCompareFeatures' in planObj && planObj.getPlanCompareFeatures
? planObj.getPlanCompareFeatures()
: [] ),
...( 'getPromotedFeatures' in planObj && planObj.getPromotedFeatures
? planObj.getPromotedFeatures()
: [] ),
...( 'getSignupFeatures' in planObj && planObj.getSignupFeatures
? planObj.getSignupFeatures()
: [] ),
...( 'getSignupCompareAvailableFeatures' in planObj && planObj.getSignupCompareAvailableFeatures
? planObj.getSignupCompareAvailableFeatures()
: [] ),
...( 'getBlogSignupFeatures' in planObj && planObj.getBlogSignupFeatures
? planObj.getBlogSignupFeatures()
: [] ),
...( 'getPortfolioSignupFeatures' in planObj && planObj.getPortfolioSignupFeatures
? planObj.getPortfolioSignupFeatures()
: [] ),
...( 'getIncludedFeatures' in planObj && planObj.getIncludedFeatures
? planObj.getIncludedFeatures()
: [] ),
];
}
/**
* Determines if a plan has a superior version of a specific feature.
*/
export function planHasSuperiorFeature( plan: string | Plan, feature: string ): boolean {
const planConstantObj = getPlan( plan );
const features = planConstantObj?.getInferiorFeatures?.() ?? [];
return ( features as ReadonlyArray< string > ).includes( feature );
}
export function shouldFetchSitePlans( sitePlans: {
hasLoadedFromServer: boolean;
isRequesting: boolean;
} ): boolean {
return ! sitePlans.hasLoadedFromServer && ! sitePlans.isRequesting;
}
/**
* Returns the monthly slug which corresponds to the provided yearly slug or "" if the slug is
* not a recognized or cannot be converted.
*/
export function getMonthlyPlanByYearly( planSlug: string ): string {
const plan = getPlan( planSlug );
if ( plan && 'getMonthlySlug' in plan && plan.getMonthlySlug ) {
return plan.getMonthlySlug();
}
return findFirstSimilarPlanKey( planSlug, { term: TERM_MONTHLY } ) || '';
}
/**
* Returns the yearly slug which corresponds to the provided monthly slug or "" if the slug is
* not a recognized or cannot be converted.
*/
export function getYearlyPlanByMonthly( planSlug: string ): string {
const plan = getPlan( planSlug );
if ( plan && 'getAnnualSlug' in plan && plan.getAnnualSlug ) {
return plan.getAnnualSlug();
}
return findFirstSimilarPlanKey( planSlug, { term: TERM_ANNUALLY } ) || '';
}
/**
* Returns the biennial slug which corresponds to the provided slug or "" if the slug is
* not a recognized or cannot be converted.
*/
export function getBiennialPlan( planSlug: string ): string {
return findFirstSimilarPlanKey( planSlug, { term: TERM_BIENNIALLY } ) || '';
}
/**
* Returns the triennial slug which corresponds to the provided slug or "" if the slug is
* not recognized or cannot be converted.
*/
export function getTriennialPlan( planSlug: string ): string {
return findFirstSimilarPlanKey( planSlug, { term: TERM_TRIENNIALLY } ) || '';
}
/**
* Returns true if plan "types" match regardless of their interval.
*
* For example (fake plans):
* planLevelsMatch( PRO_YEARLY, PRO_YEARLY ) => true
* planLevelsMatch( PRO_YEARLY, PRO_MONTHLY ) => true
* planLevelsMatch( PRO_YEARLY, PERSONAL_YEARLY ) => false
*/
export function planLevelsMatch( planSlugA: string, planSlugB: string ): boolean {
const planA = getPlan( planSlugA );
const planB = getPlan( planSlugB );
return Boolean( planA && planB && planA.type === planB.type && planA.group === planB.group );
}
export function isEcommercePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_ECOMMERCE } );
}
export function isProPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PRO } );
}
export function isBusinessPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_BUSINESS } );
}
export function isPremiumPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PREMIUM } );
}
export function isPersonalPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PERSONAL } );
}
export function isBloggerPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_BLOGGER } );
}
export function isFreePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_FREE } );
}
export function isFreeHostingTrial( planSlug: string ): boolean {
return planSlug === PLAN_HOSTING_TRIAL_MONTHLY;
}
export function isBusinessTrial( planSlug: string ): boolean {
return planSlug === PLAN_HOSTING_TRIAL_MONTHLY || planSlug === PLAN_MIGRATION_TRIAL_MONTHLY;
}
export function is100YearPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_100_YEAR } );
}
// Checks if it is an Enterprise plan (a.k.a VIP), introduced as part of pdgrnI-1Qp-p2.
// This is not a real plan, but added to display Enterprise in the pricing grid.
export function isWpcomEnterpriseGridPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_ENTERPRISE_GRID_WPCOM, group: GROUP_WPCOM } );
}
export function isWooExpressPlusPlan( planSlug: string ): boolean {
return PLAN_WOOEXPRESS_PLUS === planSlug;
}
export function isWooExpressMediumPlan( planSlug: string ): boolean {
return [ PLAN_WOOEXPRESS_MEDIUM, PLAN_WOOEXPRESS_MEDIUM_MONTHLY ].includes( planSlug );
}
export function isWooExpressSmallPlan( planSlug: string ): boolean {
return [ PLAN_WOOEXPRESS_SMALL, PLAN_WOOEXPRESS_SMALL_MONTHLY ].includes( planSlug );
}
export function isWooExpressPlan( planSlug: string ): boolean {
return ( WOO_EXPRESS_PLANS as ReadonlyArray< string > ).includes( planSlug );
}
export function isFlexiblePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_FLEXIBLE } );
}
export function isStarterPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_STARTER } );
}
export function isJetpackStarterPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_JETPACK_STARTER } );
}
export function isJetpackGrowthPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_JETPACK_GROWTH } );
}
export function isSecurityDailyPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_SECURITY_DAILY } );
}
export function isSecurityRealTimePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_SECURITY_REALTIME } );
}
export function isSecurityT1Plan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_SECURITY_T1 } );
}
export function isSecurityT2Plan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_SECURITY_T2 } );
}
export function isCompletePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_ALL } );
}
export function isWpComPlan( planSlug: string ): boolean {
return planMatches( planSlug, { group: GROUP_WPCOM } );
}
export function isWpComBusinessPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_BUSINESS, group: GROUP_WPCOM } );
}
export function isWpComEcommercePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_ECOMMERCE, group: GROUP_WPCOM } );
}
export function isWpComProPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PRO, group: GROUP_WPCOM } );
}
export function isWpComPremiumPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PREMIUM, group: GROUP_WPCOM } );
}
export function isWpComPersonalPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PERSONAL, group: GROUP_WPCOM } );
}
export function isWpComBloggerPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_BLOGGER, group: GROUP_WPCOM } );
}
export function isWpComFreePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_FREE, group: GROUP_WPCOM } );
}
export function isWpComAnnualPlan( planSlug: string ): boolean {
return planMatches( planSlug, { term: TERM_ANNUALLY, group: GROUP_WPCOM } );
}
export function isWpComBiennialPlan( planSlug: string ): boolean {
return planMatches( planSlug, { term: TERM_BIENNIALLY, group: GROUP_WPCOM } );
}
export function isWpComTriennialPlan( planSlug: string ): boolean {
return planMatches( planSlug, { term: TERM_TRIENNIALLY, group: GROUP_WPCOM } );
}
export function isWpComMonthlyPlan( planSlug: string ): boolean {
return planMatches( planSlug, { term: TERM_MONTHLY, group: GROUP_WPCOM } );
}
export function isJetpackBusinessPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_BUSINESS, group: GROUP_JETPACK } );
}
export function isJetpackPremiumPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PREMIUM, group: GROUP_JETPACK } );
}
export function isJetpackPersonalPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_PERSONAL, group: GROUP_JETPACK } );
}
export function isJetpackFreePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_FREE, group: GROUP_JETPACK } );
}
export function isJetpackOfferResetPlan( planSlug: string ): boolean {
return ( JETPACK_RESET_PLANS as ReadonlyArray< string > ).includes( planSlug );
}
export function isP2FreePlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_FREE, group: GROUP_P2 } );
}
export function isP2PlusPlan( planSlug: string ): boolean {
return planMatches( planSlug, { type: TYPE_P2_PLUS } );
}
export function findFirstSimilarPlanKey(
planKey: string | Plan,
diff: PlanMatchesQuery
): string | undefined {
return findSimilarPlansKeys( planKey, diff )[ 0 ];
}
/**
* A similar plan is one that has the same `type`, `group`, and `term` as first
* argument, except for differences specified in the second argument.
*
* For example:
*
* > findSimilarPlansKeys( TYPE_BUSINESS, { term: TERM_BIENNIALLY } );
* [PLAN_BUSINESS_2_YEARS]
* > findSimilarPlansKeys( TYPE_JETPACK_BUSINESS_MONTHLY, { type: TYPE_ANNUALLY } );
* [TYPE_JETPACK_BUSINESS]
*/
export function findSimilarPlansKeys(
planKey: string | Plan,
diff: PlanMatchesQuery = {}
): string[] {
const plan = getPlan( planKey );
// @TODO: make getPlan() throw an error on failure. This is going to be a larger change with a separate PR.
if ( ! plan ) {
return [];
}
return findPlansKeys( {
type: plan.type,
group: plan.group,
term: plan.term,
...diff,
} );
}
/**
* Finds all keys of plans matching a query
*
* For example:
*
* > findPlansKeys( { term: TERM_BIENNIALLY } );
* [PLAN_PERSONAL_2_YEARS, PLAN_PREMIUM_2_YEARS, PLAN_BUSINESS_2_YEARS]
*/
export function findPlansKeys( query: PlanMatchesQuery = {} ): string[] {
const plans = getPlans();
return Object.keys( plans ).filter( ( k ) => planMatches( plans[ k ], query ) );
}
/**
* Matches plan specified by `planKey` against `query`.
* Only compares `type`, `group`, and `term` properties.
*
* For example:
*
* > planMatches( TYPE_BUSINESS, { term: TERM_ANNUALLY, group: GROUP_WPCOM, type: TYPE_BUSINESS } );
* true
*
* > planMatches( TYPE_BUSINESS, { term: TERM_BIENNIALLY } );
* false
*/
export function planMatches( planKey: string | Plan, query: PlanMatchesQuery = {} ): boolean {
const acceptedKeys = [ 'type', 'group', 'term' ];
const unknownKeys = Object.keys( query ).filter( ( key ) => ! acceptedKeys.includes( key ) );
if ( unknownKeys.length ) {
throw new Error(
`planMatches can only match against ${ acceptedKeys.join( ',' ) }, ` +
`but unknown keys ${ unknownKeys.join( ',' ) } were passed.`
);
}
// @TODO: make getPlan() throw an error on failure. This is going to be a larger change with a separate PR.
const plan = getPlan( planKey );
if ( ! plan ) {
return false;
}
if (
( ! ( 'type' in query ) || plan.type === query.type ) &&
( ! ( 'group' in query ) || plan.group === query.group ) &&
( ! ( 'term' in query ) || plan.term === query.term )
) {
return true;
}
return false;
}
export function calculateMonthlyPriceForPlan( planSlug: string, termPrice: number ): number {
const plan = getPlan( planSlug );
if ( ! plan ) {
throw new Error( `Unknown plan: ${ planSlug }` );
}
return calculateMonthlyPrice( plan.term, termPrice );
}
export function calculateMonthlyPrice( term: string, termPrice: number ): number {
const divisor = getBillingMonthsForTerm( term );
return parseFloat( ( termPrice / divisor ).toFixed( 2 ) );
}
export function getBillingMonthsForTerm( term: string ): number {
if ( term === TERM_MONTHLY ) {
return 1;
} else if ( term === TERM_ANNUALLY ) {
return 12;
} else if ( term === TERM_BIENNIALLY ) {
return 24;
} else if ( term === TERM_TRIENNIALLY ) {
return 36;
} else if ( term === TERM_QUADRENNIALLY ) {
return 48;
} else if ( term === TERM_QUINQUENNIALLY ) {
return 60;
} else if ( term === TERM_SEXENNIALLY ) {
return 72;
} else if ( term === TERM_SEPTENNIALLY ) {
return 84;
} else if ( term === TERM_OCTENNIALLY ) {
return 96;
} else if ( term === TERM_NOVENNIALLY ) {
return 108;
} else if ( term === TERM_DECENNIALLY ) {
return 120;
} else if ( term === TERM_CENTENNIALLY ) {
return 1200;
}
throw new Error( `Unknown term: ${ term }` );
}
export function getBillingYearsForTerm( term: string ): number {
if ( term === TERM_MONTHLY ) {
return 0;
} else if ( term === TERM_ANNUALLY ) {
return 1;
} else if ( term === TERM_BIENNIALLY ) {
return 2;
} else if ( term === TERM_TRIENNIALLY ) {
return 3;
} else if ( term === TERM_CENTENNIALLY ) {
return 100;
}
throw new Error( `Unknown term: ${ term }` );
}
export function getBillingTermForMonths( term: number ): string {
if ( term === 1 ) {
return TERM_MONTHLY;
} else if ( term === 12 ) {
return TERM_ANNUALLY;
} else if ( term === 24 ) {
return TERM_BIENNIALLY;
} else if ( term === 36 ) {
return TERM_TRIENNIALLY;
} else if ( term === 48 ) {
return TERM_QUADRENNIALLY;
} else if ( term === 60 ) {
return TERM_QUINQUENNIALLY;
} else if ( term === 72 ) {
return TERM_SEXENNIALLY;
} else if ( term === 84 ) {
return TERM_SEPTENNIALLY;
} else if ( term === 96 ) {
return TERM_OCTENNIALLY;
} else if ( term === 108 ) {
return TERM_NOVENNIALLY;
} else if ( term === 120 ) {
return TERM_DECENNIALLY;
} else if ( term === 1200 ) {
return TERM_CENTENNIALLY;
}
throw new Error( `Unknown term: ${ term }` );
}
export function plansLink(
urlString: string,
siteSlug: string | undefined | null,
intervalType: string,
forceIntervalType = false
): string {
const url = new URL( urlString, window.location.origin );
if ( 'monthly' === intervalType || forceIntervalType ) {
url.pathname += '/' + intervalType;
}
if ( siteSlug ) {
url.pathname += '/' + siteSlug;
}
if ( urlString.startsWith( '/' ) ) {
return url.pathname + url.search;
}
return url.toString();
}
export type FilteredPlan = Plan &
Pick<
WPComPlan,
| 'getPlanCompareFeatures'
| 'getAnnualPlansOnlyFeatures'
| 'getPlanTagline'
| 'getNewsletterTagLine'
| 'getBlogOnboardingTagLine'
>;
export function applyTestFiltersToPlansList(
planName: string | Plan,
abtest: string | undefined,
extraArgs: Record< string, string | boolean[] > = {}
): FilteredPlan {
const plan = getPlan( planName );
if ( ! plan ) {
throw new Error( `Unknown plan: ${ planName }` );
}
const filteredPlanConstantObj = { ...plan };
const filteredPlanFeaturesConstantList =
'getPlanCompareFeatures' in plan && plan.getPlanCompareFeatures
? plan.getPlanCompareFeatures( abtest, extraArgs )
: [];
/* eslint-disable @typescript-eslint/no-empty-function */
// these becomes no-ops when we removed some of the abtest overrides, but
// we're leaving the code in place for future tests
const removeDisabledFeatures = () => {};
const updatePlanDescriptions = () => {};
const updatePlanFeatures = () => {};
/* eslint-enable */
removeDisabledFeatures();
updatePlanDescriptions();
updatePlanFeatures();
return {
...filteredPlanConstantObj,
getPlanCompareFeatures: () => filteredPlanFeaturesConstantList,
};
}
export function applyTestFiltersToProductsList(
productName: string
): Product & Pick< WPComPlan, 'getPlanCompareFeatures' > {
const product = getProductFromSlug( productName );
if ( typeof product === 'string' ) {
throw new Error( `Unknown product ${ productName } ` );
}
const filteredProductConstantObj = { ...product };
/* eslint-disable @typescript-eslint/no-empty-function */
// these becomes no-ops when we removed some of the abtest overrides, but
// we're leaving the code in place for future tests
const removeDisabledFeatures = () => {};
const updatePlanDescriptions = () => {};
const updatePlanFeatures = () => {};
/* eslint-enable */
removeDisabledFeatures();
updatePlanDescriptions();
updatePlanFeatures();
return {
...filteredProductConstantObj,
getPlanCompareFeatures: () => [],
};
}
export function getPlanTermLabel(
planName: string,
translate: ( text: string ) => string
): string | undefined {
const plan = getPlan( planName );
if ( ! plan || ! plan.term ) {
return;
}
switch ( plan.term ) {
case TERM_MONTHLY:
return translate( 'Monthly subscription' );
case TERM_ANNUALLY:
return translate( 'Annual subscription' );
case TERM_BIENNIALLY:
return translate( 'Two year subscription' );
case TERM_TRIENNIALLY:
return translate( 'Three year subscription' );
case TERM_CENTENNIALLY:
return translate( 'Hundred year subscription' );
}
}
export const getPopularPlanSpec = ( {
flowName,
customerType,
isJetpack,
availablePlans,
}: {
flowName?: string | null;
customerType: string;
isJetpack: boolean;
availablePlans: string[];
} ): false | { type: string; group: string } => {
// Jetpack doesn't currently highlight "Popular" plans
if ( isJetpack ) {
return false;
}
if ( availablePlans.length === 0 ) {
return false;
}
const defaultPlan = getPlan( availablePlans[ 0 ] );
if ( ! defaultPlan ) {
return false;
}
const group = GROUP_WPCOM;
if ( flowName === 'hosting' ) {
return {
type: TYPE_BUSINESS,
group,
};
}
if ( customerType === 'personal' ) {
if ( availablePlans.findIndex( isPremiumPlan ) !== -1 ) {
return {
type: TYPE_PREMIUM,
group,
};
}
// when customerType is not personal, default to business
} else if ( availablePlans.findIndex( isBusinessPlan ) !== -1 ) {
return {
type: TYPE_BUSINESS,
group,
};
}
// finally, just return the default one.
return {
type: defaultPlan.type,
group,
};
};
function isValueTruthy< T >( value: T ): value is Exclude< T, null | undefined | false | 0 | '' > {
return !! value;
}
export const chooseDefaultCustomerType = ( {
currentCustomerType,
selectedPlan,
currentPlan,
}: {
currentCustomerType: string;
selectedPlan?: string;
currentPlan: { productSlug: PlanSlug | undefined };
} ): string => {
if ( currentCustomerType ) {
return currentCustomerType;
}
const group = GROUP_WPCOM;
const businessPlanSlugs = [
findPlansKeys( { group, term: TERM_ANNUALLY, type: TYPE_PREMIUM } )[ 0 ],
findPlansKeys( { group, term: TERM_BIENNIALLY, type: TYPE_PREMIUM } )[ 0 ],
findPlansKeys( { group, term: TERM_TRIENNIALLY, type: TYPE_PREMIUM } )[ 0 ],
findPlansKeys( { group, term: TERM_ANNUALLY, type: TYPE_BUSINESS } )[ 0 ],
findPlansKeys( { group, term: TERM_BIENNIALLY, type: TYPE_BUSINESS } )[ 0 ],
findPlansKeys( { group, term: TERM_TRIENNIALLY, type: TYPE_BUSINESS } )[ 0 ],
findPlansKeys( { group, term: TERM_ANNUALLY, type: TYPE_ECOMMERCE } )[ 0 ],
findPlansKeys( { group, term: TERM_BIENNIALLY, type: TYPE_ECOMMERCE } )[ 0 ],
findPlansKeys( { group, term: TERM_TRIENNIALLY, type: TYPE_ECOMMERCE } )[ 0 ],
findPlansKeys( { group, term: TERM_ANNUALLY, type: TYPE_PRO } )[ 0 ],
findPlansKeys( { group, term: TERM_BIENNIALLY, type: TYPE_PRO } )[ 0 ],
]
.map( ( planKey ) => getPlan( planKey ) )
.filter( isValueTruthy )
.map( ( plan ) => plan.getStoreSlug() );
if ( selectedPlan ) {
return businessPlanSlugs.includes( selectedPlan as PlanSlug ) ? 'business' : 'personal';
} else if ( currentPlan ) {
const isPlanInBusinessGroup =
businessPlanSlugs.indexOf( currentPlan.productSlug as PlanSlug ) !== -1;
return isPlanInBusinessGroup ? 'business' : 'personal';
}
return 'personal';
};
/**
* Determines if a plan includes Jetpack Search by looking at the plan's features.
*/
export const planHasJetpackSearch = ( planSlug: string ): boolean =>
planHasFeature( planSlug, FEATURE_JETPACK_SEARCH ) ||
planHasFeature( planSlug, FEATURE_JETPACK_SEARCH_MONTHLY );
/**
* Determines if a plan includes Jetpack Search Classic by checking available plans.
*/
export function planHasJetpackClassicSearch(
plan: WithCamelCaseSlug | WithSnakeCaseSlug
): boolean {
return (
plan &&
( isJetpackBusiness( plan ) ||
isBusiness( plan ) ||
isEnterprise( plan ) ||
isEcommerce( plan ) ||
isPro( plan ) ||
isVipPlan( plan ) )
);
}
export function getFeaturesList(): FeatureList {
return FEATURES_LIST;
}
export const getPlanFeaturesObject = ( planFeaturesList?: Array< string > ) => {
if ( ! planFeaturesList ) {
return [];
}
return planFeaturesList.map( ( featuresConst ) => FEATURES_LIST[ featuresConst ] );
};
export function isValidFeatureKey( feature: string ) {
return !! FEATURES_LIST[ feature ];
}
export function getFeatureByKey( feature: string ) {
return FEATURES_LIST[ feature ];
}
export function getFeatureDifference(
smallerPlan: string,
biggerPlan: string,
featureBundleSelector: keyof WPComPlan
) {
let biggerPlanObject = ( getPlan( biggerPlan ) as WPComPlan )?.[ featureBundleSelector ] as
| Array< string >
| ( () => Array< string > );
let smallerPlanObject = ( getPlan( smallerPlan ) as WPComPlan )?.[ featureBundleSelector ] as
| Array< string >
| ( () => Array< string > );
if ( typeof biggerPlanObject === 'function' ) {
biggerPlanObject = biggerPlanObject();
} else if ( ! Array.isArray( biggerPlanObject ) ) {
biggerPlanObject = [];
}
if ( typeof smallerPlanObject === 'function' ) {
smallerPlanObject = smallerPlanObject();
} else if ( ! Array.isArray( smallerPlanObject ) ) {
smallerPlanObject = [];
}
const difference = biggerPlanObject.filter(
( feature ) => ! smallerPlanObject.includes( feature )
);
return difference;
}