Stirling-Tools/Stirling-PDF · error · StripeFunctionError

create-checkout-session returned neither client_secret nor U

Error message

create-checkout-session returned neither client_secret nor URL

What it means

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.

Source

Thrown at frontend/editor/src/portal/billing/stripe.ts:295

    // redirect on completion. A redirect would reload the page, skip that finalize step, and
    // make Stripe ignore onComplete entirely (console warns "redirect_on_completion: always").
    redirect_on_completion: "never",
    ...(req.billingOwnerEmail
      ? { billing_owner_email: req.billingOwnerEmail }
      : {}),
  });
  if (!res.success) {
    throw new StripeFunctionError(
      res.error ?? "create-checkout-session failed",
    );
  }
  const alreadySubscribed = Boolean(res.already_subscribed);
  const redirectUrl = alreadySubscribed
    ? (res.portal_url ?? null)
    : (res.url ?? null);
  const clientSecret = alreadySubscribed ? null : (res.client_secret ?? null);
  if (!clientSecret && !redirectUrl) {
    throw new StripeFunctionError(
      "create-checkout-session returned neither client_secret nor URL",
    );
  }
  return {
    clientSecret,
    redirectUrl,
    alreadySubscribed,
  };
}

/** Result of {@link createBundleStripeQuote} — the Stripe-issued quote handles. */
export interface BundleStripeQuote {
  stripeQuoteId: string;
  stripeQuoteNumber: string | null;
}

interface BundleStripeQuoteRequest {
  teamId: number;

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Check the edge function: confirm every success path sets client_secret (embedded) or url/portal_url (redirect).
  2. Verify the Stripe Checkout Session was actually created and that client_secret is read from session.client_secret.
  3. For the already_subscribed branch, ensure billing portal creation populates portal_url.
  4. Catch and toast at the call site; this is an upstream contract bug, not a user error.

Example fix

// edge function — before
return json({ success: true });

// after
return json({
  success: true,
  client_secret: session.client_secret,
  url: session.url,
});
Defensive patterns

Strategy: validation

Validate before calling

function isCheckoutResponseWellFormed(res: CheckoutResponse): boolean {
  return Boolean(res.client_secret) || Boolean(res.url) || Boolean(res.portal_url);
}
// (defensive — the error indicates an upstream contract bug, but this guards UI code)
if (!isCheckoutResponseWellFormed(res)) {
  throw new Error("Malformed checkout response from server.");
}

Type guard

function hasCheckoutHandle(res: CheckoutResponse): res is CheckoutResponse & { client_secret?: string; url?: string; portal_url?: string } {
  return Boolean(res.client_secret ?? res.url ?? res.portal_url);
}

Try / catch

try {
  const r = await createCheckoutSession(req);
  // start embedded or redirect
} catch (e) {
  if (e instanceof StripeFunctionError && /neither/i.test(e.message)) {
    notifications.show({ message: "Checkout session could not be started. Try again.", color: "red" });
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/a2e1c77e7cc5bb84. Report an issue: GitHub.