{"record":{"id":"9d08b03f3f6a5f35","repo":"Stirling-Tools/Stirling-PDF","slug":"quote-pdf-fetch-failed","errorCode":null,"errorMessage":"quote PDF fetch failed","messagePattern":"quote PDF fetch failed","errorType":"exception","errorClass":"StripeFunctionError","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/portal/billing/stripe.ts","lineNumber":478,"sourceCode":"/**\n * Fetch the Stripe-rendered quote PDF for a persisted quote, via the {@code create-payg-bundle-quote}\n * GET route (streams application/pdf). Returns a Blob the caller can object-URL for download.\n */\nexport async function fetchBundleQuotePdf(quoteId: number): Promise<Blob> {\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<Blob>(\n    `create-payg-bundle-quote?quote_id=${quoteId}`,\n    { method: \"GET\" },\n  );\n  if (error) {\n    throw new StripeFunctionError(error.message ?? \"quote PDF fetch failed\");\n  }\n  if (!(data instanceof Blob)) {\n    throw new StripeFunctionError(\"quote PDF response was not a file\");\n  }\n  return data;\n}\n\n/**\n * {@code VITE_STRIPE_PUBLISHABLE_KEY} — the Stripe pk used by embedded Checkout. Coalesces to \"\" when\n * unset so the declared `string` return type is honest (Vite substitutes `undefined` for a missing\n * env var); callers guard with a falsy check.\n */\nexport function getStripePublishableKey(): string {\n  return import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY ?? \"\";\n}\n\n// Process-wide memoized Stripe.js loader, shared by the embedded-checkout modals so the SDK promise\n// is created once rather than per-modal. loadStripe is dynamically imported so its chunk only loads","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/portal/billing/stripe.ts#L460-L496","documentation":"Fallback message in fetchBundleQuotePdf when the GET invocation of create-payg-bundle-quote returns an error whose .message is falsy. The PDF fetch failed at the edge function, but the error carried no human-readable text, so the helper substitutes a generic description.","triggerScenarios":"supabase.functions.invoke against the create-payg-bundle-quote GET route rejects with an error object lacking message (or with message === ''). This is the right-hand operand of `error.message ?? fallback`.","commonSituations":"Transport/CORS error on the GET route; edge function threw a non-Error value; the function returned an error status with an empty body; supabase-js version changed the error shape.","solutions":["Inspect the full StripeFunctionError / underlying error in dev tools for a code or cause, even when message is empty.","Check the create-payg-bundle-quote GET route logs in the Supabase dashboard.","Verify quoteId is valid and the GET route accepts ?quote_id=.","Confirm CORS and content-type handling on the GET route (it streams application/pdf)."],"exampleFix":"// before\nthrow new StripeFunctionError(error.message ?? \"quote PDF fetch failed\");\n\n// after — expose more of the underlying error\nconst e = error as { code?: string; hint?: string };\nthrow new StripeFunctionError(\n  error.message ?? `quote PDF fetch failed (code=${e.code ?? \"?\"})`,\n  e.code,\n);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isFunctionsError(e: unknown): e is { message?: string; code?: string } {\n  return typeof e === \"object\" && e !== null && \"message\" in e;\n}","tryCatchPattern":"try {\n  const pdf = await fetchBundleQuotePdf(quoteId);\n  downloadBlob(pdf);\n} catch (e) {\n  if (e instanceof StripeFunctionError) {\n    notifications.show({ message: `Could not download quote PDF: ${e.message}`, color: \"red\" });\n  }\n}","preventionTips":["Inspect the underlying FunctionsError in dev tools for code/cause when message is empty.","Confirm CORS + application/pdf streaming on the GET route.","Pin @supabase/supabase-js to avoid error-shape drift."],"tags":["billing","stripe","supabase","edge-function","pdf","network"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}