{"record":{"id":"a2e1c77e7cc5bb84","repo":"Stirling-Tools/Stirling-PDF","slug":"create-checkout-session-returned-neither-client-se","errorCode":null,"errorMessage":"create-checkout-session returned neither client_secret nor URL","messagePattern":"create-checkout-session returned neither client_secret nor URL","errorType":"exception","errorClass":"StripeFunctionError","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/portal/billing/stripe.ts","lineNumber":295,"sourceCode":"    // redirect on completion. A redirect would reload the page, skip that finalize step, and\n    // make Stripe ignore onComplete entirely (console warns \"redirect_on_completion: always\").\n    redirect_on_completion: \"never\",\n    ...(req.billingOwnerEmail\n      ? { billing_owner_email: req.billingOwnerEmail }\n      : {}),\n  });\n  if (!res.success) {\n    throw new StripeFunctionError(\n      res.error ?? \"create-checkout-session failed\",\n    );\n  }\n  const alreadySubscribed = Boolean(res.already_subscribed);\n  const redirectUrl = alreadySubscribed\n    ? (res.portal_url ?? null)\n    : (res.url ?? null);\n  const clientSecret = alreadySubscribed ? null : (res.client_secret ?? null);\n  if (!clientSecret && !redirectUrl) {\n    throw new StripeFunctionError(\n      \"create-checkout-session returned neither client_secret nor URL\",\n    );\n  }\n  return {\n    clientSecret,\n    redirectUrl,\n    alreadySubscribed,\n  };\n}\n\n/** Result of {@link createBundleStripeQuote} — the Stripe-issued quote handles. */\nexport interface BundleStripeQuote {\n  stripeQuoteId: string;\n  stripeQuoteNumber: string | null;\n}\n\ninterface BundleStripeQuoteRequest {\n  teamId: number;","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/portal/billing/stripe.ts#L277-L313","documentation":"Thrown by createCheckoutSession when the edge function reports success but provides neither an embedded client_secret nor a redirect URL. The function is expected to return at least one of client_secret (embedded Checkout) or url/portal_url (redirect), and a success response missing all of them is treated as malformed.","triggerScenarios":"create-checkout-session returns { success: true } but client_secret, url, and portal_url are all absent/null. Also fires when already_subscribed is true but portal_url was not provided.","commonSituations":"An edge function deploy returned success but forgot to include the Checkout handle; a Stripe Session was created but its client_secret/url weren't serialized; the already-subscribed branch didn't populate portal_url.","solutions":["Check the edge function: confirm every success path sets client_secret (embedded) or url/portal_url (redirect).","Verify the Stripe Checkout Session was actually created and that client_secret is read from session.client_secret.","For the already_subscribed branch, ensure billing portal creation populates portal_url.","Catch and toast at the call site; this is an upstream contract bug, not a user error."],"exampleFix":"// edge function — before\nreturn json({ success: true });\n\n// after\nreturn json({\n  success: true,\n  client_secret: session.client_secret,\n  url: session.url,\n});","handlingStrategy":"validation","validationCode":"function isCheckoutResponseWellFormed(res: CheckoutResponse): boolean {\n  return Boolean(res.client_secret) || Boolean(res.url) || Boolean(res.portal_url);\n}\n// (defensive — the error indicates an upstream contract bug, but this guards UI code)\nif (!isCheckoutResponseWellFormed(res)) {\n  throw new Error(\"Malformed checkout response from server.\");\n}","typeGuard":"function hasCheckoutHandle(res: CheckoutResponse): res is CheckoutResponse & { client_secret?: string; url?: string; portal_url?: string } {\n  return Boolean(res.client_secret ?? res.url ?? res.portal_url);\n}","tryCatchPattern":"try {\n  const r = await createCheckoutSession(req);\n  // start embedded or redirect\n} catch (e) {\n  if (e instanceof StripeFunctionError && /neither/i.test(e.message)) {\n    notifications.show({ message: \"Checkout session could not be started. Try again.\", color: \"red\" });\n  } else throw e;\n}","preventionTips":["Ensure every success path in create-checkout-session sets client_secret or url/portal_url.","Add a server-side contract test asserting the checkout response always carries a handle.","Treat this error as an upstream bug — file an issue against the edge function."],"tags":["billing","stripe","supabase","edge-function","checkout","contract"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}