Stirling-Tools/Stirling-PDF · error · StripeFunctionError

create-payg-bundle-quote failed

Error message

create-payg-bundle-quote failed

What it means

Fallback thrown by createBundleStripeQuote when the create-payg-bundle-quote edge function returns success === false OR omits stripe_quote_id. A real Stripe quote handle is mandatory to proceed, so an absent stripe_quote_id is a hard failure even if success was true.

Source

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

/**
 * Create + finalize the Stripe QUOTE backing a persisted quote row, via {@code create-payg-bundle-quote}.
 * The customer-facing quote number + PDF are Stripe's. On edit the server cancels the prior Stripe quote
 * and issues a new one. Capacity is credited only when the accepted quote's invoice is PAID (the webhook).
 */
export async function createBundleStripeQuote(
  req: BundleStripeQuoteRequest,
): Promise<BundleStripeQuote> {
  const res = await invoke<BundleStripeQuoteResponse>(
    "create-payg-bundle-quote",
    {
      team_id: req.teamId,
      quote_id: req.quoteId,
      ...(req.poNumber ? { po_number: req.poNumber } : {}),
      ...(req.daysUntilDue != null ? { days_until_due: req.daysUntilDue } : {}),
    },
  );
  if (!res.success || !res.stripe_quote_id) {
    throw new StripeFunctionError(
      res.error ?? "create-payg-bundle-quote failed",
    );
  }
  return {
    stripeQuoteId: res.stripe_quote_id,
    stripeQuoteNumber: res.stripe_quote_number ?? null,
  };
}

/** A raised Stripe invoice — the {@code accept-payg-bundle-quote} result. */
export interface BundleInvoice {
  invoiceId: string;
  /** Stripe-hosted page where the buyer pays / downloads the invoice. */
  hostedInvoiceUrl: string | null;
  invoicePdf: string | null;
  status: string | null;
}

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Read the res.error if present in the function logs; if absent, fix the function to return it.
  2. Verify quoteId corresponds to a valid open quote (see getLatestBundleQuote) for the team.
  3. Confirm the Stripe secret key + connected-account config the function uses.
  4. Catch StripeFunctionError at the call site and show a retry-able error to the admin.

Example fix

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

// after
if (quoteNotFound) {
  return json({ success: false, error: 'quote_not_found' });
}
return json({ success: true, stripe_quote_id: stripeQuote.id });
Defensive patterns

Strategy: try-catch

Type guard

function isBundleQuoteResponse(res: BundleStripeQuoteResponse | null): res is BundleStripeQuoteResponse & { success: true; stripe_quote_id: string } {
  return res != null && res.success === true && typeof res.stripe_quote_id === "string";
}

Try / catch

try {
  const { stripeQuoteId } = await createBundleStripeQuote(req);
  proceedToAccept(stripeQuoteId);
} catch (e) {
  if (e instanceof StripeFunctionError) {
    notifications.show({ message: `Could not create quote: ${e.message}`, color: "red" });
  } else throw e;
}

Prevention

When it happens

Trigger: create-payg-bundle-quote responds with success:false, or with success:true but no stripe_quote_id — e.g. Stripe rejected the quote creation, the team/quote row was invalid, or the function's Stripe call failed and it returned success without the handle.

Common situations: Stripe API key misconfigured in the function; the quote row referenced by quote_id does not exist or belongs to another team; Stripe-side error (invalid customer, currency mismatch) swallowed by the function; response shape changed after a function deploy.

Related errors


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