# Numbered Invoices for GoCommerce — Open-Source Module

> Issue a gapless, numbered invoice for every paid GoCommerce order: seller details, GSTIN or VAT number, the number format, and what the document leaves out.

- Canonical: https://kitcommerce.store/integrations/invoices/
- Last updated: 2026-09-25

---

Platform

## Numbered invoices for GoCommerce

A Go package that issues an invoice, with its own gapless number, the moment an order is paid — and keeps a snapshot of the order as it was. The seller’s name, address and tax id are its settings, set in code.

- **Platform** module
- **5** settings
- **10** tests
- **2** API operations
- **ext/invoices**

### What it does

The module listens for order.paid and issues an invoice for that order: a number from its own per-year sequence, the order’s total and currency, and a snapshot of the whole order as it stood. The admin gets an Invoices screen and a link from each order; the API gets two read-only routes.

The package doc explains why it is a module at all: an order is what a shopper asked for, while an invoice is an accounting document with its own gapless sequence and its own retention rules. Keeping them apart is why this is a module and not a column on the order.

It needs no third party and talks to nothing outside the store. It prints a tax id beside the seller — a GSTIN, a VAT number, whatever applies — and serves the invoice as a page to print or as JSON.

### Configuration

One setting is required: the seller’s name. All five are Config fields — this module has no settings in the admin’s panel — so they are set in your own main(), or through the reference binary’s -invoices flag and its three environment variables.

*Invoices module settings — 5 settings, 1 required*

| Setting | Environment variable | Required | What it does |
| --- | --- | --- | --- |
| `SellerName` | `INVOICES_SELLER_NAME` | Yes | Heads the document. Without it the module refuses to register and the store does not start; the reference binary falls back to “This store”. |
| `SellerAddress` | `INVOICES_SELLER_ADDRESS` | No | Printed under the seller’s name. The code’s comment calls it required, but only the name is checked. |
| `TaxID` | `INVOICES_TAX_ID` | No | Printed beside the seller, for jurisdictions that need it — a GSTIN, a VAT number, whatever applies. |
| `NumberFormat` | — | No | Builds the number from {year} and {seq}; {seq:05} pads to five digits. Defaults to INV-{year}-{seq:05}. |
| `Footer` | — | No | Printed at the bottom of every invoice. |

The module reads its Config struct, not the environment. The variable names are the ones the package’s own example or the reference binary uses; in your own `main()` you choose where each value comes from. Where a setting has a panel label, the admin’s settings drawer can hold it too, and a value typed there wins over Config.

### Setting it up

1. **Choose the number format** — The default is INV-{year}-{seq:05}, which gives INV-2026-00001. Settle yours before the first invoice; the count restarts at 1 each calendar year.
2. **Install the module** — Import it and pass invoices.New to gocommerce.New with at least SellerName, as below — or run the reference binary with -invoices and the INVOICES\_ variables.
3. **Take a payment** — Every order that reaches paid gets its invoice. On start-up, paid orders that have none are caught up.
4. **Print or fetch it** — Open it from the order or the Invoices screen in the admin, or GET /api/admin/x/invoices/{orderId} — HTML to print, or JSON with Accept: application/json.

main.go

```
import (
	"github.com/itswadesh/gocommerce/core"
	"github.com/itswadesh/gocommerce/ext/invoices"
)

app, err := gocommerce.New(cfg,
	invoices.New(invoices.Config{
		SellerName:   "Example Ltd",
		NumberFormat: "INV-{year}-{seq:05}",
	}),
)
```

The package doc’s own example, with its imports; cfg is your gocommerce.Config. Add SellerAddress, TaxID and Footer the same way. Import path `github.com/itswadesh/gocommerce/ext/invoices`.

### How it works

- **Issued on payment, once**

  The module subscribes to order.paid. Event delivery is at least once, so a unique constraint on the order id turns a repeated event into a no-op rather than a second invoice.

- **Gapless, per year**

  The number comes from a counter row per year, taken under a lock inside the transaction that writes the invoice. A rollback gives the number back, which a PostgreSQL sequence would not.

- **Your format**

  NumberFormat expands {year} — the UTC calendar year — and {seq}, with {seq:05} padding to five digits. Two deliveries at once queue for the counter rather than share a number.

- **A snapshot of the order**

  The order is stored as JSON as it stood when the invoice was issued, so later edits to the order do not change the invoice’s lines or totals.

- **Nothing missed at start-up**

  When the store starts, the module looks for paid orders with no invoice and issues them — the safety net under at-least-once delivery.

- **HTML or JSON, behind a right**

  The invoice route answers with a printable page, or JSON when asked. Reading needs the invoices.read right, which the manager and staff roles hold by default.

### What it does not do

Read these before an order depends on it. No store is known to run GoCommerce in production yet, so these come from the code, not from anyone’s experience.

- **No tax line** — The order’s tax is in the snapshot, but the printed invoice shows subtotal, shipping, discount and total only. With tax-exclusive prices, the lines shown do not add up to the total.
- **Amounts in minor units** — The page prints every amount as a whole number of minor units — INR 123450 for ₹1,234.50 — and says so at its foot.
- **Seller details are read live** — The seller block comes from Config when an invoice is viewed, not from the snapshot. Change the address and every past invoice shows the new one.
- **No PDF, no email** — The invoice is an HTML page to print or JSON to process. The module renders no PDF and does not send the invoice to the shopper.
- **No credit notes** — A refund issues nothing and changes nothing: the module listens for order.paid only.
- **500 at a time on start-up** — The start-up catch-up issues invoices for at most 500 paid orders. A larger backlog is cleared over several restarts.

FAQ

### Questions about the Invoices module

**Is it a GST invoice?**

It prints your GSTIN if you set TaxID, and nothing more GST-specific: no HSN or SAC codes, no place of supply, no CGST, SGST or IGST lines — and, as it stands, no tax line at all. Whether a document meets your invoicing rules is a question for your accountant; the template in invoices.go is where to change it.

**When does a cash-on-delivery order get its invoice?**

When it is marked paid, because the module listens for order.paid. A cash-on-delivery order has no invoice until an operator records the payment.

**Can the numbering have gaps?**

Not from this module. The counter is taken under a row lock inside the transaction that writes the invoice, so a failed write gives its number back. The count restarts at 1 each calendar year, in UTC.

**Does the shopper receive the invoice?**

Not from this module. It issues the invoice, stores it and serves it to the admin and the API; sending it to the shopper is not in the code.

**Why is an invoice not just a field on the order?**

The package doc answers it: an order is what a shopper asked for, while an invoice is an accounting document with its own gapless sequence and its own retention rules. Keeping them apart is why this is a module.

### Source

Everything on this page is read from [`ext/invoices`](https://github.com/itswadesh/gocommerce/tree/main/ext/invoices) in the GoCommerce repository, MIT licensed. When this page and the code disagree, the code is right and this page is out of date.

- [ext/invoices on GitHub](https://github.com/itswadesh/gocommerce/tree/main/ext/invoices)
- [All GoCommerce modules](https://kitcommerce.store/integrations/)

### Try it against a store of your own

The one-command stack gives you GoCommerce’s API and admin on your own machine in minutes. Add this module to it and try it on test orders before a real one depends on it.

[Deploy in minutes](https://kitcommerce.store/#one-command) · [Read the module](https://github.com/itswadesh/gocommerce/tree/main/ext/invoices) · [All integrations](https://kitcommerce.store/integrations/)
