# Build a Billing System in Lovable

- Tool: Lovable Prompts
- Last updated: June 2026

## TL;DR

Paste the starter prompt into Lovable Agent Mode and get a working B2B billing system: customers, draft-to-sent-to-paid invoice workflow, gap-free sequential numbering via a Postgres sequence assigned only at finalize, line items, Stripe payment links, idempotent webhook reconciliation, PDF generation, and dunning emails. Full build takes 1-2 days. Expected credit burn: 150-250 on a Pro plan with one top-up.

## Frequently asked questions

### Why is gap-free invoice numbering required, and why is COUNT(*)+1 wrong?

Most tax jurisdictions (EU, UK, Canada, Australia, and many US states) require invoice numbers to be sequential and gap-free. An auditor who sees INV-2026-00041, INV-2026-00043 will ask where #42 is — and 'we abandoned a draft' is not an acceptable answer in a VAT audit. COUNT(*)+1 is wrong because two concurrent finalizations can both read the same count and assign the same number. nextval() as a column DEFAULT is wrong because the sequence increments when the draft is created, then leaves a gap when the draft is abandoned. The correct approach: invoice_number is NULL on all draft rows; the SECURITY DEFINER finalize_invoice() function calls nextval() at the moment of finalization and cannot be interrupted or rolled back independently.

### Does Stripe Invoicing handle this? When should I just use that?

Stripe Invoicing (stripe.com/invoicing) covers simple invoice → pay flows at 0.4% per paid invoice (capped at $2). Use it if your needs are: create invoice, customer pays via Stripe-hosted page, you reconcile in Stripe Dashboard. Build in Lovable when you need invoice numbering that matches a legacy scheme your accountant requires, integration with your existing Lovable customer table as the single source of truth, custom line-item logic Stripe can't model (tiered usage, hourly billing from a time-tracking integration), a customer-facing portal that matches your brand exactly, or you want offline payment support (bank transfer marked paid manually). The break-even point is roughly 150 invoices/month where Stripe Invoicing fees ($2 × 150 = $300/mo) exceed the cost of this custom build.

### How do I generate a PDF of the invoice?

The generate-pdf follow-up (follow-up #3) uses @pdf-lib, a pure JavaScript PDF library that works inside Deno Edge Functions without any external service. It renders a simple table layout with your org name, invoice details, and line items. For complex branded layouts with custom fonts and pixel-perfect design, you can proxy to Browserless ($5-10/mo) or PDFShift ($9/mo) — pass your HTML template and get a PDF back. The @pdf-lib approach is free and sufficient for standard B2B invoice formats. Include a PDF preview iframe in InvoiceView so you can review before sending.

### Can my customer pay without creating an account?

Yes — the /pay/:invoiceNumber route is intentionally public. Customers receive an email with a link like /pay/INV-2026-00042?token=eyJhbGc... and can view and pay via Stripe Payment Link without logging in. The signed JWT token in the query string prevents invoice enumeration — without a valid token for the specific invoice number, the page returns a 404 (not a 403, which would confirm the invoice exists). Tokens are generated in finalize-invoice with a 30-day expiry and signed with INVOICE_TOKEN_SECRET stored in Cloud tab → Secrets.

### Why must the Stripe webhook read raw body?

Stripe verifies webhook authenticity by generating an HMAC-SHA256 signature over the raw request body and comparing it to the stripe-signature header. If you call req.json() first, the body is JSON-parsed and converted back to a string when constructEventAsync tries to verify it — the whitespace and key ordering may differ from the original, producing a different hash. The signature check fails silently: constructEventAsync returns a response that appears valid but with a false flag, and your webhook processes no events. The rule is: const body = await req.text() must be the first thing your webhook handler does, before any JSON parsing, before logging the body, before anything.

### How do I handle EU VAT or US sales tax?

For EU VAT: store tax_id and billing_address.country on the customers table. When generating a line item, apply the correct VAT rate based on customer country (you'll need a VAT rates lookup table or use a library). For B2B transactions with a valid VAT ID (reverse charge), tax_cents = 0 and add a 'VAT reverse charge applies' note. For US sales tax: Stripe Tax ($0.50/transaction above $500/mo volume) can auto-calculate and collect state sales tax if enabled in finalize-invoice. The billing system starter scaffolds tax_cents as a separate column specifically so VAT/tax can be tracked independently from the subtotal for reporting.

### When does it make sense to hire RapidDev for billing instead?

If your build outgrows this prompt kit and you need custom architecture, RapidDev builds production-grade Lovable apps at $13K-$25K — book a free 30-minute consultation at rapidevelopers.com.

---

Source: https://www.rapidevelopers.com/lovable-prompts/lovable-prompts-for-building-billing-system
© RapidDev — https://www.rapidevelopers.com/lovable-prompts/lovable-prompts-for-building-billing-system
