Stirling-Tools/Stirling-PDF · error · Error
Failed to fetch plans: ${error.message}
Error message
Failed to fetch plans: ${error.message} What it means
Thrown by getPlans after supabase.functions.invoke('stripe-price-lookup', ...) returns a non-null error object. Supabase edge-function invocation resolves with {data, error} rather than throwing, so the code inspects error and throws a descriptive Error wrapping error.message. The underlying cause is the edge function failing: missing Stripe keys, a bad lookup key, an exception inside the function, or a FunctionsError payload.
Source
Thrown at frontend/editor/src/proprietary/services/licenseService.ts:147
const { data, error } = await supabase.functions.invoke<{
prices: Record<
string,
{
unit_amount: number;
currency: string;
lookup_key: string;
}
>;
missing: string[];
}>("stripe-price-lookup", {
body: {
lookup_keys: SELF_HOSTED_LOOKUP_KEYS,
currency,
},
});
if (error) {
throw new Error(`Failed to fetch plans: ${error.message}`);
}
if (!data || !data.prices) {
throw new Error("No pricing data returned");
}
// Log missing prices for debugging
if (data.missing && data.missing.length > 0) {
console.warn(
"Missing Stripe prices for lookup keys:",
data.missing,
"in currency:",
currency,
);
}
// Build price map for easy access
const priceMap = new Map<View on GitHub (pinned to 9ef20dcab8)
Solutions
- Inspect error.message — for a FunctionsRelayError it names the function; for a Stripe error it carries the Stripe message.
- Confirm the stripe-price-lookup edge function is deployed and has STRIPE_SECRET_KEY set in its secrets.
- Verify all SELF_HOSTED_LOOKUP_KEYS exist as lookup keys on the Stripe products.
- Catch and fall back to static plans (per error 166's contract) if dynamic pricing is non-critical.
Example fix
// before
const { data, error } = await supabase.functions.invoke('stripe-price-lookup', { body });
if (error) throw new Error(`Failed to fetch plans: ${error.message}`);
// after — keep the throw, but surface a retry + static fallback at the caller
try { return await licenseService.getPlans(f, h, c); }
catch (e) {
if (/Failed to fetch plans/.test(e.message)) { return STATIC_PLANS; }
throw e;
} Defensive patterns
Strategy: fallback
Validate before calling
// nothing to validate client-side beyond isSupabaseConfigured; the error originates in the edge function if (!isSupabaseConfigured) return STATIC_PLANS;
Try / catch
try {
return await licenseService.getPlans(features, highlights, currency);
} catch (e) {
if (/Failed to fetch plans/.test(e instanceof Error ? e.message : "")) {
logToMonitoring(e);
return { plans: STATIC_PLANS };
}
throw e;
} Prevention
- Confirm stripe-price-lookup is deployed with STRIPE_SECRET_KEY before relying on dynamic pricing.
- Keep SELF_HOSTED_LOOKUP_KEYS in sync with the actual Stripe lookup keys.
- Treat dynamic pricing as progressive enhancement; always have static plans ready.
When it happens
Trigger: The stripe-price-lookup edge function throws (Stripe secret key missing/misconfigured on the Supabase project); one of SELF_HOSTED_LOOKUP_KEYS isn't registered in Stripe; the function times out or hits a cold-start error; Supabase returns a FunctionsHttpError/FetchError.
Common situations: Stripe price lookup keys (selfhosted:server:monthly etc.) not created in the connected Stripe account; edge function deployed without STRIPE_SECRET_KEY env var; currency param unsupported by the function; Supabase project paused/regional outage.
Related errors
- Supabase is not configured. Please use static plans instead.
- No pricing data returned
- Failed to create checkout session: ${error.message}
- Failed to create billing portal session: ${error.message}
- Failed to update seat count: ${error.message}
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/f133e22cfcd067a9.
Report an issue: GitHub.