{"record":{"id":"efe2e50966f135ea","repo":"toeverything/AFFiNE","slug":"unsupported-subscription-plan","errorCode":"unsupported_subscription_plan","errorMessage":"Unsupported subscription plan: ${plan}.","messagePattern":"Unsupported subscription plan: (.+?)\\.","errorType":"exception","errorClass":"UnsupportedSubscriptionPlan","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/payment/service.ts","lineNumber":105,"sourceCode":"  ) {\n    this.scheduleManager = new ScheduleManager(this.stripeProvider);\n  }\n\n  get stripe() {\n    return this.stripeProvider.stripe;\n  }\n\n  select(plan: SubscriptionPlan): SubscriptionManager {\n    switch (plan) {\n      case SubscriptionPlan.Team:\n        return this.workspaceManager;\n      case SubscriptionPlan.Pro:\n      case SubscriptionPlan.AI:\n        return this.userManager;\n      case SubscriptionPlan.SelfHostedTeam:\n        return this.selfhostManager;\n      default:\n        throw new UnsupportedSubscriptionPlan({ plan });\n    }\n  }\n\n  async listPrices(user?: CurrentUser): Promise<KnownStripePrice[]> {\n    const prices = await this.listStripePrices();\n\n    const customer = user\n      ? await this.getOrCreateCustomer({\n          userId: user.id,\n          userEmail: user.email,\n        })\n      : undefined;\n\n    return [\n      ...(await this.userManager.filterPrices(prices, customer)),\n      ...this.workspaceManager.filterPrices(prices, customer),\n    ];\n  }","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/payment/service.ts#L87-L123","documentation":"Thrown by SubscriptionService.select(plan) when the plan does not map to a subscription manager. Only Team, Pro, AI, and SelfHostedTeam are purchasable-managed plans; Free, Enterprise, SelfHosted (and any unknown string reaching the switch) fall through to UnsupportedSubscriptionPlan.","triggerScenarios":"Calling checkout/cancel/resume/updateRecurring with plan 'free', 'enterprise', or 'selfhosted'; sending a plan string not in the SubscriptionPlan enum that TypeScript did not catch (untyped client, raw HTTP); plan value read from a config/env and passed through unvalidated.","commonSituations":"Checkout page built from a plan list that includes Free; Enterprise customers trying the self-serve checkout; API consumers using a plan name that changed casing or was renamed across versions.","solutions":["Only pass 'pro', 'ai', 'team', or 'selfhostedteam' as plan to checkout/subscription mutations.","Filter the plan options in your UI/pricing config to the purchasable set before rendering or submitting.","Validate the plan against the enum at the client boundary (zod/TS union) so bad values fail locally with a clear message."],"exampleFix":"// before\nconst PLAN = process.env.NEXT_PUBLIC_PLAN!; // 'free' slips in\nawait svc.checkout({ plan: PLAN, recurring }, args);\n\n// after\nconst PurchasablePlan = z.enum(['pro', 'ai', 'team', 'selfhostedteam']);\nconst plan = PurchasablePlan.parse(inputPlan); // throws client-side, not server-side\nawait svc.checkout({ plan, recurring }, args);","handlingStrategy":"type-guard","validationCode":"const PURCHASABLE = ['pro', 'ai', 'team', 'selfhostedteam'] as const;\nconst PurchasablePlan = z.enum(PURCHASABLE);\nconst plan = PurchasablePlan.parse(rawPlan); // fails client-side with clear message","typeGuard":"type PurchasablePlan = 'pro' | 'ai' | 'team' | 'selfhostedteam';\nfunction isPurchasablePlan(p: string): p is PurchasablePlan {\n  return p === 'pro' || p === 'ai' || p === 'team' || p === 'selfhostedteam';\n}","tryCatchPattern":"catch (e) { if (gqlErrorCode(e) === 'unsupported_subscription_plan') { filterOutPlanFromUI(plan); logBadPlanSource(plan); return; } throw e; }","preventionTips":["Keep a single purchasable-plans constant shared by pricing UI and API calls.","Validate plan at system boundaries (env, deep links, config) before it reaches service.select.","Regenerate client enums after backend plan changes."],"tags":["payment","subscription","enum","validation","plan"],"backgroundTag":"unsupported-enum-value","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}