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
- Add the missing 'plan@type:price_REDACTED_<priceId>' entry to the payment provider's subscriptionPlans configuration and restart the service
- Fix the client/request to use a configured (type, plan) combination, e.g. plan 'common' with type 'tier'
- Extend the constructor's mustHave list so misconfiguration fails at startup instead of at request time
- 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
- Keep the StripeSubscriptionPlans config in sync with the plan catalog in the model; add both in the same change
- Enforce all plans at provider startup by extending the constructor's mustHave list
- Validate requested (type, plan) pairs at the API boundary before reaching the provider
- Test each configured plan end-to-end after deployment
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
- No price configured 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/98c625128e49a1b8.
Report an issue: GitHub.