GSTN
GSTN (Goods and Services Tax Network) is the Government of India portal for filing GSTR-1 (outward supplies) and GSTR-3B (monthly summary) returns. The ERP can produce the filing-format JSON today; direct-API upload to GSTN is scaffolded as a stub and awaits production credentials.
Current status
The portal upload is a stub — calling it throws. The filing-format
JSON generator (src/lib/gstReports.ts) is live and drives the "Download
JSON" flow under Finance → GST, which produces a file suitable for the
offline utility. Most users will continue to use the offline flow until
the auth handshake below is implemented.
Current capability — filing-format JSON
File: src/lib/gstReports.ts.
This module is pure — it takes a joined InvoiceSource[] and a
LedgerSummarySource, and emits the schema shapes that the GSTN v2 offline
utility accepts. Section map (matches src/lib/gstReports.ts:25-36):
| Section | Meaning |
|---|---|
b2b |
Registered-B2B — buyer has a GSTIN |
b2cl |
B2C-Large — unregistered inter-state, invoice ≥ ₹2,50,000 |
b2cs |
B2C-Small — everything else B2C, aggregated per rate × state |
exp |
Exports — stubbed |
cdnr |
Credit / debit notes to registered parties |
cdnur |
Credit / debit notes to unregistered parties — stubbed |
hsn |
HSN summary aggregated by HSN × rate |
Deliberately not implemented (data not captured yet): at, atadj,
txp, doc_issue, nil.
Outputs are verified by src/lib/gstReports.test.ts — see that file for the
exact shape expectations per section.
HSN / SAC on line items
InvoiceLineSource (src/lib/gstReports.ts:45-58) exposes both hsnCode
and sacCode; the generator emits whichever is present. Many line items do
not yet carry an HSN — the generator emits whatever the invoice line
carries and does not fail. Backfilling HSN codes per product/service is a
standing TODO tracked outside this doc.
What the generator does NOT do
- No GSTIN checksum validation. Shape validation only — see
src/lib/gstin.tsbelow. The GST portal runs the real checksum on upload. - No HSN lookup. Whatever the invoice line stores is emitted verbatim.
- No export / reverse-charge handling beyond the section stubs above.
Portal upload — stubbed
File: src/lib/gstnApiClient.ts.
Both uploadGstr1 (gstnApiClient.ts:114-128) and uploadGstr3b
(gstnApiClient.ts:131-144) currently throw. The isGstnConfigured() helper
(gstnApiClient.ts:52-54) returns true only when every env var below is
set, so the frontend should disable any "File to GSTN" action until the
handshake lands.
Planned auth + encryption flow
Once production credentials arrive, the implementation inside uploadGstr1
will (per the comment block at gstnApiClient.ts:99-112):
- Request an OTP / refresh the session key via the EVP auth endpoint.
- Derive the rolling request key (RSA + AES-ECB — GSTN ASP/GSP spec §2.5).
- Encrypt the payload with the session key.
- POST to
${base}/taxpayerapi/v1.3/returns/gstr1withstate-cd,gstin,rtnprd, and the HMACauthorizationheader. - Parse
{ reference_id, status_cd, error_cd, error_m }.
The hmacSignPayload helper (gstnApiClient.ts:79-97) is already in place
for step 4 — it computes HMAC-SHA256 over a payload and returns the hex
digest.
Planned env vars
| Variable | Purpose |
|---|---|
VITE_GSTN_API_BASE |
https://api.sandbox.gst.gov.in (sandbox) or https://api.gst.gov.in (prod) |
VITE_GSTN_CLIENT_ID |
GSP client ID |
VITE_GSTN_CLIENT_SECRET |
GSP client secret — see warning below |
VITE_GSTN_FILER_GSTIN |
Our GSTIN, also used as x-filer-id |
VITE_GSTN_DEVICE_ID |
Registered device identifier |
The VITE_* names are placeholders
The VITE_GSTN_* prefix exists because the stub currently reads
import.meta.env in the browser. When the real flow is implemented the
secret and encryption steps MUST move server-side — either to an
edge function that talks to GSTN, or to an ASP-hosted intermediary.
Shipping VITE_GSTN_CLIENT_SECRET in the browser bundle is not
acceptable for production. Track this when the handshake lands.
Session state
The OTP-authenticated session token is ephemeral. The plan is to persist
it in a GstnSession Supabase table (see gstnApiClient.ts:21-22) so
that retries and scheduled filings can reuse a valid session. This table
does not exist yet.
GSTIN validator
File: src/lib/gstin.ts.
Two exported helpers:
normalizeGstin(value)— trims and uppercases. Canonical storage form.isValidGstin(value)— shape check against/^\d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d]Z[A-Z\d]$/after normalising.
Shape only — no checksum. The checksum requires the GSTN code32 table
and is validated server-side / by the portal. Every frontend form that
collects a GSTIN should call isValidGstin before submit; every persisted
GSTIN should round-trip through normalizeGstin.
Filing workflow today
- Finance → GST → select period (month + year).
- System loads invoices via the API handler and runs
src/lib/gstReports.tsto produce theGSTR-1/GSTR-3Bpayload. - User clicks Download JSON and uploads to the GST portal offline utility. Portal runs the checksum + business validations and returns errors inline — fix in the ERP, regenerate, re-upload.
Once the direct API flow is ready, a File to GSTN button will replace
(or sit alongside) the download, gated on isGstnConfigured().