{"record":{"id":"90db46153d310c1a","repo":"hcengineering/platform","slug":"failed-to-update-subscription-at-provider","errorCode":null,"errorMessage":"Failed to update subscription at provider","messagePattern":"Failed to update subscription at provider","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"services/payment/pod-payment/src/server.ts","lineNumber":434,"sourceCode":"          if (accountUuid == null) {\n            res.status(400).json({ error: 'Missing account, cannot update plan' })\n            return\n          }\n\n          let updateResult: SubscriptionData | CheckoutResponse | null\n\n          try {\n            // Update via provider using the provider's subscription ID\n            updateResult = await provider.updateSubscriptionPlan(\n              ctx,\n              subscription.providerSubscriptionId,\n              plan,\n              loginInfo.workspaceUrl,\n              accountUuid\n            )\n          } catch (err) {\n            ctx.error('Failed to update subscription at provider', { err })\n            res.status(500).json({ error: 'Failed to update subscription at provider' })\n            return\n          }\n\n          if (updateResult === null) {\n            res.status(404).json({ error: 'Failed to update subscription at provider' })\n            return\n          }\n\n          // Check if it's a CheckoutResponse (free-to-paid upgrade)\n          if ('checkoutUrl' in updateResult) {\n            // Return checkout response for free-to-paid upgrades\n            res.status(200).json(updateResult)\n            return\n          }\n\n          // It's a SubscriptionData - update was direct\n          // Upsert the updated subscription into our database\n          await accountClient.upsertSubscription(updateResult)","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/server.ts#L416-L452","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait api.post(`/subscriptions/${id}/updatePlan`, { plan: 'Pro' }) // wrong casing\n// after: use exact provider plan identifier\nawait api.post(`/subscriptions/${id}/updatePlan`, { plan: 'pro' })","handlingStrategy":"try-catch","validationCode":"const validPlans = await provider.listPlans()\nif (!validPlans.includes(plan)) throw new ValidationError(`Unknown plan '${plan}'; valid: ${validPlans.join(', ')}`)\nconst sub = await getSubscription(id)\nif (!['active', 'trialing'].includes(sub.status)) throw new ValidationError(`Subscription status '${sub.status}' cannot change plan`)","typeGuard":"function isUpdatableSubscription(sub: { status: string }): sub is { status: 'active' | 'trialing' } {\n  return sub.status === 'active' || sub.status === 'trialing'\n}","tryCatchPattern":"try {\n  return await updatePlan(id, plan)\n} catch (err) {\n  logger.error({ err }, 'provider plan update failed')\n  if (isTransient(err)) {\n    await sleep(backoff(attempt))\n    return updatePlan(id, plan)\n  }\n  if (isAuthError(err)) await rotateProviderCredentials()\n  throw err\n}","preventionTips":["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."],"tags":["payment","provider-api","http-500","subscription"],"backgroundTag":"payment-provider-api-failure","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}