Platform
Order and GST tax invoices in Go
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. A store in India with a valid GSTIN that sells in rupees issues a GST tax invoice instead, and downloads its invoices as files for TallyPrime.
- Platform module
- 5 settings
- 43 tests
- 5 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 yearly 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 five routes: the list, one invoice, the Tally export, and reading and setting the Tally ledger names.
A store whose profile is in India, whose tax registration is a valid GSTIN and which sells in rupees issues a GST tax invoice instead. It is built once, from what checkout charged, and stored as a document that does not change: supplier and buyer with their GSTINs, each variant’s HSN code, the place of supply from the delivery address, CGST and SGST (UTGST in a union territory without a legislature) within a state and IGST between states and for exports, a summary by rate and the totals. Every other store gets the plain invoice it always did.
On a GST store the Invoices screen also has an Export for Tally card. A period’s tax invoices download as two TallyPrime “Import Data” XML files, ledgers then vouchers. Each invoice becomes one sales voucher, a buyer with a GSTIN becomes a Sundry Debtors ledger named by legal name and GSTIN, and the ledger names are set once to match the accountant’s company.
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. It needs no third party and talks to nothing outside the store.
Configuration
One setting is required: the seller’s name. All five are Config fields, set in your own main() or through the reference binary’s -invoices flag and its three environment variables. A GST store takes its seller details from the store’s profile under Settings instead, and keeps its Tally ledger names on the Invoices screen.
| Setting | Environment variable | Required | What it does |
|---|---|---|---|
SellerName | INVOICES_SELLER_NAME | Yes | Heads the plain invoice. Without it the module refuses to register and the store does not start; the reference binary falls back to “This store”. A GST tax invoice names the supplier from the store’s profile instead. |
SellerAddress | INVOICES_SELLER_ADDRESS | No | Printed under the seller’s name on the plain invoice. The code’s comment calls it required, but only the name is checked. |
TaxID | INVOICES_TAX_ID | No | Printed beside the seller on the plain invoice: a VAT number or whatever applies. A store’s GST mode comes from the tax registration in its profile, not from this field. |
NumberFormat | — | No | Builds the number from {year}, {fy} and {seq}; {seq:05} pads to five digits. Defaults to INV-{year}-{seq:05}. On a GST store the count restarts on 1 April, {year} is the year the financial year began and {fy} prints it as 26-27, and a number longer than 16 characters, or with anything other than letters, digits, / and -, is refused: no invoice is issued. |
Footer | — | No | Printed at the bottom of every plain 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
- Choose the number formatThe default is INV-{year}-{seq:05}, which gives INV-2026-00001. Settle yours before the first invoice. A plain store’s count restarts each calendar year, a GST store’s on 1 April; INV/{fy}/{seq:05} is the usual Indian shape.
- Install the moduleImport 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.
- For GST, fill in the profileUnder Settings: India, rupees, the GSTIN as the tax registration, the legal name and the address. Give each variant its HS code, which a GST invoice prints as the HSN.
- Print, fetch or exportOpen an invoice from its order or the Invoices screen, or GET /api/admin/x/invoices/{orderId}: HTML to print, or JSON with Accept: application/json. A GST store downloads its Tally files from the same screen.
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. A sample order gets none.
-
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. A GST store counts by financial year, on the store’s clock.
-
GST from what checkout charged
A GST invoice works out no tax of its own: each line’s rate and tax are what checkout charged, and a tax rate can apply to one product. Within a state CGST is half the tax rounded down and SGST or UTGST the rest, so the heads always add up; between states it is all IGST.
-
Refused rather than wrong
A GST invoice whose lines do not add up to the order total, or whose Indian delivery address names no state GST knows, is not issued. The paid order stays without one, logged, until the cause is fixed. Checkout on a GST store refuses a buyer GSTIN that does not parse, and an Indian address with no state.
-
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. A GST invoice keeps its whole document, seller included, as issued.
-
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.
-
Tally files that import once
The export reads only the issued GST documents and adds them up, at most a year at a time. Each voucher carries a REMOTEID, gocommerce:<store>:<invoice number>, so importing the same file again with Tally’s overwrite option replaces vouchers instead of duplicating them. A voucher whose entries do not sum to zero is refused.
-
Behind its own rights
Reading invoices and downloading the Tally files needs invoices.read, which the manager and staff roles hold by default. Setting the Tally ledger names needs invoices.configure, which managers hold.
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 e-invoices or e-way billsA GST invoice carries no IRN or signed QR code, and no e-way bill is made. Both need an account with a GST Suvidha Provider or the Invoice Registration Portal.
- Delivery without GSTCheckout does not charge GST on delivery, so a GST invoice prints delivery as an untaxed line and flags it for the accountant.
- No credit notesA refund issues nothing and changes nothing: the module listens for order.paid only. There are no GST credit notes, no bills of supply and no cess, and every line’s unit is NOS.
- Tally files not yet importedThe files follow Tally’s published format, but they have not yet been test-imported into TallyPrime itself. Tally’s help does not show a party ledger’s GST registration or a voucher’s GST tags as XML, so the party ledger carries none, and the accountant sets each tax ledger’s duty head in Tally once.
- No tax line on the plain invoiceOutside GST mode, 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.
- Minor units on the plain invoiceThe plain invoice prints every amount as a whole number of minor units, INR 123450 for ₹1,234.50, and says so at its foot. The GST invoice prints rupees and paise.
- Plain seller details are read liveOn the plain invoice the seller block comes from Config when the invoice is viewed, not from the snapshot. Change the address and every past plain invoice shows the new one.
- No PDF, no emailThe 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, and buyers in the dealer portal do not see it.
- 500 at a time on start-upThe 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?
Yes, on a store in India with a valid GSTIN that sells in rupees: a tax invoice with both GSTINs, HSN codes, the place of supply and the CGST, SGST or IGST split from what checkout charged, numbered by financial year. It is not an e-invoice: there is no IRN and no e-way bill. Every other store gets a plain invoice that prints the seller’s tax id and no tax line.
Does it connect to Tally?
It exports to it. A GST store downloads a period’s tax invoices as two TallyPrime import files, at most a year at a time, and the accountant imports the ledgers first, then the vouchers. Nothing is sent to Tally by itself, and the files have not yet been test-imported into TallyPrime.
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, or a dealer’s order on account, has no invoice until someone 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. A plain store’s count restarts at 1 each calendar year, in UTC; a GST store’s on 1 April, on the store’s clock.
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.
Source
Everything on this page is read from 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.TallyPrime is a trademark of its owner; the export writes Tally’s published import format and implies no endorsement. See trademarks.
Try it against a store of your own
The backend + admin quick start runs GoCommerce’s API and admin on your own machine, without a storefront. Add this module to it and try it on test orders before a real one depends on it.