If you've ever built billing software for India, you've hit the question: when does GST split into CGST + SGST, and when is it a single IGST? Get it wrong and the invoice is wrong — even if the total looks right.
I built a small GST invoice generator in pure Python (standard library only) to get this math exactly right. Here's the logic, with a real worked example whose grand total is ₹15,522.
The one rule that decides everything
Compare two state codes:
- Seller's state code == place-of-supply state code → intra-state → tax splits in half: CGST + SGST
- They differ → inter-state → one single tax: IGST
That's it. The customer's address doesn't decide it — the place of supply does (for goods, usually where they're delivered). In code:
intra = shop_state_code == place_of_supply_state_code
Per-line math
For each item: taxable = qty × rate, then tax = taxable × GST%. The split is where people slip:
from decimal import Decimal, ROUND_HALF_UP
D = Decimal
CENT = D("0.01")
def money(x):
"""Round to 2 decimals, half-up (the way shops actually bill)."""
return D(x).quantize(CENT, rounding=ROUND_HALF_UP)
taxable = money(qty * rate)
tax = money(taxable * gst_percent / 100)
if intra:
cgst = money(tax / 2)
sgst = tax - cgst # <-- the important line
igst = D("0")
else:
cgst = sgst = D("0")
igst = tax
Two things to notice:
1. Never use floats for money. 0.1 + 0.2 != 0.3 in floating point, and those errors will surface on an invoice. Decimal with explicit half-up rounding is how real billing works.
2. The odd-paisa trick. Splitting ₹111.75 in half gives ₹55.875 — not a real amount. Round one half up (CGST ₹55.88) and set the other half to tax − cgst (SGST ₹55.87), so the two halves always add back to the exact tax. If you round both halves independently, invoices drift by a paisa and accountants come looking for you.
The worked example (real output)
An intra-state invoice — seller and place of supply in the same state — with five items at three different GST rates:
| Item | Qty | Rate | GST | Taxable | Tax |
|---|---|---|---|---|---|
| CTC Tea (1 kg) | 10 | ₹420 | 5% | ₹4,200.00 | ₹210.00 |
| Earthen Cups (pack of 12) | 20 | ₹60 | 12% | ₹1,200.00 | ₹144.00 |
| Chai Masala Combo Pack | 15 | ₹149 | 5% | ₹2,235.00 | ₹111.75 |
| Electric Kettle 1.8L | 3 | ₹1,299 | 18% | ₹3,897.00 | ₹701.46 |
| LED Bulb 9W (set of 4) | 8 | ₹299 | 18% | ₹2,392.00 | ₹430.56 |
Because it's intra-state, every tax above splits into CGST + SGST halves — including the chai masala's ₹111.75, which becomes CGST ₹55.88 + SGST ₹55.87 (spot the odd-paisa trick doing its job).
Tax summary by rate (this grouping is what goes on the invoice and into GST returns):
| GST rate | Taxable value | CGST | SGST | Total tax |
|---|---|---|---|---|
| 5% (2.5% + 2.5%) | ₹6,435.00 | ₹160.88 | ₹160.87 | ₹321.75 |
| 12% (6% + 6%) | ₹1,200.00 | ₹72.00 | ₹72.00 | ₹144.00 |
| 18% (9% + 9%) | ₹6,289.00 | ₹566.01 | ₹566.01 | ₹1,132.02 |
Totals:
Subtotal (taxable value) ₹13,924.00
Total GST ₹ 1,597.77
Grand total ₹15,521.77 → rounded ₹15,522 (round-off +₹0.23)
If the same five items were supplied to a customer in a different state, nothing about the taxable values changes — the same ₹1,597.77 simply appears as IGST instead of the CGST/SGST split, and the total stays ₹15,522. Same money, different tax heads — which is exactly why getting the split right matters for filing, not for the customer.
One more detail: the round-off
Invoices are paid in whole rupees, so the generator rounds the grand total (₹15,521.77 → ₹15,522) and shows the +₹0.23 round-off as its own line. Hiding the difference inside an item rate is how totals stop reconciling — display it openly instead.
Full code
The complete generator — JSON or CSV in, print-ready HTML invoice out, with a per-rate tax summary and the amount in words (Indian numbering: lakh/crore) — is on GitHub: gst-invoice-generator. Run it with the bundled sample and you'll get exactly the ₹15,522 invoice above.
Demo first — on YOUR sample — before you pay anything. Need invoice automation, spreadsheet cleanup, or a scraping/automation tool for your business? Send me a sample of your data and I'll show you a working demo first.
👉 My Python web scraping & automation gig on Fiverr
— Abhishek, Python Automation Developer
Top comments (0)