hcengineering/platform · error · Error

No price configured for plan: ${planKey}

Error message

No price configured for plan: ${planKey}

What it means

updateSubscriptionPlan resolves the Stripe price ID for the target plan by building the key `${newPlan}@tier` (subscription plan updates are always tier type) and indexing this.subscriptionPlans. If no price is configured for that key it throws this error before touching Stripe. Like error 810, this is a configuration gap between the requested plan and the payment service's subscriptionPlans config.

Source

Thrown at services/payment/pod-payment/src/providers/stripe/provider.ts:311

  async updateSubscriptionPlan (
    ctx: MeasureContext,
    subscriptionId: string,
    newPlan: string,
    workspaceUrl: string,
    accountUuid: string
  ): Promise<SubscriptionData | CheckoutResponse | null> {
    // Get the current subscription to check if it's free
    const currentSub = await this.stripe.getSubscription(ctx, subscriptionId)

    // Check if subscription is free by checking if the price amount is 0
    const price = currentSub.items.data[0]?.price
    const isFreeSubscription = price?.unit_amount === 0 || price === undefined

    // Get the Stripe price ID for the new plan (subscriptions updates are tier type)
    const planKey = getPlanKey(SubscriptionType.Tier, newPlan)
    const priceId = this.subscriptionPlans[planKey]
    if (priceId === undefined) {
      throw new Error(`No price configured for plan: ${planKey}`)
    }

    // If subscription is free, create a checkout instead of updating directly
    if (isFreeSubscription) {
      const successUrl = `${this.frontUrl}/workbench/${workspaceUrl}/setting/setting/billing/subscriptions?payment=success&checkout_id={CHECKOUT_SESSION_ID}`
      const cancelUrl = `${this.frontUrl}/workbench/${workspaceUrl}/setting/setting/billing/subscriptions?payment=canceled`

      const customerId = typeof currentSub.customer === 'string' ? currentSub.customer : currentSub.customer?.id
      const metadata = currentSub.metadata ?? {}

      const response = await this.stripe.createCheckout(ctx, {
        priceId,
        successUrl,
        cancelUrl,
        customerId,
        subscriptionId: currentSub.id,
        metadata: {
          workspaceUuid: metadata.workspaceUuid,

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Add the missing '<newPlan>@tier:price_xxx' entry to the subscriptionPlans config and restart the service
  2. Validate newPlan on the API/frontend against the configured plan list before calling updateSubscriptionPlan
  3. Sync the plan catalog (model) with the Stripe price config so unknown plans are rejected upstream with a clearer message
  4. Check for typos/case differences between the plan key sent and the one configured

Example fix

// before (client)
await updateSubscriptionPlan(ctx, workspaceId, SubscriptionType.Tier, 'premium') // no 'premium@tier' configured
// after
const configuredPlans = ['common', 'rare', 'epic', 'legendary']
if (!configuredPlans.includes(newPlan)) throw new ApiError(400, `Unknown plan: ${newPlan}`)
await updateSubscriptionPlan(ctx, workspaceId, SubscriptionType.Tier, newPlan)
Defensive patterns

Strategy: validation

Validate before calling

const planKey = `${newPlan}@tier`
if (!configuredPlanKeys.includes(planKey)) {
  throw new ApiError(400, `Unknown plan '${newPlan}'. Available: ${configuredPlanKeys.join(', ')}`)
}

Type guard

function isKnownPlan(plan: string, known: readonly string[]): plan is typeof known[number] {
  return (known as readonly string[]).includes(plan)
}

Try / catch

try {
  await paymentProvider.updateSubscriptionPlan(ctx, workspaceId, SubscriptionType.Tier, newPlan)
} catch (err) {
  if ((err as Error).message.startsWith('No price configured for plan:')) {
    throw new ApiError(400, `Cannot switch to plan '${newPlan}': it is not offered`) 
  }
  throw err
}

Prevention

When it happens

Trigger: Calling updateSubscriptionPlan with a newPlan value that has no '<plan>@tier:priceId' entry in the provider config (constructor only enforces common/rare/epic/legendary@tier); client requesting an unknown or renamed plan name during an upgrade/downgrade flow.

Common situations: New pricing tier added in the product but the payment service config was not updated; plan renamed on the frontend while the backend still uses the old key; user downgrading to a free/unknown plan name not present in the config.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/b3c5deebc22c9678. Report an issue: GitHub.