{"record":{"id":"bc4ec6580cd1405d","repo":"Stirling-Tools/Stirling-PDF","slug":"unconfigured","errorCode":"unconfigured","errorMessage":"SaaS Supabase not configured — set VITE_SUPABASE_URL.","messagePattern":"SaaS Supabase not configured — set VITE_SUPABASE_URL\\.","errorType":"exception","errorClass":"StripeFunctionError","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/portal/billing/stripe.ts","lineNumber":73,"sourceCode":"  portal_url?: string;\n  already_subscribed?: boolean;\n  error?: string;\n}\n\ninterface PortalResponse {\n  success: boolean;\n  url?: string;\n  error?: string;\n}\n\nasync function invoke<T>(\n  name: string,\n  body: Record<string, unknown>,\n): Promise<T> {\n  ensureSaasSupabase();\n  const supabase = getSupabaseClient();\n  if (!supabase) {\n    throw new StripeFunctionError(\n      \"SaaS Supabase not configured — set VITE_SUPABASE_URL.\",\n      \"unconfigured\",\n    );\n  }\n  const { data, error } = await supabase.functions.invoke<T>(name, { body });\n  if (error) {\n    throw new StripeFunctionError(\n      error.message ?? `Edge function ${name} failed`,\n    );\n  }\n  if (data == null) {\n    throw new StripeFunctionError(`Edge function ${name} returned no data`);\n  }\n  return data;\n}\n\n/** Call a SECURITY DEFINER public.* RPC with the admin's JWT (same client as {@link invoke}). */\nasync function rpc<T>(fn: string, args: Record<string, unknown>): Promise<T> {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/portal/billing/stripe.ts#L55-L91","documentation":"Thrown by the invoke() helper used by all Stripe edge-function calls. ensureSaasSupabase() runs first (asserting SaaS mode), then getSupabaseClient() is expected to return a configured client. If it returns null — meaning VITE_SUPABASE_URL was never provided to the build — the helper throws a StripeFunctionError with code \"unconfigured\" before attempting any network call.","triggerScenarios":"Any Stripe billing call (checkout, quote, invoice, portal) is made in an environment where the Supabase client was never instantiated because VITE_SUPABASE_URL is unset/empty. The client singleton is null, so invoke refuses to proceed.","commonSituations":"Dev started the frontend in core/proprietary/desktop mode instead of saas; .env.saas was not layered (Vite only loaded .env); the env var was renamed/removed; CI built the portal without the SaaS secrets. Note: ensureSaasSupabase usually throws first if you are not in SaaS mode at all, so reaching this null-check typically means you ARE in SaaS mode but the URL env var is missing.","solutions":["Set VITE_SUPABASE_URL (and VITE_SUPABASE_ANON_KEY) in the SaaS env file and restart the dev server so Vite re-reads env.","Run in SaaS mode: `task frontend:dev` with the saas flavor / MODE=saas, or `task frontend:prepare MODE=saas` to create .env.saas.local.","Confirm the variable is committed in frontend/editor/.env.saas (not only in an uncommitted .local that the build does not load).","If intentional (non-SaaS build), gate the billing UI so these functions are never invoked outside SaaS."],"exampleFix":"# before\nVITE_SUPABASE_URL=\n\n# after (frontend/editor/.env.saas)\nVITE_SUPABASE_URL=https://your-project.supabase.co\nVITE_SUPABASE_ANON_KEY=eyJ...","handlingStrategy":"validation","validationCode":"function isSaasSupabaseConfigured(): boolean {\n  return Boolean(\n    getSupabaseClient() &&\n    import.meta.env.VITE_SUPABASE_URL,\n  );\n}\n\nif (!isSaasSupabaseConfigured()) {\n  notifications.show({ message: \"Billing requires SaaS Supabase config.\", color: \"red\" });\n  return;\n}","typeGuard":"function hasSupabaseClient(): boolean {\n  return getSupabaseClient() != null;\n}","tryCatchPattern":"try {\n  await createCheckoutSession(req);\n} catch (e) {\n  if (e instanceof StripeFunctionError && e.code === \"unconfigured\") {\n    notifications.show({ message: \"Billing is not configured for this environment.\", color: \"red\" });\n  } else throw e;\n}","preventionTips":["Commit VITE_SUPABASE_URL + VITE_SUPABASE_ANON_KEY in frontend/editor/.env.saas.","Run the portal in SaaS mode and verify getSupabaseClient() is non-null before opening billing.","Hide all billing entry points when getSupabaseClient() returns null."],"tags":["billing","stripe","supabase","config","env","saas"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}