Indian business essentials for .NET: validate GSTIN (with its check digit), PAN, TAN, Aadhaar, IFSC, PIN codes and mobile numbers; format rupees the Indian way (₹12,34,567.00 · ₹4.2 L · "Rupees … Only"); calculate GST; work with financial years. Zero dependencies on .NET 8, culture-independent, AOT- and trimming-friendly.
Every Indian ERP, invoicing or KYC system rewrites the same helpers, usually with a regex that accepts GSTINs with typos,
ToString("N2") that groups digits the Western way on Linux servers, and an amount-in-words function copied from a forum.
IndiaKit does these properly, once, with tests:
- Real checksums, not just regexes. GSTIN uses the GSTN mod-36 check digit and Aadhaar the Verhoeff algorithm. The test suite proves every single-character typo in a GSTIN (all 525 of them) and every adjacent-digit swap in an Aadhaar number is caught.
- Errors you can show users.
Check()says what's wrong: "Invalid check digit: expected 'V', got 'X'. Check for a typo." - Same output on every server. Lakh/crore grouping is implemented directly, not borrowed from the OS culture
(
en-INdata differs between Windows, ICU and invariant-mode containers).
dotnet add package IndiaKit
dotnet add package IndiaKit.FluentValidation # optional: FluentValidation rulesusing IndiaKit.Identifiers;
using IndiaKit.Money;
using IndiaKit.Gst;
using IndiaKit.Dates;
Gstin.Check("27AAPFU0939F1ZV"); // Valid
Gstin.Check("27AAPFU0939F1ZX"); // Invalid check digit: expected 'V', got 'X'. Check for a typo.
var gstin = Gstin.Parse("27 aapfu 0939f 1zv"); // spaces / lower case tolerated
// 27AAPFU0939F1ZV -> Maharashtra, PAN AAPFU0939F (Firm)
Aadhaar.Mask("2341 2341 2346"); // XXXX XXXX 2346
IndianMobile.ToE164("098765 43210"); // +919876543210
Ifsc.BankCode("HDFC0001234"); // HDFC
Rupees.Format(12345678.9m); // ₹1,23,45,678.90
Rupees.FormatCompact(4_250_000m); // ₹42.5 L
Rupees.ToWords(118000m); // Rupees One Lakh Eighteen Thousand Only
var interState = GstCalculator.IsInterState(supplierStateCode: "27", placeOfSupplyStateCode: gstin.StateCode);
GstCalculator.Calculate(100_000m, 18m, interState);
// Taxable 100000.00 + CGST 9000.00 + SGST 9000.00 = 118000.00
GstCalculator.FromInclusive(1180m, 18m, interState: true);
// Taxable 1000.00 + IGST 180.00 = 1180.00
var fy = FinancialYear.For(new DateTime(2026, 10, 12));
// FY 2026-27 | AY 2027-28 | Q3 | invoice prefix INV/2026-27/That output is real: it's samples/IndiaKit.Sample, which you can run with dotnet run --project samples/IndiaKit.Sample.
DataAnnotations (included in the main package). Empty values pass, so combine with [Required] when needed:
using IndiaKit.Validation;
public sealed class VendorForm
{
[Required, Gstin] public string? Gstin { get; set; }
[Pan] public string? Pan { get; set; }
[IndianMobile] public string? Mobile { get; set; }
[Pincode(ErrorMessage = "Enter a 6-digit PIN code")] public string? Pin { get; set; }
}Errors carry the precise reason ("Invalid check digit: expected 'V', got 'X'") unless you set ErrorMessage.
Available: [Gstin], [Pan], [Tan], [Aadhaar], [Ifsc], [Pincode], [IndianMobile].
FluentValidation (IndiaKit.FluentValidation, works with FluentValidation 11 and 12):
using IndiaKit.FluentValidation;
RuleFor(v => v.Gstin).NotEmpty().GstinInState(v => v.StateCode); // "GSTIN is registered in Maharashtra, not Karnataka."
RuleFor(v => v.Pan).Pan();
RuleFor(v => v.Mobile).IndianMobile();| Area | Types | Highlights |
|---|---|---|
| Identifiers | Gstin, Pan, Tan, Aadhaar, Ifsc, Pincode, IndianMobile, Verhoeff |
Check (reason), IsValid, TryParse / Parse; GSTIN → state, PAN, entity number; PAN → holder type; masking; E.164 mobiles |
| GST | GstCalculator, GstLine, GstBreakup, GstStates |
per-line rounding to paise, CGST/SGST split that always reconciles, inclusive-price back-calculation, state-code table with names and abbreviations |
| Money | Rupees |
Format, FormatCompact (K / L / Cr), ToWords, NumberToWords |
| Dates | FinancialYear |
For(date), Current (IST), Start/End, assessment year, QuarterOf, MonthOf, parsing "FY 2026-27" / "2026-27" |
GST rates: accepts 0, 0.25, 3, 5, 18 and 40%, plus 12% and 28% so that invoices dated before the September 2025 rate rationalisation can still be calculated.
Privacy: only the format of an Aadhaar number is checked, never whether it was issued. Aadhaar and PAN are sensitive
personal data: store them encrypted and display them masked (Aadhaar.Mask, Pan.Mask).
readonly structvalue types forGstin,PanandFinancialYear: no allocations beyond the normalised string, equality by value.- Multi-targets netstandard2.0 (.NET Framework 4.6.1+, Xamarin, Unity) and net8.0, which is marked
IsTrimmableandIsAotCompatible. - Packages ship with SourceLink, deterministic builds, symbol packages (
.snupkg) and XML docs, and use package validation to guard the public API.
dotnet test # 105 tests
dotnet pack -c Release -o artifactsReleases are published from GitHub Actions with NuGet Trusted Publishing (OIDC). No API key is stored in the repository:
pushing a tag v1.2.3 builds, tests, packs and publishes both packages.
MIT © Nitish Kumar. More projects: github.com/KumarNitish007