All work

Live in production

Mohasib

Statutory e-invoicing platform for Pakistani businesses and tax consultants

My role
Sole engineer — product definition, architecture, implementation, deployment
Timeline
2025 — present · actively maintained
Team
Solo

Outcome

Live
in production, pre-revenue
Bulk
spreadsheet import replaces sequential entry
Multi-client
one consultant login across many businesses
Sandbox
rehearsal path before any real filing

What it looks like

Penalty calculator showing a PKR 250,000 sales-tax input producing a PKR 50,000 estimated exposure, with the flat fine and the two-percent calculation itemised side by side.
The penalty calculator encodes the statute directly: Section 33 of the Sales Tax Act 1990 read with Chapter XIV of the Sales Tax Rules 2006 and the 2025 SROs. It shows both the flat fine and the percentage calculation, then applies whichever is higher — so the number is auditable rather than asserted. No account required.
Consultant command centre listing clients by health status, today's urgent filings, and a week-ahead table of obligations with due dates.
The consultant view is organised around deadlines rather than documents. Clients carry a health state — healthy, watch, at risk — derived from what is due and how close it is, so a firm managing many businesses sees exposure before it becomes a penalty.
Browser prompt offering to install Mohasib as an application.
Installable as a PWA. Tax work happens on whatever device is to hand during a client visit, so the product needed to leave the browser tab without a second codebase.

Context

Chapter XIV of Pakistan's Sales Tax Rules 2006 obliges sales-tax-registered businesses to transmit every sales invoice electronically to PRAL and to carry the government-issued invoice number and QR code on the document. Non-compliance carries penalties under Section 33 of the Sales Tax Act 1990. In practice, most small businesses were meeting the obligation by re-keying invoices into a government portal one at a time.

The problem

  1. An invoice has no legal standing until the regulator acknowledges it, so the integration cannot be treated as fire-and-forget. A silently failed transmission produces a document the business is not permitted to issue.
  2. The PRAL schema is strict and its rejection messages are written for integrators rather than end users. A single malformed tax field invalidates the entire payload.
  3. Businesses issue invoices in batches out of existing spreadsheets. A one-at-a-time form was never going to meet the actual workload.
  4. Tax consultants administer many client businesses from a single login, which makes tenant isolation a launch requirement rather than something to retrofit later.

How I approached it

Treat the regulator as an observable system boundary

All PRAL communication passes through a single integration layer rather than being distributed across controllers. Every request and response is persisted against its invoice, so a rejection six months old remains fully reconstructible — which matters because the underlying records are subject to audit.

Invoices advance through an explicit state machine — draft, validated, transmitted, acknowledged, failed. A partially submitted invoice can therefore never be mistaken for a legally issued one, and failed transmissions remain recoverable rather than disappearing.

Validate ahead of the network, not after it

The regulator's schema is mirrored as a local validation layer, so structural and tax-calculation errors surface before any request is made. Opaque government error codes become field-level messages a business owner can act on directly.

Bulk Excel and CSV import applies that same validation across every row and reports all failures together, so a 500-row upload is corrected in one pass rather than through 500 sequential rejections.

Provide a sandbox so nobody rehearses on live tax records

A sandbox environment mirrors production against the regulator's test endpoints. New users — and every new feature — are exercised against realistic responses before touching a real filing.

The same principle produced two public utilities that require no account at all: a penalty calculator and an invoice-format validator. Both let a prospective user verify their data against the rules before committing to the product.

Model tenancy around the consultant, not only the business

The data model treats the business as the tenant and the user as a member holding a role, which allows a consultant to move between client businesses without ever crossing a tenant boundary.

Scoping is enforced where queries are constructed rather than in the interface, so a missing filter fails closed instead of leaking one client's tax records into another's view.

Firms are not single-operator businesses, so staff carry roles of their own — principal, manager, associate — and managers and associates see only the clients assigned to them. Delegation inside a firm is therefore a permission boundary rather than a shared password.

Organise the consultant's view around deadlines, not documents

A consultant's actual job is not issuing invoices; it is not missing filings. The command centre inverts the usual document-centric layout and leads with what is due — today's urgent items first, then the week ahead, with each obligation named and dated.

Every client carries a derived health state — healthy, watch, at risk — computed from outstanding obligations and their proximity. That turns a list of clients into a triage queue, which is what a firm carrying dozens of them actually needs.

Architecture

  1. Clients

    • Invoice form
    • Excel / CSV import
    • Consultant command centre
    • Public tools (no account)
  2. Application

    • Next.js App Router
    • Tenant scoping
    • Firm roles & assignment
    • Obligation calendar
  3. Compliance core

    • Local schema validation
    • Invoice state machine
    • PRAL integration layer
  4. Persistence

    • MongoDB (tenant-scoped)
    • Request/response audit log
  5. Regulator

    • PRAL sandbox
    • PRAL production

Cross-cutting

  • Authentication & authorisation
  • Structured error reporting
Every path to the regulator passes through one integration layer, so validation, retry and the audit trail exist in exactly one place.
Frontend
Next.js App Router · TypeScript · server components for invoice listings
Backend
Node.js REST API · role-based authorisation · full request/response audit log
Database
MongoDB · tenant-scoped collections · indexed on business and invoice date
Regulator
PRAL / FBR e-invoicing API across separate sandbox and production endpoints
Primary flow
Create → local validation → PRAL transmission → 22-digit IRN and QR payload
Bulk flow
Excel/CSV upload → row-level validation report → batched transmission
Firm roles
Principal, manager, associate — client visibility scoped per member, invitation by email
Obligations
Deadline calendar per client with derived health states — healthy, watch, at risk
Public tools
Penalty calculator and invoice-format validator, no account required
Delivery
Installable PWA · English and Urdu interface
Pricing model
Three published tiers — free allowance, per-business subscription, per-client consultant plan

Trade-offs I chose

Persist the complete regulator exchange rather than only its outcome

WhyTax records are auditable. When a client asks why an invoice was rejected months earlier, an answer has to exist. Storage is inexpensive; an unanswerable compliance question is not.

Mirror the government schema locally instead of relying on remote validation

WhyA network round trip per validation error would have made bulk import unusable and would have surfaced raw regulator error codes to non-technical users.

An explicit state machine in place of an `isSubmitted` boolean

WhyA boolean cannot express 'transmitted but not yet acknowledged'. In a compliance product that ambiguity is a legal exposure, not a user-experience detail.

Publish free, unauthenticated compliance tools

WhyThe penalty calculator and format validator answer the question a prospective customer actually has before they will trust the product with a filing. They also cost almost nothing to run.

What I took away

  • Integrating with a regulator is predominantly a reliability and auditability problem rather than a coding one. Almost all of the substantive work sat in what happens when the other side declines.
  • Compliance software operates under a different failure budget from ordinary CRUD. 'Mostly working' is not a passing grade when the output is a legal instrument.
  • Validating locally against a mirrored schema was the single change that made bulk import viable at all.

Stack

  • Next.js
  • TypeScript
  • Node.js
  • MongoDB
  • PRAL / FBR API
  • Multi-tenant
  • PWA
  • i18n