{"record":{"id":"1b1b23e791ed0d5e","repo":"hcengineering/platform","slug":"missing-or-invalid-field-plan","errorCode":null,"errorMessage":"Missing or invalid field: plan","messagePattern":"Missing or invalid field: plan","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"services/payment/pod-payment/src/server.ts","lineNumber":398,"sourceCode":"    withToken,\n    withLoginInfo,\n    withOwner,\n    (req: RequestWithAuth, res: Response) => {\n      if (provider === undefined) {\n        res.status(503).json({ error: 'Payment provider is not configured' })\n        return\n      }\n\n      void handleRequest(\n        ctx,\n        'update-plan',\n        async (ctx) => {\n          const subscriptionId = req.params.subscriptionId\n          const { plan } = req.body\n          const loginInfo = req.loginInfo as WorkspaceLoginInfo\n\n          if (plan === undefined || typeof plan !== 'string') {\n            res.status(400).json({ error: 'Missing or invalid field: plan' })\n            return\n          }\n\n          if (loginInfo?.workspaceUrl === undefined) {\n            res.status(401).json({ error: 'Missing workspace url in login info' })\n            return\n          }\n\n          // Get subscription from our database using internal ID\n          const subscription = await accountClient.getSubscriptionById(subscriptionId)\n\n          if (subscription === undefined || subscription === null) {\n            res.status(404).json({ error: 'Subscription not found' })\n            return\n          }\n\n          const accountUuid = subscription.accountUuid ?? req.token?.account\n          if (accountUuid == null) {","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/server.ts#L380-L416","documentation":"HTTP 400 returned by POST /api/v1/subscriptions/:subscriptionId/updatePlan when req.body.plan is missing or not a string (services/payment/pod-payment/src/server.ts:397-400). The handler destructures `{ plan }` from the JSON body and requires a string plan name such as 'common', 'rare', 'epic', or 'legendary'. This is input validation rejecting the request before any provider or DB calls.","triggerScenarios":"POST .../updatePlan with no JSON body, body without a `plan` key, plan sent as a non-string (number, object, null), or request missing Content-Type: application/json so Express leaves body.plan undefined.","commonSituations":"Client forgot the JSON body or used form encoding; plan identifier renamed/typo'd on the client; sending plan id (number) instead of plan name (string); middleware/json parsing not configured on a custom client.","solutions":["Send a JSON body containing a string plan: { \"plan\": \"pro\" } with Content-Type: application/json.","Validate the plan field on the client before calling (typeof plan === 'string').","Confirm the plan name matches one of the valid plan identifiers ('common', 'rare', 'epic', 'legendary').","If using a fetch/axios wrapper, ensure JSON.stringify is used and the content-type header is set."],"exampleFix":"// before\nawait fetch(url, { method: 'POST', body: { plan: 2 } })\n// after\nawait fetch(url, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ plan: 'epic' })\n})","handlingStrategy":"validation","validationCode":"function validateUpdatePlanBody(body: unknown): { plan: string } {\n  const { plan } = (body ?? {}) as { plan?: unknown }\n  if (typeof plan !== 'string' || plan.length === 0) throw new ValidationError('plan must be a non-empty string')\n  return { plan }\n}","typeGuard":"function hasValidPlan(body: unknown): body is { plan: string } {\n  return typeof body === 'object' && body !== null && 'plan' in body && typeof (body as { plan: unknown }).plan === 'string'\n}","tryCatchPattern":"try {\n  const res = await fetch(`${base}/api/v1/subscriptions/${id}/updatePlan`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', ...auth },\n    body: JSON.stringify({ plan })\n  })\n  if (res.status === 400) {\n    const { error } = await res.json()\n    throw new ClientError(error ?? 'invalid plan field')\n  }\n  return await res.json()\n} catch (err) {\n  logger.warn({ err }, 'updatePlan rejected')\n  throw err\n}","preventionTips":["Always send JSON with Content-Type: application/json.","Validate request bodies with a schema (zod/ajv) on both client and server.","Keep a single shared constant list of valid plan names.","Add client-side unit tests for the plan-update payload."],"tags":["http-400","validation","request-body","subscription"],"backgroundTag":"schema-validation-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}