1 ---
2 name: invoice-generator
3 description: Generate professional invoices as HTML pages or React apps with PDF export.
4 ---
5
6 # Invoice Generator
7
8 Build invoices as standalone HTML pages (preferred for simplicity) or React web artifacts. They auto-scale to fit the page and export to PDF via the browser's native print dialog. The web page is the single source of truth — the PDF is a print of it.
9
10 > **NixOS note:** Puppeteer / headless Chromium fails on this platform due to missing system libraries (`libglib-2.0.so.0`). Use`window.print()` (browser print dialog → Save as PDF) instead. The approach below is designed for that reality.
11
12 ## Before You Start Building — Gather Information First
13
14 **Do NOT start building the invoice until you have enough information to populate real line items and details.** An invoice with placeholder data is useless.
15
16 ### If the user provides complete invoice details
17
18 Go ahead and start building immediately. You have what you need.
19
20 ### If the user asks to "make me an invoice" without providing details
21
22 You MUST ask clarifying questions before writing any code. Ask about:
23
24 1. **Seller info** — Business name, address, email, phone, logo (if any), VAT/tax ID (if applicable)
25 2. **Client info** — Client/company name, address, contact email, VAT number (if B2B in EU)
26
27 3. **Line items** — Description of each service/product, quantity, rate/price per unit
28 4. **Payment terms** — Net 30, due on receipt, etc. + preferred payment method (bank transfer, Stripe, PayPal, etc.)
29
30 5. **Invoice number** — Do they have an existing numbering scheme, or should you start one?
31 6. **Dates** — Invoice date, service/delivery date (if different), due date
32
33 7. **Tax** — What tax rate applies? (Sales tax, VAT, ITBIS, none?) This depends on jurisdiction.
34 8. **Where is the seller based?** — Determines required legal fields, page size (US = Letter, everyone else = A4), and tax handling
35
36 9. **Billing contact email** — Always ask explicitly. Do NOT invent a placeholder like `billing@company.com` — the user must confirm the real email address.
37 10. **Production domain** — Always ask what domain the app is deployed to. Do NOT use the Replit dev domain or a made-up domain on the invoice itself.
38
39 ### How to ask
40
41 Start with the essentials:
42
43 > "To create your invoice, I need a few details:
44 >
45 >
46 >
47 > 1. Your business name and address (the seller)
48 >
49 > 2. Who you're billing — client name and address
50 >
51 > 3. What you're billing for — list each item/service with the quantity and price
52 >
53 > 4. Payment terms — when is it due, and how should they pay?
54 >
55 > 5. What email should clients contact for billing questions?
56 >
57 > 6. What domain is your app deployed to (for the website shown on the invoice)?"
58
59 Then follow up for tax details, numbering, branding, etc. based on what they share.
60
61 ### If the invoice feels incomplete
62
63 If the user gives vague descriptions like "consulting work," push for specifics: *"Can you break that down into specific deliverables? e.g., 'Website redesign — 3 revision rounds' at $5,000. Specific line items look more professional and reduce client pushback."*
64
65 ## Flag Guesses and Inferred Details
66
67 If you had to guess or infer any details — tax rates, payment terms, invoice numbers, dates — you MUST tell the user what you assumed. After presenting the first draft, explicitly list anything you weren't sure about. For example:
68
69 > "A few things I assumed — let me know if any need adjusting:
70 >
71 >
72 >
73 > - I used invoice number INV-2026-0001 — do you have an existing numbering scheme?
74 >
75 > - I set the tax rate to 0% since you didn't mention taxes — should I add sales tax or VAT?
76 >
77 > - I set payment terms to Net 30 with a due date of April 13 — is that right?"
78
79 Do NOT silently present fabricated details as fact. Getting invoice details wrong can cause real payment and legal issues.
80
81 ## Two Approaches: Choose Based on Use Case
82
83 ### Approach A — Standalone HTML (preferred for SaaS/subscription templates)
84
85 **Use when:** The same invoice template will be sent to many different clients (e.g., a subscription SaaS billing its users). Client-specific data (name, email, invoice number, dates, amount) is passed via URL query parameters. One file serves all clients.
86
87 #### Architecture
88
89 ```text
90
91 client/public/invoice-template.html \# Single file, URL-param driven
92
93 ```
94
95 ##### How it works
96
97 - The template lives in the main app's `public/` folder, served as a static asset
98 - All client-specific fields are read from `URLSearchParams` at runtime
99
100 - Seller info, domain, and billing email are hardcoded (they never change)
101 - Client pastes the parameterized link into any browser → prints to PDF
102
103 **File location:** `client/public/invoice-template.html`→ served at`/invoice-template.html`
104
105 ### Approach B — React Component (preferred for one-off custom invoices)
106
107 **Use when:** Building a one-off invoice for a specific client with complex line items, custom branding per client, or when integrated into an app's billing flow.
108
109 #### Architecture (2)
110
111 ```text
112
113 artifacts/<client>-invoice/
114
115 client/src/pages/Invoice.tsx \# Invoice data + component with auto-scale
116
117 client/src/index.css \# Print-ready styles (A4 or Letter)
118
119 ```
120
121 ## URL Parameter Pattern (Approach A)
122
123 This is the cleanest pattern for subscription/SaaS invoices. Every field that varies per client is a query param; everything that belongs to the seller is hardcoded.
124
125 ### Supported parameters
126
127 | Param | Description | Default |
128
129 |-------|-------------|---------|
130
131 | `client` | Client / company name | "Client Name" (shown in amber as warning) |
132
133 | `email` | Client email address | empty |
134
135 | `inv`| Invoice number |`PREFIX-YYYY-0001` |
136
137 | `date` | Invoice date (ISO: YYYY-MM-DD) | today |
138
139 | `due` | Due date (ISO: YYYY-MM-DD) | today + 30 days |
140
141 | `period` | Service period label | next calendar month |
142
143 | `amount` | Line item amount | plan default |
144
145 | `plan` | Plan name label | "Pro" |
146
147 | `stripe` | Stripe payment link URL | placeholder (shown dimmed) |
148
149 ### Example parameterized URL
150
151 ```text
152
153 https://nido.com.do/invoice-template.html
154
155 ?client=Familia+García
156
157 &email=garcia@email.com
158
159 &inv=NiDO-2026-0014
160
161 &date=2026-04-01
162
163 &due=2026-05-01
164
165 &period=April+2026
166
167 &amount=35
168
169 &plan=Pro
170
171 &stripe=https://buy.stripe.com/abc123
172
173 ```
174
175 ### URL parsing boilerplate
176
177 ```javascript
178
179 function parseParam(name, fallback) {
180
181 const v = new URLSearchParams(window.location.search).get(name);
182
183 return (v && v.trim()) ? v.trim() : fallback;
184
185 }
186
187 function parseDate(param, fallback) {
188
189 const raw = new URLSearchParams(window.location.search).get(param);
190
191 if (raw && raw.trim()) {
192
193 const d = new Date(raw.trim() + 'T12:00:00'); // noon avoids TZ off-by-one
194
195 if (!isNaN(d)) return d;
196
197 }
198
199 return fallback;
200
201 }
202
203 ```
204
205 ### Always show the shareable URL in the UI
206
207 Add a URL display bar above the invoice so the user can see and copy the full parameterized link:
208
209 ```html
210
211 <div class="print-bar">
212
213 <div class="url-display" id="url-display" onclick="copyLink()" title="Click to copy">
214
215 <svg><!-- link icon --></svg>
216
217 <span id="url-text">loading…</span>
218
219 </div>
220
221 <button class="btn-copy" onclick="copyLink()">Copy Link</button>
222
223 <button class="btn-print" onclick="window.print()">Download / Print PDF</button>
224
225 </div>
226
227 ```
228
229 ```javascript
230
231 // Show the actual full URL so user can copy and send it
232
233 document.getElementById('url-text').textContent = window.location.href;
234
235 ```
236
237 The URL bar should be hidden in `@media print`.
238
239 ## Page Cutoff / Auto-Scale Logic
240
241 This is the most important implementation detail. Use a **two-layer structure**:
242
243 1. **`.invoice-page`** — fixed`height: 1056px`(Letter) or`height: 1122px`(A4),`overflow: hidden`. This is the page boundary.
244 2. **`.inv-scaler`** — inner wrapper,`min-height: 1056px`,`display: flex; flex-direction: column; transform-origin: top left`. This holds all content and gets scaled.
245
246 ### CSS
247
248 ```css
249
250 .invoice-page {
251
252 width: 816px; /* 8.5in at 96dpi */
253
254 height: 1056px; /* 11in at 96dpi — Letter */
255
256 overflow: hidden;
257
258 position: relative;
259
260 }
261
262 .inv-scaler {
263
264 width: 100%;
265
266 min-height: 1056px; /* Fills page by default so footer stays at bottom */
267
268 display: flex;
269
270 flex-direction: column;
271
272 transform-origin: top left;
273
274 }
275
276 ```
277
278 ### JavaScript scaleToFit function
279
280 ```javascript
281
282 function scaleToFit() {
283
284 const page = document.querySelector('.invoice-page');
285
286 const scaler = document.querySelector('.inv-scaler');
287
288 if (!page || !scaler) return;
289
290 // Reset any existing transform so we measure true content height
291
292 scaler.style.transform = 'none';
293
294 scaler.style.width = '100%';
295
296 const pageH = page.clientHeight; // 1056px
297
298 const contentH = scaler.scrollHeight;
299
300 if (contentH > pageH) {
301
302 const scale = pageH / contentH;
303
304 scaler.style.transformOrigin = 'top left';
305
306 scaler.style.transform = `scale(${scale})`;
307
308 // Compensate horizontal shrink so content stays full-width visually
309
310 scaler.style.width = `${100 / scale}%`;
311
312 }
313
314 }
315
316 // Run on load, after fonts resolve, on resize, and via safety timeouts
317
318 scaleToFit();
319
320 document.fonts.ready.then(scaleToFit);
321
322 window.addEventListener('resize', scaleToFit);
323
324 setTimeout(scaleToFit, 300);
325
326 setTimeout(scaleToFit, 1000);
327
328 ```
329
330 **Why `min-height`on`.inv-scaler`?** Without it,`flex: 1`on`.inv-body`does nothing (no constrained parent height), and the footer won't stick to the bottom of short invoices. With`min-height: 1056px`, the scaler always fills the full page — footer stays at the bottom — and only grows taller if content genuinely overflows, at which point`scaleToFit` shrinks it back.
331
332 ## Page Length Rules
333
334 - **Most invoices should fit on one page.** The auto-scale handles this for typical invoices (up to ~15 line items).
335 - **It's OK to go multi-page** if the invoice has many line items. Don't shrink text to an unreadable size just to force everything onto one page.
336
337 - **If going multi-page**: repeat the table header (Description | Qty | Rate | Amount) on each page, and put the totals section on the final page.
338 - **Always put payment instructions and totals on the last page** so the client sees how much they owe and how to pay without hunting.
339
340 ## Brand Domain and Contact Email
341
342 - **Never invent placeholder domains** like `yourcompany.com`or`nido.app` — always ask the user what their real production domain is.
343 - **Never invent billing emails** — always ask explicitly. The billing email and the app domain are often different (e.g., website: `nido.com.do`, billing email:`billing@nido.com`).
344
345 - **Hardcode seller info directly** in the script — do not make it a URL param. The seller never changes between invoices.
346 - **Do not use `window.location.hostname`** as the brand domain — the dev/Replit URL is not the brand domain.
347
348 ```javascript
349
350 // ── Hardcode seller info — never use window.location or placeholders ──
351
352 const appDomain = 'nido.com.do'; // confirmed with user
353
354 const billingEmail = 'billing@nido.com'; // confirmed with user
355
356 ```
357
358 ## Required Fields by Jurisdiction
359
360 ### EU (VAT Directive 2006/112/EC, Article 226) — legally mandatory
361
362 - Sequential invoice number (gaps must be documented — auditors **will** assess VAT on missing numbers)
363 - Invoice date + date of supply (if different)
364
365 - Seller's full name, address, and **VAT number**
366 - Customer's name and address (and VAT number if B2B)
367
368 - Description of goods/services, quantity/extent
369 - Unit price excluding VAT, VAT rate per line, VAT amount **in the member state's currency** (even if invoice is in USD)
370
371 - **Reverse charge:** if selling B2B cross-border within EU, charge 0% VAT and add the notation `"Reverse charge — VAT to be accounted for by the recipient (Art. 196, Directive 2006/112/EC)"`. Include the customer's VAT number (validate via VIES).
372
373 **US — no federal invoice law.** Sequential numbering is best practice (IRS wants unique IDs for audit trail) but not legally required. Sales tax rules vary by state; many services are untaxed.
374
375 **Other jurisdictions:** Always ask the user which country they operate in before assuming a tax regime. Key questions: Is the tax called VAT, GST, HST, sales tax, or something else? What is the rate? Are digital services exempt or taxable? If exempt, label the tax line clearly (e.g., `VAT — Exempt`or the local equivalent) with`$0.00` rather than omitting the line entirely — omitting it looks like you forgot, whereas labeling it exempt looks intentional and professional.
376
377 **Numbering scheme:** `{PREFIX}-{YYYY}-{SEQ:04d}`e.g.`NiDO-2026-0042`. Prefix can distinguish clients or entities. Never reuse or skip; if you void one, keep the voided record.
378
379 ## Payment Terms Glossary
380
381 | Term | Meaning | Typical use |
382
383 |------|---------|-------------|
384
385 | Due on receipt | Pay immediately | Small amounts, new clients |
386
387 | Net 30 | Due 30 days from invoice date | Standard B2B |
388
389 | Net 60 / Net 90 | 60/90 days | Large enterprise (push back on this) |
390
391 | 2/10 Net 30 | 2% discount if paid in 10 days, else full in 30 | Incentivize fast payment |
392
393 | EOM | Due end of month | |
394
395 | 1.5% monthly late fee | Compounds on overdue balance | Check local usury caps — often ~18% APR max |
396
397 ## Layout Structure
398
399 ```html
400
401 <div class="invoice-page">
402
403 <div class="inv-scaler">
404
405 <!-- Teal/brand header bar -->
406
407 <div class="inv-header">
408
409 Logo + "INVOICE" title + invoice number
410
411 </div>
412
413 <!-- Accent stripe (thin colored bar below header) -->
414
415 <div class="accent-stripe"></div>
416
417 <!-- Main content -->
418
419 <div class="inv-body">
420
421 <!-- Two columns: From (seller) / Bill To (client) -->
422
423 <div class="parties">...</div>
424
425 <!-- Invoice date / service period / due date / payment terms -->
426
427 <div class="dates-row">...</div>
428
429 <!-- Line items table -->
430
431 <table>Description | Plan | Qty | Rate | Amount</table>
432
433 <!-- Subtotal / Tax / Total -->
434
435 <div class="totals-section">...</div>
436
437 <!-- How to pay (Stripe link, bank details, contact email) -->
438
439 <div class="payment-box">...</div>
440
441 <!-- Footer: seller brand + late fee policy -->
442
443 <div class="inv-footer">...</div>
444
445 </div><!-- /inv-body -->
446
447 </div><!-- /inv-scaler -->
448
449 </div><!-- /invoice-page -->
450
451 ```
452
453 ## Stripe Payment Link Pattern
454
455 Make the Stripe URL a query param so each invoice can have a unique checkout link:
456
457 ```javascript
458
459 const stripeUrl = parseParam('stripe', 'https://buy.stripe.com/your-link');
460
461 const stripeEl = document.getElementById('stripe-link');
462
463 stripeEl.href = stripeUrl;
464
465 // Visually dim the link if it's still a placeholder
466
467 if (stripeUrl === 'https://buy.stripe.com/your-link') {
468
469 stripeEl.style.opacity = '.5';
470
471 stripeEl.title = 'Add ?stripe=YOUR_LINK to set the real payment URL';
472
473 }
474
475 ```
476
477 ## Print Styles
478
479 ```css
480
481 @page { size: letter; margin: 0; }
482
483 @media print {
484
485 body { background: \#fff; padding: 0; }
486
487 .print-bar { display: none; } /* Hide URL bar and buttons */
488
489 .invoice-page {
490
491 width: 100%;
492
493 height: 100vh;
494
495 box-shadow: none;
496
497 overflow: hidden;
498
499 }
500
501 }
502
503 ```
504
505 ## Best Practices
506
507 1. **Show the due date as an actual date** — "Due: April 13, 2026" not just "Net 30" (clients miscount)
508 2. **Specific line items** — "NiDO Plan Pro — Monthly Subscription" not "Subscription"
509
510 3. **Payment instructions on the invoice itself** — Stripe link, bank details, or payment link
511 4. **Always ask for billing email and production domain** — never invent placeholders
512
513 5. **Right-align all numbers** — amounts, quantities, rates, totals
514 6. **Bold the total due** — it should be the most visually prominent number on the page
515
516 7. **Highlight missing required fields in amber** — if `client`param is missing, color the field`#D97706` as a visual warning to the sender
517 8. **Auto-fill dates dynamically** — invoice date defaults to today, due date to today + 30 days, service period to next calendar month. Only override if the user passes explicit date params.
518
519 9. **Dates: use noon (`T12:00:00`) when parsing ISO dates** — avoids timezone off-by-one errors where`2026-04-01` renders as March 31
520
521 ## Limitations
522
523 - Cannot send invoices, process payments, or track payment status
524 - Tax calculation is flat-rate per invoice — doesn't handle mixed VAT rates per line or US multi-state nexus
525
526 - Not a substitute for accounting software; no ledger integration
527 - Puppeteer / headless Chrome does not work on NixOS — use `window.print()` (browser print → Save as PDF) instead