Skip to content

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.ts below. 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):

  1. Request an OTP / refresh the session key via the EVP auth endpoint.
  2. Derive the rolling request key (RSA + AES-ECB — GSTN ASP/GSP spec §2.5).
  3. Encrypt the payload with the session key.
  4. POST to ${base}/taxpayerapi/v1.3/returns/gstr1 with state-cd, gstin, rtnprd, and the HMAC authorization header.
  5. 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

  1. Finance → GST → select period (month + year).
  2. System loads invoices via the API handler and runs src/lib/gstReports.ts to produce the GSTR-1 / GSTR-3B payload.
  3. 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().