hcengineering/platform · error
Failed to update subscription at provider
Error message
Failed to update subscription at provider
What it means
HTTP 500 returned when provider.updateSubscriptionPlan(...) throws (services/payment/pod-payment/src/server.ts:432-435). The provider rejected or failed the plan change — invalid plan name for the product, provider API error, or network failure. The DB is not upserted and the client gets the generic message; the real cause is logged via ctx.error.
Source
Thrown at services/payment/pod-payment/src/server.ts:434
if (accountUuid == null) {
res.status(400).json({ error: 'Missing account, cannot update plan' })
return
}
let updateResult: SubscriptionData | CheckoutResponse | null
try {
// Update via provider using the provider's subscription ID
updateResult = await provider.updateSubscriptionPlan(
ctx,
subscription.providerSubscriptionId,
plan,
loginInfo.workspaceUrl,
accountUuid
)
} catch (err) {
ctx.error('Failed to update subscription at provider', { err })
res.status(500).json({ error: 'Failed to update subscription at provider' })
return
}
if (updateResult === null) {
res.status(404).json({ error: 'Failed to update subscription at provider' })
return
}
// Check if it's a CheckoutResponse (free-to-paid upgrade)
if ('checkoutUrl' in updateResult) {
// Return checkout response for free-to-paid upgrades
res.status(200).json(updateResult)
return
}
// It's a SubscriptionData - update was direct
// Upsert the updated subscription into our database
await accountClient.upsertSubscription(updateResult)View on GitHub (pinned to 63e28dc964)
Solutions
- Read ctx.error logs for the wrapped provider error to distinguish invalid-plan vs auth vs network.
- Verify the plan name maps to an existing product/price in the payment provider.
- Check the subscription status in the provider; resume/cure past_due or canceled subscriptions before changing plan.
- Validate provider API credentials, then retry with backoff if the error was transient.
Example fix
// before
await api.post(`/subscriptions/${id}/updatePlan`, { plan: 'Pro' }) // wrong casing
// after: use exact provider plan identifier
await api.post(`/subscriptions/${id}/updatePlan`, { plan: 'pro' }) Defensive patterns
Strategy: try-catch
Validate before calling
const validPlans = await provider.listPlans()
if (!validPlans.includes(plan)) throw new ValidationError(`Unknown plan '${plan}'; valid: ${validPlans.join(', ')}`)
const sub = await getSubscription(id)
if (!['active', 'trialing'].includes(sub.status)) throw new ValidationError(`Subscription status '${sub.status}' cannot change plan`) Type guard
function isUpdatableSubscription(sub: { status: string }): sub is { status: 'active' | 'trialing' } {
return sub.status === 'active' || sub.status === 'trialing'
} Try / catch
try {
return await updatePlan(id, plan)
} catch (err) {
logger.error({ err }, 'provider plan update failed')
if (isTransient(err)) {
await sleep(backoff(attempt))
return updatePlan(id, plan)
}
if (isAuthError(err)) await rotateProviderCredentials()
throw err
} Prevention
- Keep plan names in sync with the provider's product/price catalog; fetch them at runtime when possible.
- Check subscription status (past_due/canceled) before attempting plan changes.
- Monitor provider API key expiry and rotate on schedule.
- Retry only transient provider failures with exponential backoff.
When it happens
Trigger: Calling updatePlan where the provider call throws: plan string not matching a provider product/price, provider auth/permission failure, subscription in a non-updatable state (past_due, canceled), or a provider/network outage.
Common situations: Plan renamed in the provider dashboard so the price lookup misses; Polar API key expired/rotated; trying to change plan on a canceled or unpaid subscription; rate limiting or downtime at the provider.
Related errors
- platform.status.InternalServerError
- Payment service URL not specified
- Authentication token not specified
- Network error: ${String(err)}
- ${error.error ?? text}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/90db46153d310c1a.
Report an issue: GitHub.