DEV Community

taxgarden
taxgarden

Posted on

Python Function to Validate GSTIN Checksum: The Algorithm Behind India's 15-Digit GST Number

Every Indian GST registration number (GSTIN) has a checksum in the 15th character. For invoicing software, ERP integrations, or vendor onboarding tools, validating before save prevents downstream pain.

GSTIN STRUCTURE

Position 1-2
State code (01-38)
Position 3-12
PAN of the entity
Position 13
Entity number (1-9 or A-Z)
Position 14
Always 'Z'
Position 15
Checksum
THE ALGORITHM (modified Luhn with custom 36-character set):

```python def validate_gstin(gstin: str) -> dict: import re CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" gstin = gstin.strip().upper()

pattern = r'^[0-3][0-9][A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$' if not re.match(pattern, gstin): return {"valid": False, "error": "GSTIN format invalid"}

state_code = int(gstin[:2]) if not (1 <= state_code <= 38): return {"valid": False, "error": f"Invalid state code: {gstin[:2]}"}

def calculate_checksum(g14: str) -> str: total, factor = 0, 2 for char in reversed(g14): code = CHARS.index(char) addend = factor * code factor = 1 if factor == 2 else 2 addend = (addend // len(CHARS)) + (addend % len(CHARS)) total += addend return CHARS[(len(CHARS) - total % len(CHARS)) % len(CHARS)]

expected = calculate_checksum(gstin[:14]) if expected != gstin[14]: return {"valid": False, "error": f"Checksum mismatch: expected {expected}, got {gstin[14]}"}

return {"valid": True, "state_code": gstin[:2], "pan": gstin[2:12]}

Test for g in ["29AAACB2230M1ZP", "27AADCB2230M1ZA", "99AAACB2230M1ZP"]: print(g, validate_gstin(g)) ```

COMMON GOTCHAS

Always normalize to uppercase first
O vs 0 confusion from OCR on scanned invoices
Position 14 is always 'Z'; foreign OIDAR entities differ
State 37 = current AP, 28 = legacy AP (both valid)
For GST registration process and all state codes: https://taxgarden.in/blog/gst-registration-process-india-2026

#python #gst #india #fintech #validation

Top comments (0)