Payroll for nine African countries from one platform: statutory rules, payslips, bank files and employee self-service.

9Countries supported
280+Backend test files
3Languages

Project Summary

  • Client: Mediacent Interactive
  • Industry: hr tech
  • My role: Lead Engineer (v3)
  • Core tasks: Platform architecture, Backend (Node.js), Web app (SvelteKit), Statutory engine, Integrations

Running payroll across Africa means a different rulebook in every country, and the rules keep changing. Kenya alone has revised its NSSF tiers, replaced NHIF with SHIF and introduced a housing levy within a few years. PayrollMaster Africa, built by Mediacent Interactive, takes that complexity off employers and payroll bureaus. I contributed to the v2 platform and then led the v3 rebuild.

The problem

  • Rules that move. Statutory rates and bands change mid-year, and every run must apply the right version for its period.
  • Many countries, one employer. Regional businesses need consistent payroll across borders.
  • Manual reporting. P9s, P10s and statutory returns were assembled by hand at the end of each period.

What I built

  • Config-driven statutory engine. One calculator driven by versioned, date-ranged rules, seeded for Kenya, Uganda, Tanzania, Rwanda, Nigeria, Ghana, Zambia, DR Congo and South Sudan. Kenya alone covers PAYE, NSSF Tier I/II, SHIF (and legacy NHIF), the Affordable Housing Levy, NITA and withholding tax.
  • Payroll runs with approvals. Each run moves from draft to calculated, approved and completed, with line-level approval before anything is paid.
  • Payslips at scale. Branded PDF payslips with custom templates, emailed in bulk through a job queue.
  • Statutory reports. P9 (Excel and PDF), P10, NSSF, SHIF and NITA returns generated straight from the run.
  • Bank files and accounting. Payment files for banks including ABSA and NCBA, and journals to Zoho, QuickBooks and Business Central.
  • Employee self-service. Staff see their payslips and compensation in English, Swahili or French.
  • Beyond payroll. Leave, loans, casual workers, a bureau mode for agencies running payroll for many clients, a partner portal and subscription billing.

Under the hood

The backend is Node.js and TypeScript on Express, with MongoDB through Mongoose, Redis and BullMQ for background jobs, Zod validation and Pino logging. Multi-tenancy is enforced in one place: a Mongoose plugin scopes every query to the current tenant using AsyncLocalStorage, so no endpoint can forget the filter. The web app is SvelteKit with Svelte 5 and Tailwind, and the backend is covered by more than 280 Vitest test files.

The result

PayrollMaster v3 gives employers and bureaus one platform for payroll across nine countries, where a change in tax law is a configuration update rather than a code release. I wrote the large majority of the v3 backend and web app.

Tech stack

Frontend
  • SvelteKit 2
  • Svelte 5
  • TypeScript
  • Tailwind CSS 4
  • Paraglide i18n
Backend
  • Node.js
  • TypeScript
  • Express
  • BullMQ
  • Zod
Data
  • MongoDB (Mongoose)
  • Redis
Integrations
  • Paystack
  • DPO
  • Zoho
  • QuickBooks
  • Business Central
Other
  • pdfmake
  • Vitest
  • Pino

What I did

  • Led the v3 rebuild of the backend and web app
  • Designed the config-driven, versioned statutory engine
  • Built multi-tenancy, payroll runs, approvals, payslips and statutory reports
  • Integrated bank files, accounting journals and subscription billing
  • Contributed to the earlier v2 platform

Key decisions

  • Statutory rules as versioned data, not code, so rate changes ship as configuration
  • Tenant scoping enforced centrally by a Mongoose plugin instead of per-query filters
  • Queue-backed payslip generation and email, so large runs never block the API

Challenges

  • Applying the right rule version when rates change mid-period
  • Guaranteeing tenant isolation in a shared database
  • Generating and emailing large payslip runs reliably
  • Keeping up with frequent statutory changes across nine countries

Have a similar problem to solve? Let's talk.

Start a conversation