Invoice Module — Admin
HTTP surface for admin invoice access — async generation with SSE readiness notification, per-vendor GST tax invoices, lazily generated and cached; check status, enqueue, stream, download, or force regeneration for any order.
HTTP surface for platform-admin invoice access. Invoices are issued per vendor sub-order (the Amazon model): a multi-vendor order produces one tax invoice per seller, each under that vendor's own GSTIN. Generation is lazy and async: the first POST …/generate call enqueues a background render job; an SSE stream reports when it is ready; repeat downloads stream the cached PDF instantly. Admins can access any order and force a regeneration.
Source:
api-modules/invoice/src/controllers/admin-invoices.controller.ts.Invoices are lazily materialised: the row (with its immutable, per-(vendor, financial-year) serial) and the rendered PDF are created on first generation and cached in object storage. Later downloads stream the cached PDF; a regenerate re-renders against the same serial (a GST serial never changes). The seller identity (legal name, GSTIN, address) is snapshotted at first issue from
vendor_profile; templated parts (logo, declaration, customer-care lines, QR, authorised signatory, currency symbol, serial prefix) come from the admininvoicesettings group. The logo falls back tostore.branding.logo_urlwhen unset.
Conventions
Authentication
All endpoints require a Better-Auth admin session and a role granting the matching permission.
| Permission | Grants |
|---|---|
invoice:view | Status checks, enqueue, download, and stream for any order. |
invoice:regenerate | Force a fresh render against the same serial. |
The SSE stream (/stream) is authenticated via the session cookie, which the browser EventSource sends automatically.
Response shape
- Status / generate endpoints return the standard JSON envelope:
{ data, message, statusCode }. - Download (
GET …/invoice) streams rawapplication/pdfbytes withContent-Disposition: attachment— no JSON envelope. - SSE stream (
GET …/invoice/stream) returnstext/event-stream— no JSON envelope.
Generation flow
POST …/invoice/generate (invoice:view)
│
├─ status: "ready" ──► GET …/invoice (streams cached PDF)
│
└─ status: "pending" ──► open GET …/invoice/stream
│
├─ { status:"pending", heartbeat:true } (periodic)
├─ { status:"ready", downloadUrl } ──► GET …/invoice
└─ { status:"failed", error }On page load, call GET …/invoice/status to render the current state without triggering a render.
Endpoints — Order level
Base path: /admin/orders/:orderId/invoice…
Check order invoice status
GET /admin/orders/:orderId/invoice/status · invoice:view
Returns the merged status across all vendor sub-orders for the given order, without enqueuing anything.
Response (data field):
{
"status": "ready" | "pending" | "failed" | "none",
"invoiceNumber": null, // null at order level (per-vendor)
"downloadUrl": "…" | null, // set only when ready
"streamUrl": "…" | null, // set only when pending or failed
"jobId": "…" | null
}status: "none" is returned when the order is still in pending_payment.
Enqueue order invoice generation
POST /admin/orders/:orderId/invoice/generate · invoice:view
Enqueues render jobs for all non-cancelled vendor sub-orders if not already cached. Idempotent.
Always returns HTTP 200. Response (data field):
{
"status": "ready" | "pending",
"jobId": "…",
"downloadUrl": "…" | null,
"streamUrl": "…" | null
}Download order invoice (all vendors merged)
GET /admin/orders/:orderId/invoice · invoice:view
Streams one PDF merging every non-cancelled vendor sub-order's invoice. Returns 409 while still generating. Filename: invoice-{orderNumber}.pdf.
Stream order invoice readiness (SSE)
GET /admin/orders/:orderId/invoice/stream · invoice:view
Opens a Server-Sent Events connection. Each data: frame is JSON:
{ "status": "pending", "heartbeat": true } // periodic
{ "status": "ready", "downloadUrl": "…" } // terminal
{ "status": "failed", "error": "…" } // terminalCloses after ~60 s. Re-check GET …/invoice/status and reconnect if still pending.
Regenerate order invoices
POST /admin/orders/:orderId/invoice/regenerate · invoice:regenerate
Forces a fresh render for every vendor sub-order of the order, against their existing immutable serials. Overwrites the cached PDFs. Returns the same shape as POST …/generate.
Endpoints — Sub-order level
Base path: /admin/orders/:orderId/vendors/:orderVendorId/invoice…
Check sub-order invoice status
GET /admin/orders/:orderId/vendors/:orderVendorId/invoice/status · invoice:view
Returns the status for a single vendor sub-order without enqueuing anything.
Response (data field):
{
"status": "ready" | "pending" | "failed" | "none",
"invoiceNumber": "INV-2526-000001" | null,
"downloadUrl": "…" | null,
"streamUrl": "…" | null,
"jobId": "…" | null
}Enqueue sub-order invoice generation
POST /admin/orders/:orderId/vendors/:orderVendorId/invoice/generate · invoice:view
Enqueues the render job for the vendor sub-order if not already cached. Idempotent.
Always returns HTTP 200. Response (data field):
{
"status": "ready" | "pending",
"jobId": "…",
"downloadUrl": "…" | null,
"streamUrl": "…" | null
}Download a single vendor's invoice
GET /admin/orders/:orderId/vendors/:orderVendorId/invoice · invoice:view
Streams the cached application/pdf when ready. Returns 409 while still generating. Filename: invoice-{serial}.pdf (e.g. invoice-INV-2526-000001.pdf).
Stream sub-order invoice readiness (SSE)
GET /admin/orders/:orderId/vendors/:orderVendorId/invoice/stream · invoice:view
Opens a Server-Sent Events connection. Same frame shape as the order-level stream:
{ "status": "pending", "heartbeat": true }
{ "status": "ready", "downloadUrl": "…" }
{ "status": "failed", "error": "…" }Closes after ~60 s. Re-check status and reconnect if needed.
Regenerate a single vendor's invoice
POST /admin/orders/:orderId/vendors/:orderVendorId/invoice/regenerate · invoice:regenerate
Forces a fresh render for the vendor sub-order against its existing immutable serial. Overwrites the cached PDF. Returns the same shape as POST …/generate.
Configuration
Invoice presentation and numbering live under the admin settings group invoice (see the Settings docs): title, logo_url, number_prefix (≤ 4 chars; serial is {prefix}/{FY}/{000001}), currency_symbol, declaration_text, customer_care_lines, show_authorized_signatory, authorized_signatory_label, authorized_signatory_image_url, show_qr, qr_image_url, footer_note.
Google Merchant Center Module — Admin
HTTP surface for syncing eligible product variants to a Google Merchant Center account via the Merchant API v1 (the canonical successor to the deprecated Content API for…
Klaviyo Marketing Module — Admin
HTTP surface for the Klaviyo marketing-automation integration — connect/disconnect the Klaviyo account, inspect each outbound sync surface (profiles, events, product catalog,…