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
- Add the missing '<newPlan>@tier:price_xxx' entry to the subscriptionPlans config and restart the service
- Validate newPlan on the API/frontend against the configured plan list before calling updateSubscriptionPlan
- Sync the plan catalog (model) with the Stripe price config so unknown plans are rejected upstream with a clearer message
- 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
- Expose the configured plan list to the client (e.g. via a plans endpoint) and populate plan pickers from it
- Never hardcode plan names in the frontend; use the shared plan catalog
- Add new tiers to Stripe, config, and model in one deployment step
- Validate plan names server-side before calling the provider
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
- Missing priceId for plan: ${planKey}
- Failed to cancel subscription ${providerSubscriptionId}
- Failed to uncancel subscription ${providerSubscriptionId}
- Payment provider is not configured. Please provide payment p
- Missing config for attributes: ${missingEnv.join(', ')}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/b3c5deebc22c9678.
Report an issue: GitHub.