Skip to content
Mizani by eazysoft
Documentation

Install Mizani and keep it running.

Everything you need to go from a licence to a working panel at /mizani. Read this before booking a support consultation: most answers are here.

Requirements

  • A Laravel 12 or 13 application on PHP 8.3 or later.
  • M-Pesa through starnerz/laravel-daraja 5.1 or later. Mizani reads the traffic Laravel Daraja already sends and receives.
  • The Laravel scheduler running, for the commands in Scheduled commands below.
  • For statement import only: pdftotext from Poppler (apt install poppler-utils). Without it, everything else works.

There is no npm package, no Vite entry and no published asset: the panel ships its own compiled styles.

Install

1. Point Composer at the repository. After you buy, your account page shows your repository username and token. Add the repository to your application's composer.json:

"repositories": [
    {"type": "composer", "url": "https://repo.mizani.eazysoft.africa"}
]

Store the token where Composer finds it, not in your repository. In CI, set COMPOSER_AUTH instead.

composer config --global --auth http-basic.repo.mizani.eazysoft.africa <username> <token>

2. Require the panel and the M-Pesa driver, then migrate. The ledger, which records every payment, comes with them; its migrations load automatically.

composer require mizani/panel mizani/mpesa
php artisan migrate

3. Open the gate (next section), then visit /mizani. Until the first payment is recorded, the overview is a checklist: tables, driver, configuration, schedule, and a sandbox payment that proves the whole path. It disappears with the first recorded row.

Publish the configuration only to change something, and the views only to change how they look:

php artisan vendor:publish --tag=mizani-panel-config
php artisan vendor:publish --tag=mizani-panel-views

Who gets in

Mizani has no users and no login of its own. A gate over your application's own users decides who may open the panel. Define it in a service provider:

Gate::define('viewMizaniPanel', fn ($user) => $user->isAdmin());

Until it is defined, the panel is closed to everybody. Removing somebody from your application removes them from the panel.

The panel mounts at /mizani. Change route.prefix, route.middleware or route.domain in the published config to move it, add middleware, or serve it from its own subdomain.

People and abilities

The gate decides who gets in. Buttons that move money have a second, narrower layer: six abilities. It is off until you set permissions.enforce, so upgrading takes away nobody's button.

AbilityLets somebody
resolveClear payments off the attention list.
replaySend a stored callback through the application again.
importWrite payments the provider has and you do not into the ledger.
balanceAsk the provider for a fresh float figure (metered, usually billed).
see-balancesSee the float: the Balances screen and the figure in the header.
manageGrant and revoke all of the above, for anybody.

Turning enforcement on locks everybody out until somebody is let in, so the first grant comes from a terminal. After that, the People screen does the same with checkboxes. --can sets the whole list; every change is audited.

php artisan mizani:people 1 --label="Wanjiru" --can=manage --can=resolve
php artisan mizani:people                      # who may do what
php artisan mizani:people 1 --can=none         # take it all back

An application that already has roles can define mizani.resolve, mizani.replay and the rest as gates of its own; those win.

Scheduled commands

Add these to your scheduler. mizani:reconcile, mizani:doctor and mizani:status exit non-zero when something needs a person, so you can alert on them instead of reading them.

Schedule::command('mizani:sweep')->everyFiveMinutes();
Schedule::command('mizani:reconcile')->hourly();
Schedule::command('mizani:balance --check')->hourly();
Schedule::command('mizani:prune')->daily();

mizani:sweep asks Safaricom about STK prompts that never concluded. mizani:reconcile compares Safaricom's record with yours; add --import to close the gap. Balance requests are asynchronous: the figure arrives on the result callback.

Statement import

Safaricom's Pull Transactions only reaches back 48 hours. For anything older, download the statement PDF from the M-Pesa org portal and upload it on the Reconciliation screen.

Nothing is accepted unchecked: every row's balance must follow from the row below it, and the number of rows must match the statement's own total. A file that fails either check is refused whole. Completed payments in become c2b payments; charges, money out and incomplete rows are counted and left out. Statements need pdftotext; set MIZANI_PDFTOTEXT if it is not on the PATH. The Doctor screen says whether it was found.

Masking and export

Phone numbers and the float are covered by default, so a screen can be shared without exposing a customer's number or the balance. Uncovering a phone number is recorded in the audit log; the float is a plain toggle.

The Ledger, Requests and Callbacks screens export what they list as CSV, under the same filters and range. Covered numbers stay covered in exports, stored payloads are never exported, and every export is audited.

Updates and your licence

Your licence includes every version released during the twelve months after purchase. Update the usual way:

composer update "mizani/*"

When the twelve months end, your token keeps working: every version released inside your window still installs, on a fresh server or in CI, and everything installed keeps running. Only versions released after your window stay out of reach until you renew from your account.

Nothing in Mizani checks a licence while it runs: no phone-home, no kill switch. The Licence and updates screen reads one public file, releases.json, which carries nothing about who asked, and only when somebody presses the button. Set MIZANI_PANEL_UPDATE_MANIFEST to host it yourself, or to null to switch checking off. Mizani never updates itself; your deploy applies updates.

Mizani Pro

Mizani Pro is for platforms that run M-Pesa for many businesses, each on its own paybill or till. Install it alongside the panel:

composer require mizani/pro
php artisan migrate

Your application tells Pro whose businesses exist and who may see them, by binding three contracts from mizani/contracts in a service provider:

  • ScopeResolver: your tenancy, as scope keys and labels.
  • GrantsScopes: who sees which businesses, and which platform staff get support and fleet. Until it is bound, everybody is granted nothing.
  • ResolvesConnections: each business's Daraja credentials and shortcodes. Or skip it and use Pro's own table.

To keep connections in Pro's table, set MIZANI_PRO_CONNECTIONS=database, migrate, and connect each business without a deploy. A bare --setting=name is asked for without echoing, so secrets stay out of your shell history.

php artisan mizani:pro:connect tenband-live --scope=tenband --shortcode=4012345 \
    --setting=mode=live --setting=consumer_key --setting=consumer_secret
php artisan mizani:pro:connections
php artisan mizani:pro:doctor --scope=tenband

Pro is for live paybills and tills only. Daraja's sandbox shares test shortcodes between apps, so a sandbox payment cannot be told apart by business. Connect a business once its own paybill or till is live.

Command reference

CommandDoes
mizani:statusWhat needs a person. Prints nothing when all is well.
mizani:doctorChecks the configuration for the things that break quietly.
mizani:reconcileCompares Safaricom's record with yours. --import closes the gap.
mizani:sweepAsks Safaricom about STK prompts that never concluded.
mizani:replayRe-runs callbacks whose handler threw.
mizani:resolveClears reconciled payments off the attention list once dealt with.
mizani:balanceShows the recorded float; --check asks for a fresh one.
mizani:pruneDeletes recorded traffic past its retention window.
mizani:peopleGrants and lists abilities from a terminal.

Getting help

Start with this page and with php artisan mizani:doctor, which names most configuration problems outright. If you are still stuck, book a support consultation. Consultations are billed separately from the licence.