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
- Guard against double-finalize: track invoice status and disable finalize once status === open/paid.
- Validate poNumber/companyName/accountName before calling if the function requires them.
- Read res.error / function logs to distinguish already-paid from a genuine Stripe failure.
- 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
- Track invoice status and disable finalize once it is open/paid.
- Validate poNumber/companyName before calling if the function requires them.
- Distinguish already-paid from genuine Stripe errors via the returned error code.
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
- accept-payg-bundle-quote failed
- Edge function ${name} returned no data
- create-checkout-session failed
- create-checkout-session returned neither client_secret nor U
- create-payg-bundle-quote failed
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/cfa14d92e3bb8fbb.
Report an issue: GitHub.