Tax & compliance
Invoices and credit memos
Generated from docs/plan/core/invoices-and-credit-memos/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Core-free tax and accounting documents. Currently /receipt is a well-designed order confirmation, but it is explicitly not a tax invoice. Invoices are legal documents required by most jurisdictions for B2B sales and for VAT purposes. Credit memos are issued when a full or partial refund happens. Both must carry permanent serial numbers, signatures (digital), and legally-mandated fields (merchant VAT ID, customer VAT ID, invoice date, issue date, payment terms, etc.). This is not optional in most countries.
The problem
Receipt at /receipt is well-designed but explicitly NOT a tax invoice. No credit memo for refunds. Refund ledger exists but accounting documents do not. For B2B sales and VAT reporting, I have no legal invoice to send the customer. Tax audits require invoices; I cannot produce them.
What it does
- Invoice entity (separate from receipt): created automatically at order capture (B2B) or at first payment received (B2C, per local law)
- Invoice serial number: auto-incrementing per year (INV-2025-001, INV-2025-002) or configurable format; stored permanently
- Invoice fields: merchant details (name, address, VAT ID if applicable), customer details (name, address, VAT ID if B2B), order items, tax breakdown (per rate), total, payment terms, due date, issue date, invoice date
- Credit memo: issued automatically when a refund is recorded; references the original invoice; has its own serial number (CM-2025-001)
- Digital signature: invoice is signed with merchant’s private key (optional, if certificate available); signature is embedded or QR-linked
- Invoice PDF export: generated dynamically and also stored (audit trail); can be re-generated if lost
- Settings: merchant can customize invoice template (add logo, custom footer text, terms and conditions), choose numbering scheme, set invoice issue rules (B2B only vs. all orders)
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- No automated delivery to customer (email, SMS) — merchant must send invoice manually or via integrations (reason: email delivery is a separate concern; AstroBaaS stores the invoice only)
- No multi-country templates — one template per shop (reason: each country has specific legal fields; true multi-country requires country-specific rendering, which is out of scope for core)
Data model
New Invoice entity: {id, order_id, invoice_number (string, unique per year), issue_date, payment_due_date, merchant_vat_id, customer_vat_id, items (array of {product, qty, unit_price, tax_rate, tax_amount, line_total}), total_tax, total_amount, currency, status (draft|issued|paid|cancelled)}. New CreditMemo entity: {id, invoice_id, memo_number, issue_date, refund_amount, reason, items_refunded}. Settings: invoice_template (HTML), invoice_numbering_scheme (string pattern), invoice_issue_rule (B2B|all|manual).
API
- GET /api/orders/:id/invoice → Invoice (PDF or JSON)
- GET /api/invoices/:number (fetch by invoice number)
- POST /api/invoices (admin: manually create invoice for an order)
- POST /api/invoices/:id/send-email { email } (send PDF to customer)
- GET /api/invoices/:id/credit-memo (fetch associated credit memo if refunded)
- GET /api/invoices (paginated, searchable by number/date)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Orders table/detail gains ‘Invoice’ column/section showing invoice number and status (draft/issued/paid). Invoice list page: searchable by number, date range, customer. Each invoice can be viewed (PDF or HTML), re-generated, emailed, or marked paid. Settings → Invoices: upload logo, customize template HTML, choose numbering scheme (INV-YYYY-NNN or custom), set issue rule.
The seam — why this is core
Core owns invoice entity, automatic generation at order capture, serial numbering, and PDF rendering. Merchants own template customization and invoice delivery (sending to customer is optional but merchant’s responsibility for audit compliance).
Legal and tax requirement, not optional. Must be in core.
Dependencies
- Order entity (core)
- Refund tracking (core)
- PDF rendering library (core)
- Settings (core)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- B2B order is captured; invoice is automatically created with serial number INV-2025-001 and status ‘issued’
- Invoice PDF displays merchant VAT ID, customer VAT ID, itemized tax breakdown, total amount, issue date
- Refund of 50% of the order generates credit memo CM-2025-001 referencing invoice INV-2025-001
- Admin can regenerate invoice PDF if the original is lost; PDF shows same data and invoice number
- Invoice template upload: merchant uploads HTML file with placeholders {{invoice_number}}, {{customer_name}}, {{items}}, {{total}}; rendered invoice displays the data
- Admin can search invoices by number (INV-2025-001), date range, or customer and view/download PDF
Risks
Invoice serial number collision if two orders are captured in parallel (need unique constraint + transaction). Refund is issued before invoice is created (edge case; need idempotency). Merchant loses invoice PDF; regeneration may show current tax rates, not the rate charged at order time (need to store tax details at issue time). Merchant does not understand invoice requirements and issues illegal template (AstroBaaS cannot validate legality).
Commercial context
| Suggested price | Core |
| Rival anchor | Magento Open Source: issues real invoice and credit-memo documents free. |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.