{"record":{"id":"8b94bca44e7512fd","repo":"hcengineering/platform","slug":"no-products-configured-for-plan-plankey","errorCode":null,"errorMessage":"No products configured for plan: ${planKey}","messagePattern":"No products configured for plan: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/payment/pod-payment/src/providers/polar/provider.ts","lineNumber":295,"sourceCode":"\n  async updateSubscriptionPlan (\n    ctx: MeasureContext,\n    subscriptionId: string,\n    newPlan: string,\n    workspaceUrl: string,\n    accountUuid: string\n  ): Promise<SubscriptionData | CheckoutResponse | null> {\n    // Get the current subscription to check if it's free\n    const currentSub = await this.polar.getSubscription(ctx, subscriptionId)\n\n    // Check if subscription is free by checking if it has a price with amountType === 'free'\n    const isFreeSubscription = currentSub.prices?.[0]?.amountType === 'free'\n\n    // Get the Polar product ID for the new plan (subscriptions updates are tier type)\n    const planKey = getPlanKey(SubscriptionType.Tier, newPlan)\n    const productIds = this.subscriptionPlans[planKey]\n    if (productIds === undefined || productIds.length === 0) {\n      throw new Error(`No products configured for plan: ${planKey}`)\n    }\n\n    // Use first product ID from the list (it should be the default fixed amount subscription plan)\n    const newProductId = productIds[0]\n\n    // If subscription is free, create a checkout instead of updating directly\n    if (isFreeSubscription) {\n      const successUrl = `${this.frontUrl}/workbench/${workspaceUrl}/setting/setting/billing/subscriptions?payment=success&checkout_id={CHECKOUT_ID}`\n      const returnUrl = `${this.frontUrl}/workbench/${workspaceUrl}/setting/setting/billing/subscriptions?payment=canceled`\n\n      const response = await this.polar.createCheckout(ctx, {\n        productIds: [newProductId],\n        successUrl,\n        returnUrl,\n        subscriptionId: currentSub.id,\n        externalCustomerId: accountUuid,\n        customerEmail: currentSub.customer?.email ?? undefined,\n        customerName: currentSub.customer?.name ?? undefined,","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/polar/provider.ts#L277-L313","documentation":"updateSubscriptionPlan resolves the new plan to Polar product IDs using getPlanKey(SubscriptionType.Tier, newPlan) and this.subscriptionPlans. If the key is missing or maps to an empty array, there is no Polar product to switch the subscription to, so it throws 'No products configured for plan: <planKey>'. This is a configuration gap in the Polar plan catalog for tier upgrades/downgrades.","triggerScenarios":"updateSubscriptionPlan called with newPlan whose tier key is absent from subscriptionPlans, or present but with productIds: [] — e.g. a newly advertised plan not yet given product IDs, or config parsed with an empty list.","commonSituations":"Plan added to billing UI without updating the payment pod's subscriptionPlans config; config file edited leaving 'products: []'; typo causing key mismatch; environment where only some plans are enabled.","solutions":["Populate subscriptionPlans[planKey] with valid Polar product IDs (non-empty array) and redeploy","Verify the incoming newPlan value matches a configured key (check the planKey in the message)","Cross-check product IDs exist in the Polar dashboard and are active","Add a startup validation that every advertised plan has at least one product ID"],"exampleFix":"// before\n'tier:newplan': []\n// after\n'tier:newplan': ['prod_newplan_default']","handlingStrategy":"validation","validationCode":"const planKey = getPlanKey(SubscriptionType.Tier, newPlan)\nconst ids = provider.subscriptionPlans?.[planKey]\nif (ids === undefined || ids.length === 0) {\n  throw new Error(`Cannot switch to plan ${newPlan}: no Polar products configured`)\n}","typeGuard":"function planHasProducts(provider: PolarProvider, planKey: string): boolean {\n  return Array.isArray(provider.subscriptionPlans?.[planKey]) && (provider.subscriptionPlans?.[planKey]?.length ?? 0) > 0\n}","tryCatchPattern":"try {\n  await provider.updateSubscriptionPlan(ctx, subscriptionId, newPlan)\n} catch (e) {\n  if (e.message.startsWith('No products configured for plan')) {\n    // reject plan change and notify ops to fix plan catalog\n  } else throw e\n}","preventionTips":["Startup-validate that every plan key in config has a non-empty product array","Sync plan catalog config whenever billing UI adds a plan","Guard against empty arrays in config parsing, not just undefined","Match newPlan values against a whitelist of configured tiers"],"tags":["configuration","payment","polar","plans"],"backgroundTag":"missing-plan-configuration","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}