const GSTIN_SHAPE = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/ const B36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' /** Standard GSTIN check digit: alternating 1/2 factors over base-36 values. */ export function gstinCheckDigit(first14: string): string { let sum = 0 for (let i = 0; i < 14; i++) { const v = B36.indexOf(first14[i]!) const product = v * (i % 2 === 0 ? 1 : 2) sum += Math.trunc(product / 36) + (product % 36) } return B36[(36 - (sum % 36)) % 36]! } export function validateGstin(gstin: string): { ok: boolean; stateCode?: string; reason?: string } { const g = gstin.toUpperCase().trim() if (!GSTIN_SHAPE.test(g)) return { ok: false, reason: 'shape' } if (gstinCheckDigit(g.slice(0, 14)) !== g[14]) return { ok: false, reason: 'checksum' } return { ok: true, stateCode: g.slice(0, 2) } } /** Minimal buyer identity a place-of-supply decision needs. */ export interface SupplyCustomer { gstin?: string /** 2-digit GST state code; when absent it is read from the GSTIN's first two digits. */ stateCode?: string } export interface SupplyDecision { /** True when the buyer carries a GSTIN — a registered B2B sale. */ b2b: boolean /** GST state code that is the place of supply (buyer's for B2B, the store's for B2C). */ placeOfSupplyStateCode: string /** Place of supply differs from the store ⇒ the IGST path; else CGST+SGST. */ interState: boolean /** Buyer GSTIN snapshot (upper-cased) when this is a B2B sale. */ buyerGstin?: string } /** * Place-of-supply decision at the counter — the single rule the POS and the * store server must agree on so their engine runs match to the paisa (R5). * A buyer with a GSTIN is B2B: place of supply is the buyer's state (an explicit * stateCode, else the GSTIN's first two digits). Everyone else is a B2C * over-the-counter sale — place of supply is the store. Inter-state ⇒ IGST. */ export function deriveSupply(customer: SupplyCustomer | undefined, storeStateCode: string): SupplyDecision { const gstin = customer?.gstin?.toUpperCase().trim() const b2b = gstin !== undefined && gstin !== '' const buyerState = customer?.stateCode !== undefined && customer.stateCode !== '' ? customer.stateCode : b2b ? gstin.slice(0, 2) : undefined const placeOfSupplyStateCode = b2b && buyerState !== undefined ? buyerState : storeStateCode return { b2b, placeOfSupplyStateCode, interState: placeOfSupplyStateCode !== storeStateCode, ...(b2b ? { buyerGstin: gstin } : {}), } }