hcengineering/platform · error

Missing priceId for plan: ${planKey}

Error message

Missing priceId for plan: ${planKey}

What it means

StripeProvider.createSubscription looks up a Stripe price ID in the this.subscriptionPlans map (parsed from the provider's semicolon-separated 'plan@type:priceId' config) using the key `${plan}@${type}` from the subscribe request. When no price ID is registered for that key, the provider cannot create a Stripe Checkout session and throws this error. It is a server configuration/request mismatch, not a Stripe API failure.

Source

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

      if (this.subscriptionPlans[plan] === undefined) {
        throw new Error(`Missing plan in config: ${plan}`)
      }
    }
  }

  async createSubscription (
    ctx: MeasureContext,
    request: SubscribeRequest,
    workspaceUuid: WorkspaceUuid,
    workspaceUrl: string,
    accountUuid: string
  ): Promise<CheckoutResponse> {
    ctx.info('Creating Stripe subscription', { type: request.type, plan: request.plan })

    const planKey = getPlanKey(request.type, request.plan)
    const priceId = this.subscriptionPlans[planKey]
    if (priceId === undefined) {
      throw new Error(`Missing priceId for plan: ${planKey}`)
    }
    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 response = await this.stripe.createCheckout(ctx, {
      priceId,
      successUrl,
      cancelUrl,
      customerEmail: request.customerEmail,
      customerName: request.customerName,
      metadata: {
        workspaceUuid,
        subscriptionType: request.type,
        subscriptionPlan: request.plan,
        accountUuid
      }
    })

    return {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Add the missing 'plan@type:price_REDACTED_<priceId>' entry to the payment provider's subscriptionPlans configuration and restart the service
  2. Fix the client/request to use a configured (type, plan) combination, e.g. plan 'common' with type 'tier'
  3. Extend the constructor's mustHave list so misconfiguration fails at startup instead of at request time
  4. Log or return the available planKeys in the error to ease diagnosis

Example fix

// config before
StripeSubscriptionPlans=common@tier:price_abc;rare@tier:price_def;epic@tier:price_ghi;legendary@tier:price_jkl
// after (adding the requested plan)
StripeSubscriptionPlans=common@tier:price_abc;rare@tier:price_def;epic@tier:price_ghi;legendary@tier:price_jkl;premium@tier:price_mno
Defensive patterns

Strategy: validation

Validate before calling

const planKey = `${request.plan}@${request.type}`
// parse config the same way the provider does
const configured = new Map(config.StripeSubscriptionPlans.split(';').map(p => {
  const [key, priceId] = p.split(':')
  return [key, priceId]
}))
if (!configured.has(planKey)) {
  throw new Error(`Plan not configured on payment provider: ${planKey}`)
}

Type guard

function isConfiguredPlan(
  plans: Record<string, string>,
  type: string,
  plan: string
): plans is Record<string, string> & { [k: string]: string } {
  return plans[`${plan}@${type}`] !== undefined
}

Try / catch

try {
  const checkout = await paymentProvider.createSubscription(ctx, request, wsUuid, wsUrl, accountUuid)
  return checkout
} catch (err) {
  if ((err as Error).message.startsWith('Missing priceId for plan:')) {
    throw new ApiError(400, `Plan '${request.plan}' (${request.type}) is not available for purchase`)
  }
  throw err
}

Prevention

When it happens

Trigger: Calling createSubscription (via the payment service subscribe endpoint) with a request whose (request.type, request.plan) combination produces a planKey like 'premium@tier' or 'common@yearly' that is absent from the subscriptionPlans config string. The constructor only hard-validates common/rare/epic/legendary@tier, so any other type (e.g. one-off/yearly) or plan name passes startup but fails here.

Common situations: Deploying with an incomplete StripeSubscriptionPlans config env value; typo in the plan name sent by the client; requesting a subscription type other than 'tier' (e.g. a yearly or one-off type) that was never configured; adding a new plan to the model without updating the payment service config.

Related errors


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