import { Subscription } from './base'; export type PriceKey = `${'monthly' | 'yearly'}_${string}`; export type Price = { amount: number; id: string; period: 'monthly' | 'yearly'; status: 'active' | 'inactive'; }; export type Product = { id: string; prices: Record; }; export type PlanTier = 'basic' | 'legacy_pro' | 'pro' | 'scale'; export type Plan = { eligiblePlanTiers: PlanTier[]; label: string; products: Record<'dev' | 'prod', Product[]>; tier: PlanTier; }; export type Configuration = { business_profile: { headline: string; privacy_policy_url: string; terms_of_service_url: string; }; default_return_url: string; features: { payment_method_update: { enabled: boolean; }; subscription_update: { default_allowed_updates: ('price' | 'promotion_code' | 'quantity')[]; enabled: boolean; products: { prices: string[]; product: string }[]; }; }; metadata: { [name: string]: null | number | string }; }; export enum PortalConfigKey { BuilderUpdateDev = 'builder_update_dev', BuilderUpdateProd = 'builder_update_prod', BuilderUpgradeDev = 'builder_upgrade_dev', BuilderUpgradeProd = 'builder_upgrade_prod', CLIUpdateDev = 'cli_update_dev', CLIUpdateProd = 'cli_update_prod', CLIUpgradeDev = 'cli_upgrade_dev', CLIUpgradeProd = 'cli_upgrade_prod', } // --- factory functions --- const createPrice = (id: string, price: Omit): Price => { return { id, ...price }; }; const createProduct = ( id: string, prices: Record, ): Product => ({ id, prices }); const createPlan = ( tier: PlanTier, products: { dev: Product[]; prod: Product[] }, ): Plan => { const record: Record = { basic: { label: 'Basic', tiers: ['basic', 'legacy_pro', 'pro', 'scale'] }, legacy_pro: { label: 'Pro', tiers: ['legacy_pro', 'pro', 'scale'] }, pro: { label: 'Pro', tiers: ['pro', 'scale'] }, scale: { label: 'Scale', tiers: ['scale'] }, }; const { label, tiers } = record[tier]; return { eligiblePlanTiers: tiers, label, products: { dev: products.dev, prod: products.prod }, tier, }; }; const PORTAL_VERSION = 1; // increment this value to force Stripe to update the portal configurations const createPortalConfig = ( key: PortalConfigKey, products: Product[], ): Configuration => { return { business_profile: { headline: 'ToDesktop Billing Portal', privacy_policy_url: 'https://www.todesktop.com/privacy-policy', terms_of_service_url: 'https://www.todesktop.com/terms', }, default_return_url: 'https://app.todesktop.com', features: { payment_method_update: { enabled: true }, subscription_update: { default_allowed_updates: ['price'], enabled: true, products: products.map((product) => { return { prices: Object.values(product.prices) .filter((price) => price.status === 'active') .map((price) => price.id), product: product.id, }; }), }, }, metadata: { key, version: PORTAL_VERSION }, }; }; // --- products --- export const products = { dev: { builder: { essential: createProduct('prod_MoR1S4o0nLv00y', { monthly_125: createPrice('price_1PuuAqIewCKA2h0Iw4judQNj', { amount: 125, period: 'monthly', status: 'active', }), monthly_99: createPrice('price_1M4o9XIewCKA2h0IVfGrid8E', { amount: 99, period: 'monthly', status: 'inactive', }), yearly_1200: createPrice('price_1PuvdgIewCKA2h0IisTKqjFN', { amount: 1200, period: 'yearly', status: 'active', }), }), professional: createProduct('prod_MoR1SchsRDAIgY', { monthly_199: createPrice('price_1M4o9vIewCKA2h0IHREokdD8', { amount: 199, period: 'monthly', status: 'inactive', }), monthly_240: createPrice('price_1M4o9vIewCKA2h0IHREokdD8', { amount: 240, period: 'monthly', status: 'inactive', }), monthly_300: createPrice('price_1PuuHrIewCKA2h0IQfEZ4Wog', { amount: 300, period: 'monthly', status: 'active', }), yearly_2880: createPrice('price_1PuuJAIewCKA2h0IibpUIhf9', { amount: 2880, period: 'yearly', status: 'active', }), }), }, cli: { founder: createProduct('prod_Gq0E8nbTYHtkPl', { monthly_100: createPrice('plan_Gq0FzdmoTJshQL', { amount: 100, period: 'monthly', status: 'inactive', }), monthly_125: createPrice('price_1PuuMRIewCKA2h0IrggfwCwA', { amount: 125, period: 'monthly', status: 'active', }), yearly_1200: createPrice('price_1PuuMkIewCKA2h0IRxhkDpmk', { amount: 1200, period: 'yearly', status: 'active', }), }), founder30: createProduct('prod_GsL6LaRo4NFD5n', { monthly_30: createPrice('plan_GsL7NZskOY0TC9', { amount: 30, period: 'monthly', status: 'active', }), }), founder50: createProduct('prod_GsL5O1PdbxOrxJ', { monthly_50: createPrice('plan_GsL6VYAshfh7c4', { amount: 50, period: 'monthly', status: 'active', }), monthly_90: createPrice('plan_GsL6VYAshfh7c4', { amount: 90, period: 'monthly', status: 'inactive', }), }), performance: createProduct('prod_LphQku6BNkzCga', { monthly_300: createPrice('price_1L821UIewCKA2h0IUwRhicyo', { amount: 300, period: 'monthly', status: 'inactive', }), monthly_375: createPrice('price_1PuuO7IewCKA2h0IOK9ivzS0', { amount: 375, period: 'monthly', status: 'active', }), yearly_3600: createPrice('price_1PuuOKIewCKA2h0IrcVvJkgG', { amount: 3600, period: 'yearly', status: 'active', }), }), scale: createProduct('prod_LphQxNgfUIxVfA', { monthly_1200: createPrice('price_1L821wIewCKA2h0IMLUmjulL', { amount: 1200, period: 'monthly', status: 'inactive', }), monthly_1500: createPrice('price_1PuuPFIewCKA2h0IbPmKdsJr', { amount: 1500, period: 'monthly', status: 'active', }), yearly_14400: createPrice('price_1PuuPaIewCKA2h0IhLfSlRFj', { amount: 14400, period: 'yearly', status: 'active', }), }), }, legacy: { business: createProduct('prod_Fe8CbCE2wGJIWr', { monthly_199: createPrice('plan_Fe8CUkZfXasFtB', { amount: 199, period: 'monthly', status: 'active', }), }), enterprise: createProduct('prod_Hc9PMnHUmHvOlw', { monthly_700: createPrice('price_1H2v6JIewCKA2h0IgUwsuctb', { amount: 700, period: 'monthly', status: 'active', }), }), essential: createProduct('prod_FplN7CkqFRH0ye', { monthly_58: createPrice('plan_FpljxNuZtNFFbm', { amount: 58, period: 'monthly', status: 'active', }), }), essentialNew: createProduct('prod_GYXm5X66hM1Ywj', { monthly_58: createPrice('plan_GYXmTA3EdfCv4q', { amount: 58, period: 'monthly', status: 'active', }), yearly_580: createPrice('plan_GYXmTA3EdfCv4q', { amount: 580, period: 'yearly', status: 'active', }), }), growth: createProduct('prod_FplOSDbBmxba94', { monthly_199: createPrice('plan_FplOshrr1w3Mvu', { amount: 199, period: 'monthly', status: 'active', }), }), professional: createProduct('prod_GYXo54YK5kga12', { monthly_199: createPrice('plan_GYXoIkvwVKyfF2', { amount: 199, period: 'monthly', status: 'active', }), yearly_1990: createPrice('price_1KHU70IewCKA2h0IR87yXOQ2', { amount: 1990, period: 'yearly', status: 'inactive', }), yearly_2388: createPrice('price_1KHU70IewCKA2h0IR87yXOQ2', { amount: 2388, period: 'yearly', status: 'active', }), }), startup: createProduct('prod_Fe8BTslXbdylxV', { monthly_49: createPrice('plan_Fe8BzLp7k71eGD', { amount: 49, period: 'monthly', status: 'active', }), yearly_200: createPrice('plan_Fe8BzLp7k71eGD', { amount: 200, period: 'yearly', status: 'active', }), }), }, }, prod: { builder: { essential: createProduct('prod_MoR2Ldm5PJpJZJ', { monthly_125: createPrice('price_1PuuU1IewCKA2h0IUSAcuUUb', { amount: 125, period: 'monthly', status: 'active', }), monthly_99: createPrice('price_1M4oA4IewCKA2h0IWgfbSJBe', { amount: 99, period: 'monthly', status: 'inactive', }), yearly_1200: createPrice('price_1PuuUZIewCKA2h0IDMNwNqQl', { amount: 1200, period: 'yearly', status: 'active', }), }), professional: createProduct('prod_MoR2h0Nf16hhgb', { monthly_199: createPrice('price_1M4oABIewCKA2h0INkbaCbIS', { amount: 199, period: 'monthly', status: 'inactive', }), monthly_240: createPrice('price_1MIDxtIewCKA2h0IvWi5weJL', { amount: 240, period: 'monthly', status: 'inactive', }), monthly_300: createPrice('price_1PuuVSIewCKA2h0IETsLUwF9', { amount: 300, period: 'monthly', status: 'active', }), yearly_2880: createPrice('price_1PuuVmIewCKA2h0I37dMpqI5', { amount: 2880, period: 'yearly', status: 'active', }), }), }, cli: { founder: createProduct('prod_GpzVRVsBgluoef', { monthly_100: createPrice('plan_GpzWZLfsOzjrvI', { amount: 100, period: 'monthly', status: 'inactive', }), monthly_125: createPrice('price_1PuuXFIewCKA2h0IVHSyfFsd', { amount: 125, period: 'monthly', status: 'active', }), yearly_1200: createPrice('price_1PuuXYIewCKA2h0IAsygBajd', { amount: 1200, period: 'yearly', status: 'active', }), }), founder30: createProduct('prod_GrHtZKuUDvLZOF', { monthly_30: createPrice('plan_GrHuyGjzYN0XN2', { amount: 30, period: 'monthly', status: 'active', }), }), founder50: createProduct('prod_GsL0ntimYpTTwD', { monthly_50: createPrice('plan_GsL1IRUwpj5CIF', { amount: 50, period: 'monthly', status: 'active', }), monthly_90: createPrice('price_1JMHNnIewCKA2h0II5RGjVtk', { amount: 90, period: 'monthly', status: 'inactive', }), }), performance: createProduct('prod_LphRn5Rfuq3CMh', { monthly_300: createPrice('price_1L822LIewCKA2h0I5JYyOG1p', { amount: 300, period: 'monthly', status: 'inactive', }), monthly_375: createPrice('price_1PuuYKIewCKA2h0IWBJ4cyRQ', { amount: 375, period: 'monthly', status: 'active', }), yearly_3600: createPrice('price_1PuuYcIewCKA2h0IzeU1xWbQ', { amount: 3600, period: 'yearly', status: 'active', }), }), scale: createProduct('prod_LphRKSuMLjQriP', { monthly_1200: createPrice('price_1L822RIewCKA2h0IPt9f2nZM', { amount: 1200, period: 'monthly', status: 'inactive', }), monthly_1500: createPrice('price_1PuuarIewCKA2h0IEONPcFU5', { amount: 1500, period: 'monthly', status: 'active', }), yearly_14400: createPrice('price_1Puub5IewCKA2h0IMJ3CQ58r', { amount: 14400, period: 'yearly', status: 'active', }), }), }, legacy: { business: createProduct('prod_FdmEvAgT7ZtVEw', { monthly_199: createPrice('plan_FdmE8akq9ukxiY', { amount: 199, period: 'monthly', status: 'active', }), }), enterprise: createProduct('prod_GuGGWeMQ3SCuE9', { monthly_700: createPrice('plan_GuGICX6nRtDthN', { amount: 700, period: 'monthly', status: 'active', }), }), essential: createProduct('prod_FohIP5aSMWWGyY', { monthly_58: createPrice('plan_FohJzCujA6vvt4', { amount: 58, period: 'monthly', status: 'active', }), }), essentialNew: createProduct('prod_GYXn73zcoJKsF8', { monthly_58: createPrice('plan_GYXn2cnPl5dy7j', { amount: 58, period: 'monthly', status: 'active', }), yearly_580: createPrice('price_1Gqb24IewCKA2h0ILP5rxTnT', { amount: 580, period: 'yearly', status: 'active', }), }), growth: createProduct('prod_FohKfjLOlJNm1W', { monthly_199: createPrice('plan_FohKGuWis5ocsi', { amount: 199, period: 'monthly', status: 'active', }), }), professional: createProduct('prod_GYXouE5K7vuitU', { monthly_199: createPrice('plan_GYXoKsa2j0yURg', { amount: 199, period: 'monthly', status: 'active', }), yearly_1990: createPrice('price_1KHTgaIewCKA2h0I81TVg85r', { amount: 1990, period: 'yearly', status: 'inactive', }), yearly_2388: createPrice('price_1KLT32IewCKA2h0IFeJOAgG5', { amount: 2388, period: 'yearly', status: 'active', }), }), startup: createProduct('prod_FdmCGcrX2IETUH', { monthly_49: createPrice('plan_FdmDAUN7JGD8pR', { amount: 49, period: 'monthly', status: 'active', }), yearly_200: createPrice('plan_Ft8qvk401twwrW', { amount: 200, period: 'yearly', status: 'active', }), }), }, }, } as const; const { dev, prod } = products; // --- plans --- export const basicPlan = createPlan('basic', { dev: [ dev.builder.essential, dev.cli.founder30, dev.cli.founder50, dev.cli.founder, dev.legacy.essential, dev.legacy.essentialNew, dev.legacy.startup, ], prod: [ prod.builder.essential, prod.cli.founder30, prod.cli.founder50, prod.cli.founder, prod.legacy.essential, prod.legacy.essentialNew, prod.legacy.startup, ], }); export const distributionPlan = createPlan('legacy_pro', { dev: [ dev.builder.professional, dev.cli.founder, dev.cli.founder30, dev.cli.founder50, dev.cli.performance, dev.cli.scale, dev.legacy.professional, dev.legacy.growth, dev.legacy.business, ], prod: [ prod.builder.professional, prod.cli.founder, prod.cli.founder30, prod.cli.founder50, prod.cli.performance, prod.cli.scale, prod.legacy.professional, prod.legacy.growth, prod.legacy.business, ], }); // Kept as an alias for compatibility. Prefer `distributionPlan` in new code, // since this bucket also includes current CLI plans. export const legacyProPlan = distributionPlan; export const proPlan = createPlan('pro', { dev: [ dev.builder.professional, dev.cli.performance, dev.legacy.professional, dev.legacy.growth, dev.legacy.business, ], prod: [ prod.builder.professional, prod.cli.performance, prod.legacy.professional, prod.legacy.growth, prod.legacy.business, ], }); export const scalePlan = createPlan('scale', { dev: [dev.cli.scale, dev.legacy.enterprise], prod: [prod.cli.scale, prod.legacy.enterprise], }); // --- configurations --- export const configurations = { dev: { builder: { update: createPortalConfig(PortalConfigKey.BuilderUpdateDev, [ dev.builder.essential, dev.builder.professional, ]), upgrade: createPortalConfig(PortalConfigKey.BuilderUpgradeDev, [ dev.builder.professional, ]), }, cli: { update: createPortalConfig(PortalConfigKey.CLIUpdateDev, [ dev.cli.founder, dev.cli.performance, dev.cli.scale, ]), upgrade: createPortalConfig(PortalConfigKey.CLIUpgradeDev, [ dev.cli.performance, dev.cli.scale, ]), }, }, prod: { builder: { update: createPortalConfig(PortalConfigKey.BuilderUpdateProd, [ prod.builder.essential, prod.builder.professional, ]), upgrade: createPortalConfig(PortalConfigKey.BuilderUpgradeProd, [ prod.builder.professional, ]), }, cli: { update: createPortalConfig(PortalConfigKey.CLIUpdateProd, [ prod.cli.founder, prod.cli.performance, prod.cli.scale, ]), upgrade: createPortalConfig(PortalConfigKey.CLIUpgradeProd, [ prod.cli.performance, prod.cli.scale, ]), }, }, }; // --- utilities --- export const hasActiveSub = (sub?: Subscription) => { return !!(sub && (sub.status === 'active' || sub.status === 'trialing')); }; export const hasPlan = (plan: Plan, sub?: Subscription) => { if (!sub || !hasActiveSub(sub)) return false; const plans = [basicPlan, legacyProPlan, proPlan, scalePlan]; // find the plans that are eligible to be tested const eligiblePlans = plans.filter((eligiblePlan) => { return plan.eligiblePlanTiers.includes(eligiblePlan.tier); }); // test if any of those plans contains the current subscription for (const eligiblePlan of eligiblePlans) { const allProducts = [ ...eligiblePlan.products.dev, ...eligiblePlan.products.prod, ]; for (const { id, prices } of allProducts) { if (sub?.productId && sub?.productId === id) { return true; } if (Object.values(prices).some((price) => price.id === sub?.planId)) { return true; } } } return false; }; export const getPlan = ( sub?: Pick, ): null | Plan => { const plans = [basicPlan, legacyProPlan, proPlan, scalePlan]; for (const plan of plans) { const allProducts = [...plan.products.dev, ...plan.products.prod]; for (const { id, prices } of allProducts) { if (sub?.productId && sub?.productId === id) { return plan; } if (Object.values(prices).some((price) => price.id === sub?.planId)) { return plan; } } } return null; }; // --- legacy support for createStripeCheckoutSession - delete when no longer needed --- export type LegacyProductKey = | 'builder_essential' | 'builder_professional' | 'business' | 'cli_founder_30' | 'cli_founder_50' | 'cli_founder' | 'cli_performance' | 'cli_scale' | 'enterprise' | 'essential_new' | 'essential' | 'growth' | 'professional_annual_full_price' | 'professional_annual' | 'professional' | 'startup'; export const LegacyProductRecord: Record< LegacyProductKey, Record<'dev' | 'prod', Product> > = { builder_essential: { dev: dev.builder.essential, prod: prod.builder.essential, }, builder_professional: { dev: dev.builder.professional, prod: prod.builder.professional, }, business: { dev: dev.legacy.business, prod: prod.legacy.business }, cli_founder: { dev: dev.cli.founder, prod: prod.cli.founder }, cli_founder_30: { dev: dev.cli.founder30, prod: prod.cli.founder30 }, cli_founder_50: { dev: dev.cli.founder50, prod: prod.cli.founder50 }, cli_performance: { dev: dev.cli.performance, prod: prod.cli.performance }, cli_scale: { dev: dev.cli.scale, prod: prod.cli.scale }, enterprise: { dev: dev.legacy.enterprise, prod: prod.legacy.enterprise }, essential: { dev: dev.legacy.essential, prod: prod.legacy.essential }, essential_new: { dev: dev.legacy.essentialNew, prod: prod.legacy.essentialNew, }, growth: { dev: dev.legacy.growth, prod: prod.legacy.growth }, professional: { dev: dev.legacy.professional, prod: prod.legacy.professional, }, professional_annual: { dev: dev.legacy.professional, prod: prod.legacy.professional, }, professional_annual_full_price: { dev: dev.legacy.professional, prod: prod.legacy.professional, }, startup: { dev: dev.legacy.startup, prod: prod.legacy.startup }, };