{"record":{"id":"b0ea62b1d9397341","repo":"different-ai/openwork","slug":"only-workspace-admins-can-start-seat-checkout","errorCode":null,"errorMessage":"Only workspace admins can start seat checkout.","messagePattern":"Only workspace admins can start seat checkout\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx","lineNumber":620,"sourceCode":"\n      if (!response.ok) {\n        const paymentRequiredError = getOrgPaymentRequiredError(payload);\n        if (paymentRequiredError) {\n          throw paymentRequiredError;\n        }\n\n        const limitError = getOrgLimitError(payload);\n        if (limitError) {\n          throw limitError;\n        }\n        throw getRequestError(payload, response, `Failed to invite member (${response.status}).`);\n      }\n    });\n  }\n\n  async function startSeatCheckout() {\n    if (!getCurrentAccess().canStartSeatCheckout) {\n      throw new Error(\"Only workspace admins can start seat checkout.\");\n    }\n\n    setMutationBusy(\"seat-checkout\");\n    setOrgError(null);\n    try {\n      await runReauthableAction(\"seat-checkout\", async () => {\n        ensureActiveOrganizationSelected();\n        const { response, payload } = await requestJson(\n          \"/v1/billing/stripe/checkout\",\n          {\n            method: \"POST\",\n            body: JSON.stringify({ type: \"seat\" }),\n          },\n          12000,\n        );\n\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Seat billing checkout failed (${response.status}).`);","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_providers/org-dashboard-provider.tsx#L602-L638","documentation":"startSeatCheckout gates the seat-billing checkout flow behind access.canStartSeatCheckout. Only workspace admins (owners/super-admins per getOrgAccessFlags) may begin a seat checkout; any other role throws \"Only workspace admins can start seat checkout.\" before any billing request is made. This is a client-side RBAC check, consistent with server enforcement.","triggerScenarios":"Invoking startSeatCheckout() (e.g. from a \"Buy more seats\" button) when the current member's role is not admin-level, so canStartSeatCheckout is false.","commonSituations":"A non-admin member clicks a seat-purchase CTA that should have been hidden; billing UI rendered based on stale access flags after a role change; switching to an org where the user is only a member.","solutions":["Have a workspace admin perform the seat checkout.","Confirm your role in the active organization and switch to an org where you are admin if applicable.","Reload the dashboard to refresh orgContext/access flags if your role was recently promoted.","Conditionally render the seat-checkout CTA on canStartSeatCheckout."],"exampleFix":"// before\n<button onClick={() => startSeatCheckout()}>Buy seats</button>\n\n// after\n{access.canStartSeatCheckout && (\n  <button onClick={() => startSeatCheckout()}>Buy seats</button>\n)}","handlingStrategy":"validation","validationCode":"if (!access.canStartSeatCheckout) return; // gate the CTA before calling startSeatCheckout()","typeGuard":null,"tryCatchPattern":"try {\n  await startSeatCheckout();\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"start seat checkout\")) {\n    showNotice(\"Contact a workspace admin to purchase seats.\");\n  } else throw e;\n}","preventionTips":["Show the seat-checkout CTA only for canStartSeatCheckout.","Re-fetch org context on org switch so flags reflect the active org.","Document that billing actions are admin-only."],"tags":["authorization","rbac","billing"],"backgroundTag":"insufficient-permissions","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}