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

  1. Read ctx.error logs for the wrapped provider error to distinguish invalid-plan vs auth vs network.
  2. Verify the plan name maps to an existing product/price in the payment provider.
  3. Check the subscription status in the provider; resume/cure past_due or canceled subscriptions before changing plan.
  4. 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

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


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