/** * Plan catalog — the single source of truth for tiers, limits, and features. * Provider-agnostic: Stripe and PayPal both map onto these plan keys, and * `enforceLimit` / feature-gating read from here (never from a payment provider). * * Prices are in cents. A limit of `UNLIMITED` (-1) means no cap. */ export const UNLIMITED = -1; export type PlanKey = "free" | "creator" | "pro" | "agency"; export type FeatureKey = | "multi_voice" | "cover_art" | "content_repurposing" | "all_languages" | "ai_cohost" | "series_generator" | "api_access" | "team_workspace" | "white_label" | "custom_branding" | "priority_generation"; export type UsageMetric = "script" | "audio" | "art" | "repurpose"; export interface PlanLimits { /** Monthly caps per metric; UNLIMITED (-1) = no cap. */ script: number; audio: number; art: number; repurpose: number; /** Seats in the team workspace (Agency). 1 for individual plans. */ seats: number; /** Max length of a generated episode, in minutes. */ maxEpisodeMinutes: number; } export interface Plan { key: PlanKey; name: string; /** Short marketing tagline. */ tagline: string; priceMonthly: number; // cents priceYearly: number; // cents (≈ 2 months free) highlight?: boolean; // "most popular" limits: PlanLimits; features: FeatureKey[]; /** Human-readable bullet points for the pricing page. */ bullets: string[]; } export const PLANS: Record = { free: { key: "free", name: "Free", tagline: "Try it out, no card required.", priceMonthly: 0, priceYearly: 0, limits: { script: 3, audio: 1, art: 3, repurpose: 1, seats: 1, maxEpisodeMinutes: 5 }, features: ["multi_voice", "cover_art"], bullets: [ "3 scripts / month", "1 audio generation / month", "Up to 5-minute episodes", "2 narrator voices", "Cover art generation", ], }, creator: { key: "creator", name: "Creator", tagline: "For solo creators shipping regularly.", priceMonthly: 900, priceYearly: 9000, limits: { script: 50, audio: 20, art: 50, repurpose: 50, seats: 1, maxEpisodeMinutes: 20 }, features: ["multi_voice", "cover_art", "content_repurposing", "all_languages"], bullets: [ "50 scripts / month", "20 audio generations / month", "Up to 20-minute episodes", "All 14+ voices", "13+ languages", "Content repurposing (blog, social, newsletter)", ], }, pro: { key: "pro", name: "Pro", tagline: "For serious podcasters and small studios.", priceMonthly: 2900, priceYearly: 29000, highlight: true, limits: { script: UNLIMITED, audio: 100, art: UNLIMITED, repurpose: UNLIMITED, seats: 1, maxEpisodeMinutes: 45 }, features: [ "multi_voice", "cover_art", "content_repurposing", "all_languages", "ai_cohost", "series_generator", "api_access", "priority_generation", ], bullets: [ "Unlimited scripts", "100 audio generations / month", "Up to 45-minute episodes", "AI co-host mode", "Series & season generator", "API access", "Priority generation queue", ], }, agency: { key: "agency", name: "Agency", tagline: "For teams and white-label studios.", priceMonthly: 7900, priceYearly: 79000, limits: { script: UNLIMITED, audio: UNLIMITED, art: UNLIMITED, repurpose: UNLIMITED, seats: 5, maxEpisodeMinutes: 90 }, features: [ "multi_voice", "cover_art", "content_repurposing", "all_languages", "ai_cohost", "series_generator", "api_access", "priority_generation", "team_workspace", "white_label", "custom_branding", ], bullets: [ "Everything in Pro, unlimited", "5-seat team workspace", "White-label mode", "Custom branding", "Up to 90-minute episodes", "Priority support", ], }, }; export const PLAN_ORDER: PlanKey[] = ["free", "creator", "pro", "agency"]; export function getPlan(key: string | null | undefined): Plan { if (key && key in PLANS) return PLANS[key as PlanKey]; return PLANS.free; } export function planHasFeature(key: PlanKey, feature: FeatureKey): boolean { return PLANS[key].features.includes(feature); } /** True when `count` is within the plan's cap for `metric` (UNLIMITED always passes). */ export function withinLimit(key: PlanKey, metric: UsageMetric, count: number): boolean { const limit = PLANS[key].limits[metric]; return limit === UNLIMITED || count < limit; }