Stirling-Tools/Stirling-PDF · error · StripeFunctionError
create-customer-portal-session failed
Error message
create-customer-portal-session failed
What it means
Fallback thrown by createPortalSession when the create-customer-portal-session edge function returns success === false OR omits url. The Stripe customer portal must return a hosted URL to redirect to; an absent url is a hard failure even if success was true. The comment notes a typical cause is 404 team_not_subscribed for free teams.
Source
Thrown at frontend/editor/src/portal/billing/stripe.ts:520
}
return stripePromise;
}
/**
* Mint a Stripe Customer Portal session. The admin can manage their card,
* view invoices, and cancel from Stripe's hosted UI. The edge function returns
* 404 with {@code team_not_subscribed} if called for a free team — surfaced
* here as a StripeFunctionError the caller can toast.
*/
export async function createPortalSession(
req: PortalSessionRequest,
): Promise<string> {
const res = await invoke<PortalResponse>("create-customer-portal-session", {
team_id: req.teamId,
return_url: req.returnUrl,
});
if (!res.success || !res.url) {
throw new StripeFunctionError(
res.error ?? "create-customer-portal-session failed",
);
}
return res.url;
}
View on GitHub (pinned to 9ef20dcab8)
Solutions
- Only show the portal/billing-manage control when the team is subscribed (hide for free teams to avoid team_not_subscribed).
- Read res.error / the thrown message and toast specifically for team_not_subscribed vs a real Stripe error.
- Verify returnUrl is supplied and is an allowed domain in the Stripe portal config.
- Ensure the function returns url on success and a stable error code on failure.
Example fix
// before
<Button onClick={() => createPortalSession({ teamId, returnUrl })} />
// after — gate on subscription, toast on expected failures
if (!team.isSubscribed) {
notifications.show({ message: "This team has no subscription to manage.", color: "orange" });
return;
}
try {
const url = await createPortalSession({ teamId, returnUrl });
window.location.href = url;
} catch (e) {
if (e instanceof StripeFunctionError && /team_not_subscribed/i.test(e.message)) {
notifications.show({ message: "No subscription found.", color: "orange" });
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
function teamHasSubscription(team: { isSubscribed?: boolean }): boolean {
return team.isSubscribed === true;
}
if (!teamHasSubscription(team)) {
notifications.show({ message: "This team has no subscription to manage.", color: "orange" });
return;
} Type guard
function isPortalResponseOk(res: PortalResponse | null): res is PortalResponse & { success: true; url: string } {
return res != null && res.success === true && typeof res.url === "string" && res.url.length > 0;
} Try / catch
try {
const url = await createPortalSession({ teamId, returnUrl });
window.location.href = url;
} catch (e) {
if (e instanceof StripeFunctionError && /team_not_subscribed/i.test(e.message)) {
notifications.show({ message: "No subscription found for this team.", color: "orange" });
} else throw e;
} Prevention
- Only show the manage-subscription control for subscribed teams.
- Pass a valid returnUrl that is an allowed domain in the Stripe portal config.
- Branch on the team_not_subscribed error code and toast gracefully instead of erroring.
When it happens
Trigger: create-customer-portal-session responds success:false (with res.error, or this fallback if omitted), or success:true without url. Most common for a team with no active subscription (the function returns team_not_subscribed) or when Stripe portal-session creation fails.
Common situations: Clicking "manage subscription" / "view invoices" for a free team that has no Stripe customer; Stripe-side failure creating the portal session; missing return_url; function deploy dropped the url field.
Related errors
- Edge function ${name} returned no data
- create-checkout-session failed
- create-checkout-session returned neither client_secret nor U
- create-payg-bundle-quote failed
- accept-payg-bundle-quote failed
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/427fcbbfc89f8d63.
Report an issue: GitHub.