{"record":{"id":"427fcbbfc89f8d63","repo":"Stirling-Tools/Stirling-PDF","slug":"create-customer-portal-session-failed","errorCode":null,"errorMessage":"create-customer-portal-session failed","messagePattern":"create-customer-portal-session failed","errorType":"exception","errorClass":"StripeFunctionError","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/portal/billing/stripe.ts","lineNumber":520,"sourceCode":"  }\n  return stripePromise;\n}\n\n/**\n * Mint a Stripe Customer Portal session. The admin can manage their card,\n * view invoices, and cancel from Stripe's hosted UI. The edge function returns\n * 404 with {@code team_not_subscribed} if called for a free team — surfaced\n * here as a StripeFunctionError the caller can toast.\n */\nexport async function createPortalSession(\n  req: PortalSessionRequest,\n): Promise<string> {\n  const res = await invoke<PortalResponse>(\"create-customer-portal-session\", {\n    team_id: req.teamId,\n    return_url: req.returnUrl,\n  });\n  if (!res.success || !res.url) {\n    throw new StripeFunctionError(\n      res.error ?? \"create-customer-portal-session failed\",\n    );\n  }\n  return res.url;\n}\n","sourceCodeStart":502,"sourceCodeEnd":526,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/portal/billing/stripe.ts#L502-L526","documentation":"Fallback thrown by createPortalSession when the create-customer-portal-session edge function returns success === false OR omits url. The Stripe customer portal must return a hosted URL to redirect to; an absent url is a hard failure even if success was true. The comment notes a typical cause is 404 team_not_subscribed for free teams.","triggerScenarios":"create-customer-portal-session responds success:false (with res.error, or this fallback if omitted), or success:true without url. Most common for a team with no active subscription (the function returns team_not_subscribed) or when Stripe portal-session creation fails.","commonSituations":"Clicking \"manage subscription\" / \"view invoices\" for a free team that has no Stripe customer; Stripe-side failure creating the portal session; missing return_url; function deploy dropped the url field.","solutions":["Only show the portal/billing-manage control when the team is subscribed (hide for free teams to avoid team_not_subscribed).","Read res.error / the thrown message and toast specifically for team_not_subscribed vs a real Stripe error.","Verify returnUrl is supplied and is an allowed domain in the Stripe portal config.","Ensure the function returns url on success and a stable error code on failure."],"exampleFix":"// before\n<Button onClick={() => createPortalSession({ teamId, returnUrl })} />\n\n// after — gate on subscription, toast on expected failures\nif (!team.isSubscribed) {\n  notifications.show({ message: \"This team has no subscription to manage.\", color: \"orange\" });\n  return;\n}\ntry {\n  const url = await createPortalSession({ teamId, returnUrl });\n  window.location.href = url;\n} catch (e) {\n  if (e instanceof StripeFunctionError && /team_not_subscribed/i.test(e.message)) {\n    notifications.show({ message: \"No subscription found.\", color: \"orange\" });\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"function teamHasSubscription(team: { isSubscribed?: boolean }): boolean {\n  return team.isSubscribed === true;\n}\n\nif (!teamHasSubscription(team)) {\n  notifications.show({ message: \"This team has no subscription to manage.\", color: \"orange\" });\n  return;\n}","typeGuard":"function isPortalResponseOk(res: PortalResponse | null): res is PortalResponse & { success: true; url: string } {\n  return res != null && res.success === true && typeof res.url === \"string\" && res.url.length > 0;\n}","tryCatchPattern":"try {\n  const url = await createPortalSession({ teamId, returnUrl });\n  window.location.href = url;\n} catch (e) {\n  if (e instanceof StripeFunctionError && /team_not_subscribed/i.test(e.message)) {\n    notifications.show({ message: \"No subscription found for this team.\", color: \"orange\" });\n  } else throw e;\n}","preventionTips":["Only show the manage-subscription control for subscribed teams.","Pass a valid returnUrl that is an allowed domain in the Stripe portal config.","Branch on the team_not_subscribed error code and toast gracefully instead of erroring."],"tags":["billing","stripe","supabase","edge-function","portal","contract"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}