Stirling-Tools/Stirling-PDF · error · Error

Edge function returned no client_secret

Error message

Edge function returned no client_secret

What it means

Thrown in StripeCheckoutPanel.tsx as a component-level defensive guard after createCheckoutSession() returns. If the session object has no url (hosted fallback) and no clientSecret, the checkout cannot proceed. This is typically unreachable because billing.ts createCheckoutSession() already throws this same error — but if the billing seam is shadowed by a platform override (desktop/cloud) that returns an unexpected shape, this guard catches it. The error is caught by the component's own try/catch and surfaced as setError(msg).

Source

Thrown at frontend/editor/src/cloud/components/shared/config/configSections/StripeCheckoutPanel.tsx:199

        // return URL (browser origin on web, deep link on desktop).
        const session = await createCheckoutSession({
          teamId,
          currency,
          // Maps to Stripe's customer_email when the team has no Stripe
          // customer yet — prefills + locks the email field in Checkout. Teams
          // with an existing customer get the email locked from the customer
          // record instead; this field is ignored for them.
          billingOwnerEmail: billingEmail,
        });
        if (cancelled) return;
        // Hosted-url fallback: no embedded iframe, hand the URL to the system
        // browser. The deep-link / origin return URL brings the user back.
        if (session.url && !session.clientSecret) {
          await openExternal(session.url);
          return;
        }
        if (!session.clientSecret) {
          throw new Error("Edge function returned no client_secret");
        }
        setClientSecret(session.clientSecret);
        setIsMock(
          Boolean(session.mock) || session.clientSecret.startsWith("cs_mock_"),
        );
      } catch (e: unknown) {
        if (cancelled) return;
        const msg =
          e instanceof Error
            ? e.message
            : tRef.current(
                "payg.checkout.error.startFailed",
                "Couldn't start checkout session",
              );
        setError(msg);
        onErrorRef.current?.(msg);
      } finally {
        if (!cancelled) setLoading(false);

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. If using a platform override of createCheckoutSession, ensure it always returns either { clientSecret, mock } or { url }
  2. Verify the @app/* billing seam resolves to the correct implementation for the current build flavor
  3. Check the create-checkout-session edge function logs if the real billing.ts implementation is in use
  4. Ensure STRIPE_SECRET_KEY is configured in the edge function

Example fix

// before
if (!session.clientSecret) {
  throw new Error("Edge function returned no client_secret");
}

// after
if (!session.clientSecret) {
  throw new Error(
    session.url
      ? "Checkout returned only a URL but the hosted path was already handled"
      : "Edge function returned no client_secret",
  );
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify teamId is valid before checkout
if (!teamId) {
  setError('A team is required to start checkout');
  return;
}

Type guard

function isUsableSession(session: CheckoutSession | null): session is { clientSecret: string; mock?: boolean } {
  return session !== null && typeof session.clientSecret === 'string' && session.clientSecret.length > 0;
}

Try / catch

// The component already catches at line 205:
// catch (e: unknown) {
//   if (cancelled) return;
//   const msg = e instanceof Error ? e.message : t('payg.checkout.error.startFailed');
//   setError(msg);
//   onErrorRef.current?.(msg);
// }

Prevention

When it happens

Trigger: A platform-specific override of createCheckoutSession (via @app/* shadow in desktop or cloud layer) returns an object with neither url nor clientSecret. Or the billing seam's return type is widened/changed and the component receives an empty object.

Common situations: Desktop or cloud build shadows createCheckoutSession with a stub that returns {}; billing service contract changed but the platform override wasn't updated; the edge function returns a response that the seam maps to an empty CheckoutSession.

Related errors


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