MowazanaموازنةSole Engineer & Architect2026 · solo, both repos · Saudi Arabia
A multi-tenant cloud ERP and POS for Saudi businesses that signs every invoice and clears it with ZATCA.
Stack: React 18, Laravel 10 and MySQL 8. Arabic and English with full RTL, deployed on AWS with CI/CD.
Log into the demo tenant
Try it live
A seeded demo tenant on the live system, with the ZATCA sandbox flow and the POS. It is excluded from every production metric.
| Admin / ERP login | app.mowazana-pos.com(opens in a new tab) |
|---|---|
| User | admin-demo-showcase-co |
| Password | MyiORKS7 |
| POS | The POS opens from the top bar inside the ERP, using the same login. |
Compliance the software has to carry
The problem
Saudi Arabia mandates electronic invoicing. Phase 2 moves it from a PDF with a QR code to signing every invoice and submitting it to a government gateway in real time: certificate onboarding, UBL 2.1 XML, XAdES-BES signatures over ECDSA secp256k1, a TLV-encoded QR, and a per-seller chain where each invoice embeds the hash of the one before it.
Most Saudi small businesses cannot build that, so it has to come inside the software they already run the business on. That software is a multi-tenant ERP: sales, purchasing, inventory, double-entry accounting and HR, shared by every tenant on one deployment, where no business may ever see another’s data and tenants who stop paying have to be handled without a person in the loop.
Three decisions
What was chosen, and what it cost
فاتورة ضريبية
Tax Invoice · UBL 2.1
PIH chained
XAdES-BES · secp256k1
The path one invoice takes
- 1Onboard certificate
- 2Lock chain
- 3Build UBL 2.1
- 4Sign (XAdES-BES)
- 5TLV QR
- 6Submit
- 7Cleared or reported
Serialise the invoice hash chain per tenant, and submit off the request
| Chose | ZATCA Phase 2 chains each seller’s invoices: every invoice embeds the hash of the one before it. A lockForUpdate on the tenant’s credential row, inside a transaction, assigns the counter, previous hash and UUID atomically before signing. Signing and submission then run as queued jobs on a dedicated zatca queue, with retry and backoff at 30, 120 and 600 seconds. |
|---|---|
| Over | Assigning chain values without a lock and calling the gateway synchronously from checkout. Two concurrent invoices could then read the same previous hash and fork the chain, after which ZATCA rejects every later invoice from that tenant, and the POS would wait on the government gateway for each sale. |
| Cost | A tenant’s invoices take their place in the chain one at a time, however many cashiers are selling. An invoice is saved as pending before it is reported or cleared, so it can fail after the sale is done and has to be retried. |
One shared schema, isolated by a single global scope
| Chose | All tenants share one MySQL schema and one deployment. A single TenantScope applied at the model layer, plus a tenant id bound into the JWT, filters every query by default, so a forgotten where clause cannot leak another business’s financials. Two-tenant isolation tests back it. |
|---|---|
| Over | A database per tenant, which isolates physically but means provisioning and migrating a database for every signup. Or tenant filters written by hand in each query, where one omission is a leak. |
| Cost | Isolation is logical, not physical. Any query that goes around the model layer also goes around the scope, and removing a tenant means deleting its rows table by table, in child-to-parent order, instead of dropping a database. |
Lock and wipe lapsed tenants on a schedule, with no operator
| Chose | systemd-scheduled commands run the ladder daily. A tenant is locked to read-only at day 3 overdue, Arabic reminder emails escalate up to day 150, and at day 180 the tenant is wiped from the database and S3. Every step is logged and announced by email, and the wipe deletes child tables before their parents so foreign keys hold. |
|---|---|
| Over | A person reviewing overdue accounts and locking or deleting tenants by hand. That keeps a human check in front of anything destructive, but the platform has one engineer and that work would fall to him. |
| Cost | The wipe is irreversible and nobody approves it. The safeguards are the long gap between lock and wipe, the repeated emails and the logs, so a wrong billing state would lock a paying customer before anyone looked. |
6 paying tenants in the first month
Result
Mowazana is in production. Every tenant gets the same suite, with sales and POS, purchasing, inventory, accounting, HR and ZATCA invoicing served by one Laravel API and one React SPA from a shared schema.
It shows that one engineer can carry Phase 2 compliance, tenant isolation and the billing lifecycle in a single codebase, provided the risky parts are decided once, at the chain lock, the tenant scope and the scheduler, and not screen by screen.
Screens
Captured on the demo tenant
ERP
Dashboard, double-entry accounting, inventory, purchasing and HR.




POS and invoicing
Checkout, the sale invoice, ZATCA onboarding and the VAT return.




Super-admin control plane
Applications, tenants, plans and subscription billing for the platform itself.




By the numbers
Counted from the source
| Eloquent modelsnote 2 | 73 |
|---|---|
| Migrations | 106 |
| Controllers | 77 |
| Route files | 78 |
| Services (8 ZATCA) | 14 |
| Backend tests | 107 |
| Frontend source files | 646 |
| Redux feature modules | 50 |
Notes
- 1Reported, not code-derived: 6 paying tenants and about 10K SAR in the first month.
- 2Verified: two repositories, one author, counted from the source on 2026-06-15.