Stirling-Tools/Stirling-PDF · error · StripeFunctionError

finalize-payg-bundle-invoice failed

Error message

finalize-payg-bundle-invoice failed

What it means

Fallback thrown by finalizeBundleInvoice when finalize-payg-bundle-invoice returns success === false OR omits invoice_id. Finalize stamps the recipient + PO and finalizes the Stripe invoice; a missing invoice_id means there is nothing the caller can hand to the hosted payment page.

Source

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

  quoteId: number;
  poNumber?: string;
  /** Optional company — becomes the invoice bill-to name (no length cap). */
  companyName?: string;
  /** Required account-holder name — the bill-to when there's no company, else an "Account holder" field. */
  accountName?: string;
}): Promise<BundleInvoice> {
  const res = await invoke<BundleInvoiceResponse>(
    "finalize-payg-bundle-invoice",
    {
      team_id: req.teamId,
      quote_id: req.quoteId,
      ...(req.poNumber ? { po_number: req.poNumber } : {}),
      ...(req.companyName ? { company_name: req.companyName } : {}),
      ...(req.accountName ? { account_name: req.accountName } : {}),
    },
  );
  if (!res.success || !res.invoice_id) {
    throw new StripeFunctionError(
      res.error ?? "finalize-payg-bundle-invoice failed",
    );
  }
  return {
    invoiceId: res.invoice_id,
    hostedInvoiceUrl: res.hosted_invoice_url ?? null,
    invoicePdf: res.invoice_pdf ?? null,
    status: res.status ?? null,
  };
}

/**
 * Cancel an unpaid prepaid-bundle purchase via {@code cancel-payg-bundle-quote}: the edge fn voids the
 * invoice (delete if draft, void if finalized), best-effort cancels the Stripe quote, and voids the quote
 * row so the buyer can start over. Nothing was charged (capacity is credited on invoice.paid), so there's
 * no refund. Throws a StripeFunctionError on failure (e.g. {@code invoice_already_paid}).
 */
export async function cancelBundleQuote(req: {

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Guard against double-finalize: track invoice status and disable finalize once status === open/paid.
  2. Validate poNumber/companyName/accountName before calling if the function requires them.
  3. Read res.error / function logs to distinguish already-paid from a genuine Stripe failure.
  4. Ensure the function returns a clear error code (e.g. invoice_already_paid) on the failure branch.

Example fix

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

// after
if (invoicePaid) {
  return json({ success: false, error: 'invoice_already_paid' });
}
await stripe.invoices.finalizeInvoice(invoiceId);
return json({ success: true, invoice_id: invoiceId });
Defensive patterns

Strategy: try-catch

Validate before calling

function canFinalizeInvoice(invoice: BundleInvoice | null): boolean {
  return invoice != null && invoice.status !== "paid" && invoice.status !== "open";
}

if (!canFinalizeInvoice(currentInvoice)) {
  notifications.show({ message: "Invoice cannot be finalized in its current state.", color: "orange" });
  return;
}

Type guard

function isFinalizeResponse(res: BundleInvoiceResponse | null): res is BundleInvoiceResponse & { success: true; invoice_id: string } {
  return res != null && res.success === true && typeof res.invoice_id === "string";
}

Try / catch

try {
  await finalizeBundleInvoice({ teamId, quoteId, poNumber });
} catch (e) {
  if (e instanceof StripeFunctionError && /already.*paid/i.test(e.message)) {
    notifications.show({ message: "This invoice is already paid.", color: "orange" });
    refreshInvoice();
  } else throw e;
}

Prevention

When it happens

Trigger: finalize-payg-bundle-invoice responds success:false (with or without res.error), or success:true without invoice_id. Fires when the invoice cannot be finalized — e.g. it was already paid/finalized, or required PO/company fields were rejected.

Common situations: Re-finalizing an already-finalized or already-paid invoice; Stripe rejecting the finalize call; a function guard rejecting the PO/company input and returning success without invoice_id; concurrent finalize requests.

Related errors


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