Stirling-Tools/Stirling-PDF · error · Error

Failed to create billing portal session: ${error.message}

Error message

Failed to create billing portal session: ${error.message}

What it means

Thrown by createBillingPortalSession when supabase.functions.invoke('manage-billing', ...) returns a non-null error. The manage-billing edge function creates a Stripe billing-portal session using the supplied license_key for self-hosted auth; a returned error means the function failed — invalid/expired license key, no matching Stripe customer, missing STRIPE_SECRET_KEY, or a function exception. error.message is wrapped in the thrown Error.

Source

Thrown at frontend/editor/src/proprietary/services/licenseService.ts:401

    licenseKey: string,
  ): Promise<BillingPortalResponse> {
    // Check if Supabase is configured
    if (!isSupabaseConfigured || !supabase) {
      throw new Error(
        "Supabase is not configured. Billing portal is not available.",
      );
    }

    const { data, error } = await supabase.functions.invoke("manage-billing", {
      body: {
        return_url: returnUrl,
        license_key: licenseKey,
        self_hosted: true, // Explicitly indicate self-hosted mode
      },
    });

    if (error) {
      throw new Error(
        `Failed to create billing portal session: ${error.message}`,
      );
    }

    return data as BillingPortalResponse;
  },

  /**
   * Get the installation ID from the backend (MAC-based fingerprint)
   */
  async getInstallationId(): Promise<string> {
    try {
      const response = await apiClient.get("/api/v1/admin/installation-id");

      const data: InstallationIdResponse = await response.data;
      return data.installationId;
    } catch (error) {
      console.error("Error fetching installation ID:", error);

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Read error.message to distinguish auth (license key) failures from Stripe errors.
  2. Confirm the manage-billing function is deployed with STRIPE_SECRET_KEY and that the billing portal is enabled in Stripe.
  3. Re-verify the license key via checkLicenseKey before opening the portal.
  4. Prompt the user to re-enter their license key if it's invalid.

Example fix

// before
const { url } = await licenseService.createBillingPortalSession(returnUrl, key);

// after
try {
  const { url } = await licenseService.createBillingPortalSession(returnUrl, key);
  window.location.href = url;
} catch (e) {
  showToast(e instanceof Error ? e.message : 'Billing portal unavailable');
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!licenseKey) {
  setError('License key required');
  return;
}
// optionally re-verify: await licenseService.checkLicenseKey(installationId);

Type guard

export function isPlausibleLicenseKey(k: string): boolean {
  return typeof k === 'string' && k.trim().length > 0;
}

Try / catch

try {
  const { url } = await licenseService.createBillingPortalSession(returnUrl, key);
  window.location.href = url;
} catch (e) {
  showToast(e instanceof Error ? e.message : 'Billing portal unavailable');
  if (/license|auth|invalid/i.test(e instanceof Error ? e.message : "")) promptRelicense();
}

Prevention

When it happens

Trigger: license_key doesn't map to a Stripe customer; license was revoked/expired so the function rejects; manage-billing function missing STRIPE_SECRET_KEY secret; return_url malformed; Stripe-side error creating the portal session.

Common situations: User's license was cancelled/refunded so the Stripe customer no longer has a subscription; license key typos; edge function not deployed or missing secrets; Stripe account misconfigured for billing portal (must be enabled in Stripe dashboard).

Related errors


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