mainv0.4.0 ·MIT·Harare, Zimbabwe

Fiscalisation,
solved once.

The open-source TypeScript SDK for ZIMRA's Fiscalisation Data Management System. Talk directly to FDMS with your own device certificates — no middleware, no per-receipt rent.

zsh — pos
///what-it-handles

The parts every DIY
integration gets wrong

Every Zimbabwean POS, invoicing and accounting product has to integrate FDMS. The spec is stateful, cryptographic and unforgiving — this SDK does the hard parts once, in the open.

Hash-chained receipt signing

Canonical signing strings, cents-exact rounding, tax concatenation and the receipt-to-receipt hash chain — computed and DER-signed for you. Get one byte wrong on your own and FDMS answers with RCPT020 and no further explanation.

Registration & certificates

ECDSA P-256 keys, the exact CSR subject ZIMRA requires, activation-key registration and renewal before expiry.

Fiscal day lifecycle

Open, accumulate, close. Counters tracked per tax, currency and money type — then sorted and signed exactly the way the backend verifies them.

Offline queue — built for real infrastructure

Power cuts and dead links don't stop sales. Receipts sign and number at the moment of sale, append to a journal that is never rewritten, and flush in order when connectivity returns, inside FDMS's 72-hour grace window.

Verification QR codes

The exact data string ZIMRA's validation portal expects, ready to print on every receipt.

Errors that explain themselves

Every code carries a colour, a cause, a fix and whether the day can still be closed, plus a support code a cashier can read out. RCPT030, RCPT012 and friends, as observed live.

mTLS, no HTTP dependency

Mutual TLS on plain node:https with one keep-alive agent per device. The fiscal core itself depends on nothing at all.

Runs anywhere, including Android

The engine has no platform imports: no node:*, no Buffer, no WebCrypto. Hashing is plain TypeScript. Node, Bun, Deno, browsers, and React Native with the device key sealed in Android Keystore.

Crash-safe by construction

A pending-submit marker goes to disk before every request. Kill the process at any instruction, restart, call reconcile(): FDMS ends up with every global number exactly once. Tested by killing the device at every write and every network call.

A simulator and published vectors

npx zimra-fdms-simulator is a local FDMS with real mutual TLS that replays the validation codes seen live. Conformance vectors let a port in any language prove it signs exactly as FDMS expects.

Integer cents, always

FDMS signs over cents. Amounts are whole units or cents("11.50"); a float that might be 11.499999 is refused instead of rounded, so a signature never drifts.

Every method, typed and documentedFull API reference — FiscalDevice, the offline queue, signing utilities and every exported type.
///developer-experience

A full fiscal day in a screenful

Three calls from code — or three sentences to your agent. The same binary is a Model Context Protocol server built on the stateless MCP 2026-07-28 spec, so Claude Code or any MCP client can run the day through eight fully-typed tools.

01
Register once

The test portal issues device credentials in minutes — no paperwork. One call generates keys, builds ZIMRA's exact CSR and returns your certificate.

02
Sell all day — from code or conversation

Counters, global numbers, hash chain, signature and QR — every receipt fully fiscalised by one method call, or one plain-language sentence over MCP.

03
Close with confidence

Day counters are accumulated, sorted and signed the way FDMS verifies them. No spreadsheet reconciliation at midnight.

04
One profile, one hash chain

Day state lives on disk in the CLI's profile directory, never in the session — open a day from the terminal, sell through the agent, close from either.

TypeScript
import { FiscalDevice } from "zimra-fdms";
 
const device = new FiscalDevice(identity, credentials);
 
await device.getConfig();   // tax tables, QR base URL
await device.openDay();
 
const sale = await device.submitReceipt({
  currency: "USD",
  invoiceNo: "INV-0001",
  lines: [{ name: "Consulting", price: 115, quantity: 1, taxId: 513 }],
  payments: [{ moneyType: "Cash", amount: 115 }],
});
// counters, global numbers, hash chain, signature, QR — handled
console.log(sale.qrData);
 
await device.closeDay();    // signed fiscal-day counters
The fiscal day, start to finishState persistence, counter accumulation, the asynchronous close and how to recover a lost day. MCP server setup & tool referenceAdding it to Claude Code, the profile model, and all eight tools documented.
///field-notes

Documented nowhere else

Knowledge that cost real fiscal days to earn — shipped as defaults, pinned by a regression suite.

Signatures must be ASN.1 DER

FDMS silently rejects the raw P1363 signatures WebCrypto produces. The SDK converts to DER by default — the difference between "Invoice is valid" and a day of RCPT020.

Counter order is enum order

BalanceByMoneyType counters sort Cash-before-Card by enum position, not alphabetically. One swapped pair fails the whole day-close signature.

CSR subject is ZIMRA-serial-id₁₀

The certificate request's CN must match ZIMRA's device-name format exactly, with the device ID zero-padded to ten digits.

The test environment is self-service

No paperwork to start: the FDMS test portal issues device credentials in minutes. Install to ZIMRA-validated invoice in one sitting.

The rest of what FDMS never wrote downCanonical signing strings, counter ordering, timezone traps and the error codes that explain themselves.
///going-to-production

The code is solved.
The paperwork is human.

Live fiscalisation still needs ZIMRA onboarding: sample-document approval, live device registration, ITF263, ongoing compliance. Goko Consultancy & Training Services handles that path end-to-end.

Talk to Goko