JikoXpress Financial System Architecture
Version
1.2.0 | April 2026 | QBIT SPARK CO LIMITED
FullRefinedarchitecture,architecturedatabasedflow,onuserfulljourneysdesign sessions — all scenarios, edge cases, andentitydecisionsdesigncaptured.
Table of Contents
- Overview & Core Principles
- Platform Context
- Two Financial Worlds
- Treasury Design
- Wallet System
Components - Chart of Accounts
EntityMoneyDesignSplittingDataEscrowFlowsUser JourneysSystem- Payment Channels
EscrowFullSystemMoney Journey — All ScenariosMoneySubscriptionSplittingPlatform OffersPayments- Settlements &
WithdrawalsPayouts P&LRefunds & Cancellations- Admin Withdrawal
- PSP Reconciliation
- Reporting
SecuritySafety Rules &RulesIntegrity Checks- Decision Log
1. Overview & Core Principles
JikoXpress financial system is a unified internal accounting engine — a proper ledger that tracks every single money movement acrosson the platform —platform, regardless of amount (even TZS 0.00), channel, or purpose.
Core Principles
-
Principle Description Every event is a transaction — cash,Cash, mobile money, wallet, kitchen wallet, TZS 0 promo orders — all recorded Double entry accounting — everyEvery debit has a credit,credit.booksBooks always balanceSingle source of truth — internalInternal ledger is authoritative,authoritative — not the externalproviderPSPDynamic money splitting — splitsSplits are configurable,passed as parameters, not hardcodedEscrow is a flag — anyAny transaction can be held,held — decided at runtimenotbyhardcodedthe callerEveryone has one wallet Same JikoXpress account can be customer, kitchen owner, and rider — one wallet Counter orders stay outside POS/cash/kitchen custom payments never enter the treasury pool Platform earns on online only Service fee applies only to App and WhatsApp orders - 7 sales channels: POS, Kiosk, Table QR, Mobile App, WhatsApp, Drive-Through, Direct Counter
Kitchen4walletsubscriptionistiers:separateSTARTER, GROWING, PROFESSIONAL, ENTERPRISE- Multiple fulfillment types:
—Dine-in,platformPickup,recordsDelivery,usageDrive-Through - East
doesAfricanotfirst:manageBuiltitfor M-Pesa, Tigo Pesa, Airtel Money via Selcom, Azampay
2. Platform Context
JikoXpress Pro connects customers with local chefs and restaurants (like Toasty for East Africa). The platform has:
Who Are the Actors?
Every person on the platform has one account and one wallet — regardless of their role:
Same JikoXpress account can be:
├── Customer — orders food via App/WhatsApp
├── Kitchen owner — runs a kitchen on the platform
└── Rider — delivers orders for kitchens
One person. One wallet. All roles.
3. Two Separate Financial Worlds
Not all money flows through JikoXpress. There are two separate worlds:
┌──────────────────────────────────────────────────────┐
│ WORLD 1 — JikoXpress Platform TreasuryPool │
│ Commission,│
wallets,│ settlements,App offersorders, WhatsApp orders │
│ OneMoney internalflows ledgerthrough our PSP accounts │
│ Treasury tracks everything │
│ Platform earns service fee here │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ KitchenWORLD Customer2 Wallet— Kitchen's Own World │
│ Kitchen│
manages│ independentlyPOS cash, card, kitchen custom payments │
│ Money goes directly to kitchen's physical till │
│ or kitchen's own mobile money account │
│ Treasury never sees this money │
│ Platform onlyearns recordsZERO "usedhere as(covered payment"by subscription) │
└──────────────────────────────────────────────────────┘
Which Channels Belong to Which World?
| Channel | World | Service Fee | Treasury Involvement |
|---|---|---|---|
| Mobile App | JikoXpress Pool | Yes | Full |
| JikoXpress Pool | Yes | Full | |
| POS — Cash | Kitchen's World | No | None |
| POS — Card | Kitchen's World | No | None |
| POS — Kitchen Wallet | Kitchen's World | No | None |
| POS — Kitchen Custom (Lipa Voda etc) | Kitchen's World | No | None |
| Kiosk — Cash | Kitchen's World | No | None |
| Table QR — Cash | Kitchen's World | No | None |
| Drive-Through — Cash | Kitchen's World | No | None |
The rule: Platform only earns a service fee when it brings the customer. Counter/automation channels are covered by the kitchen's subscription fee — not per-order cuts.
2. System Components
2.1 Platform4. Treasury
Design
The treasury is the central bank of JikoXpress.JikoXpress. AllIt platformis a proper ledger using double entry accounting — every money flowsmovement throughhas here.two sides (debit and credit), books always balance.
What Double Entry Means
Responsibilities:
- never
Receiveappearscommissionor disappears — it always moves from somewhere to somewhere.Example: Customer pays TZS 10,000 via Mpesa Money arrives at Selcom (we now have it): DEBIT ASSET_PSP_SELCOM 10,000 Money is split: CREDIT LIABILITY_WALLETS 9,000 (kitchen's cut — we owe them) CREDIT REVENUE_SERVICE_FEE 1,000 (we earned this) Total debits = Total credits = TZS 10,000 ✅Treasury Buckets — What the Money Is
At any moment, every
ordershilling Receiveinsubscriptionthe treasury is tagged to a bucket:ASSET_PSP_SELCOM — physical money sitting at Selcom ASSET_PSP_AZAMPAY — physical money sitting at Azampay ASSET_ESCROW — held money, belongs to nobody yet LIABILITY_WALLETS — belongs to ALL users (customers, kitchen owners, riders) this is their money — we owe it back on demand LIABILITY_SETTLEMENTS — payout requests being processed, on the way out REVENUE_SERVICE_FEE — platform's cut from online orders (OURS) REVENUE_SUBSCRIPTION — monthly/yearly kitchen feesfrom(OURS)kitchensEXPENSE_REFUNDS Hold—escrowmoneyforreturnedremotetoorderscustomers
Pay delivery partnersSubsidize platform offersProcess refundsSettle kitchen earningsRecord admin profit withdrawals
2.2The Golden Safety Rule
ASSET_PSP_SELCOM + ASSET_PSP_AZAMPAY + ASSET_ESCROW
>=
LIABILITY_WALLETS + LIABILITY_SETTLEMENTS
If this breaks → crisis alert immediately. Platform is spending money it doesn't own.
5. Wallet System
User Wallet (Personal)
Every registered JikoXpress app user wallet.gets Customerone canwallet topup,automatically payon orders,account withdraw.creation.
- One wallet per
registeredperson,appregardlessuserof role CreatedUsedautomaticallyto:ontopregistrationup, pay orders, receive earnings (kitchen revenue, delivery fees), withdraw to MpesaHasLivesitsunderownLIABILITY_WALLETStransaction ledgerLinked toin treasuryLIABILITY—accountit's the user's money, not the platform's
2.3 Kitchen EarningsWallet (Kitchen-Level)
PlatformA holdscompletely kitchenseparate revenuesystem after— ordersnot untilthe kitchenplatform owner requests payout.wallet.
PerIssued by the kitchenAccumulates with every completed orderDepleted on settlement requestLinkedtotreasurytheirLIABILITY_SETTLEMENTS account
2.4 Kitchen Customer Wallet
Completely separate from platform. Kitchen manages independently.
Kitchen issues toown loyal customers- Kitchen
handlesmanages it independently (deposits, deductions,withdrawalsbalance) - Has its own ledger — kitchen's
booksbooks, not platform's - Platform only records "
channel:paid via KITCHEN_WALLET" in the transaction — no treasury movement
2.5Wallet Externalvs PaymentTreasury
| Level | What It Sees |
|---|---|
| Treasury | LIABILITY_WALLETS = TZS 5,000,000 (total of all wallets combined) |
| Wallet level | Kibuti = TZS 45,000 / Mama Lishe = TZS 230,000 / John rider = TZS 80,000 |
Treasury sees the total obligation. Individual wallets explain the breakdown.
Wallet Transaction Ledger
PhysicalEvery moneywallet handlersmovement —creates Selcom,a Azampay,record etc.with:
Physicaltypemoney—sitsTOPUP,hereORDER_PAYMENT, ORDER_EARNING, DELIVERY_EARNING, WITHDRAWAL, SUBSCRIPTION_PAYMENTPlatformdirectioncalls—theirCREDITAPI(in)fororcollectionsDEBIT (out)amountbalanceBeforeanddisbursementsbalanceAfter— full audit trail, history reconstructable at any pointEveryreferenceIdinteraction—recordedorderinID,platformpayoutledgerID Provider can be swapped without changing internal accounting
2.6 Escrow
Temporary holding account for remote orders.
Lives in ASSET_ESCROW account in treasuryHolds money between payment and delivery/confirmationReleased when release condition is metAny transaction can be flagged for escrow at runtimeetc
3.6. Chart of Accounts
ASSETS (what platform owns)has / controls)
├── ASSET_FLOATASSET_PSP_SELCOM WorkingPhysical capitalmoney forat disbursementsSelcom
├── ASSET_ESCROWASSET_PSP_AZAMPAY MoneyPhysical heldmoney pendingat releaseAzampay
└── ASSET_RECEIVABLESASSET_ESCROW MoneyHeld owedmoney to— no owner yet
LIABILITIES (what platform REVENUEowes (money— comingnever in)touch without permission)
├── REVENUE_COMMISSIONLIABILITY_WALLETS %All takenuser wallets combined
└── LIABILITY_SETTLEMENTS Payout requests being processed
REVENUE (platform's own earnings)
├── REVENUE_SERVICE_FEE Cut from everyApp/WhatsApp orderorders
├└── REVENUE_SUBSCRIPTION Kitchen monthly/yearly fees
├──EXPENSE REVENUE_SERVICE_FEE(money Platformplatform service chargesspent)
└── REVENUE_DELIVERY_MARKUP Markup on delivery price
EXPENSE (money going out)
├── EXPENSE_DELIVERY Paying delivery partners
├── EXPENSE_OFFER_SUBSIDY Platform offer costs
├── EXPENSE_REFUNDS CustomerRefunds refundssent └──back EXPENSE_OPERATIONSto Platform operational costs
LIABILITY (what platform owes)
├── LIABILITY_SETTLEMENTS Kitchen earnings awaiting payout
├── LIABILITY_PLATFORM_WALLETS App user wallet balances
└── LIABILITY_ESCROW_DUE Escrow obligationscustomers
EQUITY (platform net worth)
├── EQUITY_CAPITAL Admin deposits / investments into platform
└── EQUITY_RETAINED_EARNINGS Accumulated platform profits
P&L Formula
Total Revenue = sum(REVENUE_*)
Total Expenses = sum(EXPENSE_*)Platform Net Profit = TotalREVENUE_SERVICE_FEE Revenue+ REVENUE_SUBSCRIPTION - TotalEXPENSE_REFUNDS
ExpensesAdmin Availablecan Cashonly =withdraw ASSET_FLOATfrom -Net LIABILITY_SETTLEMENTSProfit -— LIABILITY_PLATFORM_WALLETSnever from ASSETS covering LIABILITIES
4.7. EntityMoney DesignSplitting
4.1
When AccountEntityan
Chartonline oforder accountsis paid, the total is split into buckets dynamically. The caller (checkout service) passes the split parameters — onethe recordfinancial service just executes. No business logic in the financial service.
Split Buckets per accountOnline type.Order
| Type | Notes | ||
|---|---|---|---|
| Main food items | |||
REVENUE_SERVICE_FEE |
|||
| only |
Service Fee — Configurable Refundability
Controlled by a flag in the platform config:
service.fee.refundable = true → service fee goes into escrow with the rest
on cancel → full refund including service fee
service.fee.refundable = false → service fee goes to REVENUE immediately on confirmation
on cancel → only kitchen/rider money refunded
platform keeps its cut
Early stage → set true to build customer trust.
Mature stage → flip to false to protect platform revenue.
No code change needed — one config change.
8. Escrow System
Escrow is a dynamic flag, not a hardcoded rule. The checkout service decides whether to hold money based on the order context, and passes this to the financial service.
4.2How JournalEntryEntityIt Works
Every singletransaction moneyhas:
holdInEscrow: true/falseescrowReleaseCondition: DELIVERY_CONFIRMED | PICKUP_CODE_CONFIRMED | null
Financial service does exactly what it's told. It doesn't know WHY — neverthe updatedcheckout orlayer deleted.knows that.
Default Business Rules (Set by Checkout, Not Financial Service)
| Type | Hold in Escrow? | Release Condition | ||
|---|---|---|---|---|
| Yes | DELIVERY_CONFIRMED | |||
| DELIVERY_CONFIRMED | ||||
| PICKUP_CODE_CONFIRMED | ||||
| PICKUP_CODE_CONFIRMED | ||||
| Immediate | ||||
| Immediate |
Rules
change 4.3can TransactionEntityEverywithout paymenttouching eventthe financial serviceonejust perupdate paymentthe splitcheckout perlayer order.logic.
9. Payment Channels
| Escrow Possible? | Service Fee? | |||
|---|---|---|---|---|
| No | No | No | ||
| No | No | |||
| No | No | |||
| Yes | If online channel | |||
| Yes | If online channel | |||
| If | online ||||
4.4 TransactionSplitEntity
How transaction amount is distributed. Dynamic — not hardcoded.
4.5 EscrowEntity
Tracks escrow holdings per transaction.
4.6 DisbursementEntity
Every outgoing payment to external world.
4.7 PlatformWalletEntity
JikoXpress app user wallet.
4.8 PlatformWalletTransactionEntity
Every platform wallet movement.
4.9 KitchenEarningsEntity
Kitchen revenue held on platform.
4.10 SettlementEntity
Kitchen payout records.
4.11 TreasuryTopupEntity
Admin deposits to external provider.
4.12 WithdrawalEntity
Admin profit withdrawals from treasury.
4.13 PlatformOfferEntity
Platform offer definitions.
4.14 PlatformOfferUsageEntity
Who used which offer.
| channel |
5.10. DataFull FlowsMoney Journey — All Scenarios
5.Chapter 1 Payment— CollectionCounter FlowOrders (World 2 — No Treasury Involvement)
Scenario 1.1 — POS Cash, Any Fulfillment
Customer initiatespays paymentTZS │10,000 ▼cash CheckoutSessionat createdcounter
→ Order recorded ✅
→ Transaction logged (inventorychannel: held)CASH) │✅
▼→ TransactionEntityReceipt createdgenerated -✅
channel→ setMoney -physically holdInEscrowin flagkitchen settill -💰
status→ Treasury: nothing moves ❌
→ Platform earns: TZS 0 (covered by subscription)
Kitchen sales report shows: CASH += PENDINGTZS │10,000
├───
Scenario 1.2 — POS Kitchen Custom Payment (Lipa Voda, HaloPesa)
Customer pays TZS 15,000 via Lipa Voda at counter
→ Order recorded ✅
→ Transaction logged (channel: KITCHEN_CUSTOM) ✅
→ Money goes to kitchen's own Lipa Voda account 💰
→ Treasury: nothing moves ❌
→ Platform earns: TZS 0
Kitchen sales report shows: KITCHEN_CUSTOM += TZS 15,000
Scenario 1.3 — POS Kitchen Wallet ──────────────────────────────────►Payment
Loyal Nocustomer externalhas APITZS call20,000 │in │kitchen Cashierwallet
confirmsOrders manuallyfood │TZS │8,000, │pays ├───from kitchen wallet
→ Order recorded ✅
→ Transaction logged (channel: KITCHEN_WALLET) ✅
→ Kitchen wallet balance -= TZS 8,000 (kitchen manages independently)
→ Treasury: nothing moves ❌
→ Platform earns: TZS 0
Kitchen sales report shows: KITCHEN_WALLET += TZS 8,000
Scenario 1.4 — Kiosk, Table QR, Drive-Through (Cash)
Same as above — counter level, no treasury, no service fee. Order recorded, money stays in kitchen's physical world.
Chapter 2 — Online Orders (World 1 — Treasury Involved)
Scenario 2.1 — App Order, Pickup, Mobile Money /(USSD)
Total: ──────────────────────────────────►TZS │12,000
│Menu: ExternalTZS provider10,500
APIPackaging: calledTZS │500
│Service fee: TZS 1,000
STEP 1 — Payment initiated:
Checkout session created, inventory held
USSD prompt /sent cardto terminalcustomer │via │Selcom WebhookAPI
Customer confirms on phone
Selcom webhook fires
onSTEP confirmation2 │
│ │
└─── Platform Wallet ───────────────────────────────────────► │
Internal debit only │
No external API │
│
Payment Confirmed ◄────────────┘
│
▼
TransactionEntity status = COMPLETED
│
┌───────────┴───────────┐
│ │
holdInEscrow=false holdInEscrow=true
│ │
▼ ▼
Split money EscrowEntity created
immediately— Money arrives, held in toescrow destinations(pickup not yet confirmed):
DEBIT ASSET_PSP_SELCOM 12,000
CREDIT ASSET_ESCROW │12,000
│STEP │3 Release— conditionOrder metconfirmed, │kitchen │notified, │kitchen ▼prepares
│STEP 4 — Customer arrives, shows pickup code, confirmed:
DEBIT ASSET_ESCROW 12,000
CREDIT LIABILITY_WALLETS 11,000 (kitchen owner)
CREDIT REVENUE_SERVICE_FEE 1,000 (platform)
Kitchen wallet += TZS 11,000 ✅
Platform revenue += TZS 1,000 ✅
Scenario 2.2 — App Order, Delivery, Mobile Money
Total: TZS 18,000
Menu: TZS 12,000
Packaging: TZS 1,000
Delivery fee: TZS 4,000
Service fee: TZS 1,000
STEP 1 — Money arrives, full amount held in escrow:
DEBIT ASSET_PSP_SELCOM 18,000
CREDIT ASSET_ESCROW 18,000
STEP 2 — Order confirmed, kitchen prepares, rider picks up
STEP 3 — Delivery confirmed:
DEBIT ASSET_ESCROW 18,000
CREDIT LIABILITY_WALLETS 12,000 (kitchen — menu + packaging)
CREDIT LIABILITY_WALLETS 4,000 (rider — delivery fee)
CREDIT REVENUE_SERVICE_FEE 1,000 (platform)
Kitchen wallet += TZS 12,000 ✅
Rider wallet += TZS 4,000 ✅
Platform += TZS 1,000 ✅
Scenario 2.3 — App Order, Delivery, JikoXpress Wallet Payment
Customer has TZS 30,000 in JikoXpress wallet
Pays TZS 18,000 from wallet — NO PSP API call, purely internal
STEP 1 — Wallet debited, goes to escrow:
DEBIT LIABILITY_WALLETS 18,000 (customer wallet decreases)
CREDIT ASSET_ESCROW 18,000 (held)
STEP 2 — Delivery confirmed, escrow releases:
DEBIT ASSET_ESCROW 18,000
CREDIT LIABILITY_WALLETS 12,000 (kitchen)
CREDIT LIABILITY_WALLETS 4,000 (rider)
CREDIT REVENUE_SERVICE_FEE 1,000 (platform)
No money entered or left the pool — purely internal redistribution ✅
Scenario 2.4 — App Order, Dine-in, Mobile Money
Dine-in — no escrow needed (no delivery/pickup risk)
DEBIT ASSET_PSP_SELCOM 11,000
CREDIT LIABILITY_WALLETS 10,000 (kitchen)
CREDIT REVENUE_SERVICE_FEE 1,000 (platform)
Immediate split. No escrow. ✅
Scenario 2.5 — WhatsApp Order, Pickup, Mobile Money
Same flow as Scenario 2.1. Channel recorded as WHATSAPP. Service fee applies. Escrow holds until pickup code confirmed.
Scenario 2.6 — Split moneyPayment │(Multiple Methods)
Order total: TZS 20,000
Customer pays TZS 10,000 from wallet
Customer pays TZS 10,000 via USSD
Two TransactionEntities — both linked to destinationssame │order, │both └───────────────────────┘held │in ▼escrow
DEBIT LIABILITY_WALLETS 10,000 (wallet portion)
DEBIT ASSET_PSP_SELCOM 10,000 (USSD portion)
CREDIT ASSET_ESCROW 20,000 (total held)
On release → same split logic, total amount split proportionally ✅
Scenario 2.7 — Platform Offer / Promo Applied
Customer orders TZS 10,000 via App
Platform covers delivery fee TZS 2,000 (free delivery offer)
Customer pays TZS 8,000
Two TransactionEntities:
1. Customer: TZS 8,000 via USSD → escrow
2. Platform: TZS 2,000 subsidy (paidBy = PLATFORM) → escrow
On delivery confirmed:
DEBIT ASSET_ESCROW 10,000
CREDIT LIABILITY_WALLETS 8,000 (kitchen)
CREDIT LIABILITY_WALLETS 2,000 (rider — full fee covered)
CREDIT REVENUE_SERVICE_FEE 500 (platform still earns cut on food amount)
PlatformOfferUsageEntity created — budget tracked
If budgetUsed >= budget → offer auto-deactivated ✅
Scenario 2.8 — TZS 0.00 Free Order Created(Full Promo)
Platform gives customer completely free order
TransactionEntity still created:
amount: 0.00
channel: PLATFORM
holdInEscrow: false
Still recorded. Kitchen Notifiednotified. Order flows normally.
No money moves. Event is captured. ✅
5.2Chapter Money3 Split— FlowWallet Operations
Scenario 3.1 — User Tops Up Wallet
TotalKibuti Ordertops Amount:up TZS 10,50,000 │via ▼Mpesa
SplitDEBIT CalculatorASSET_PSP_SELCOM │50,000
├──CREDIT FoodLIABILITY_WALLETS Items50,000 Total(belongs ──────────────►to KitchenEarningsKibuti)
Kibuti wallet += XTZS │50,000 ├── Packaging Fee ────────────────► KitchenEarnings += X
│
├── Delivery Fee ──────────────────► DisbursementEntity
│ (pay delivery partner)
│
├── Platform Commission ──────────► JournalEntry
│ DEBIT ASSET_FLOAT
│ CREDIT REVENUE_COMMISSION
│
└── Tax ────────────────────────────► JournalEntry
DEBIT ASSET_FLOAT
CREDIT TAX_ACCOUNT✅
5.3
Scenario Escrow3.2 Release— FlowAnyone Requests Payout to Mpesa
EscrowEntityKitchen status = HELD
│
▼
Release Condition Metowner (DELIVERY_CONFIRMEDor /rider, PICKUP_CONFIRMED)or │customer) ▼withdraws EscrowEntityTZS status30,000
=STEP RELEASED1 │— ▼Money JournalEntry:earmarked:
DEBIT ASSET_FLOATLIABILITY_WALLETS 30,000
CREDIT ASSET_ESCROWLIABILITY_SETTLEMENTS │30,000 ▼(being processed)
STEP 2 — JikoXpress calls Selcom API, sends to Mpesa:
DEBIT LIABILITY_SETTLEMENTS 30,000
CREDIT ASSET_PSP_SELCOM 30,000 (money leaves)
Money Splitarrives Calculatoron runsMpesa │✅
▼Same TransactionSplitEntityflow createdfor percustomers, splitkitchen │owners, ▼
Each split credited to destinationriders
Scenario 3.3 — Payout Fails
Selcom API returns failure (wrong number, system down etc)
DEBIT LIABILITY_SETTLEMENTS 30,000 (earmark reversed)
CREDIT LIABILITY_WALLETS 30,000 (money back in wallet)
User notified. They can retry. No money lost. ✅
11. Subscription Payments
Kitchen owners pay monthly or yearly. They choose how to pay.
Scenario 11.1 — Subscription via Wallet
Mama Lishe pays TZS 15,000/month from wallet
Pure internal — no PSP:
DEBIT LIABILITY_WALLETS 15,000
CREDIT REVENUE_SUBSCRIPTION 15,000
Kitchen wallet -= TZS 15,000
Platform revenue += TZS 15,000 ✅
Scenario 11.2 — Subscription via Mobile Money
DEBIT ASSET_PSP_SELCOM 15,000
CREDIT REVENUE_SUBSCRIPTION 15,000
Platform revenue += TZS 15,000 ✅
Renewal Logic
On renewal date:
1. Check if owner chose wallet AND has sufficient balance
2. If yes → auto deduct (Scenario 11.1)
3. If no balance OR chose mobile money → send payment prompt
4. If payment fails → grace period (configurable, e.g. 3 days)
5. If still unpaid after grace → kitchen suspended
12. Settlements & Payouts
Any user can request a payout at any time — customers, kitchen owners, riders. Same mechanism for all.
5.Rules
- Amount cannot exceed available wallet balance
- Minimum payout amount configurable in platform settings
- Processing time depends on external PSP
- Failed payouts → money returns to wallet automatically
- Full audit trail — who requested, when, destination, result
13. Refunds & Cancellations
Scenario 13.1 — Order Cancelled, Money in Escrow, service.fee.refundable=true
TZS 18,000 in escrow, full refund including service fee
DEBIT ASSET_ESCROW 18,000
CREDIT ASSET_PSP_SELCOM 18,000
Selcom API → refund to customer Mpesa
Platform earns nothing ✅
Scenario 13.2 — Order Cancelled, service.fee.refundable=false
Platform keeps service fee
DEBIT ASSET_ESCROW 18,000
CREDIT ASSET_PSP_SELCOM 17,000 (customer refund minus fee)
CREDIT REVENUE_SERVICE_FEE 1,000 (platform keeps)
Scenario 13.3 — Order Cancelled, Paid via Wallet
DEBIT ASSET_ESCROW 18,000
CREDIT LIABILITY_WALLETS 18,000 (back to customer wallet)
No PSP call. Pure internal. Instant refund to wallet ✅
Scenario 13.4 — Post-Completion Refund Flow(Dispute)
Order Cancelleddelivered, /escrow Refundalready Requestedreleased, │kitchen ▼already Checkpaid
Rare — dispute/complaint scenario. Manual admin action required.
DEBIT LIABILITY_WALLETS 10,000 (kitchen wallet reduced)
DEBIT EXPENSE_REFUNDS 10,000
CREDIT ASSET_PSP_SELCOM 10,000 (refund to customer)
Full audit trail. SUPER_ADMIN approval required.
14. Admin Withdrawal
Platform accumulates profit from service fees and subscriptions. Admin can withdraw profit only.
Rules
- Only
SUPER_ADMINcan request - Requires second
SUPER_ADMINapproval - Can only withdraw from net profit (REVENUE minus EXPENSE minus already withdrawn)
- Can never withdraw from escrow — belongs to customers
- Can never withdraw from
LIABILITY_WALLETS— belongs to users - System enforces hard limit — no override
Withdrawal Calculation
Available to withdraw =
REVENUE_SERVICE_FEE
+ REVENUE_SUBSCRIPTION
- EXPENSE_REFUNDS
- previously_withdrawn
If requested amount <= available → allow ✅
If requested amount > available → reject hard ❌
Journal Entry
DEBIT EQUITY_RETAINED_EARNINGS 500,000
CREDIT ASSET_PSP_SELCOM 500,000
Selcom API → transfers to admin Mpesa ✅
15. PSP Reconciliation
The Rule
Selcom actual balance >= ASSET_PSP_SELCOM ✅ (normal — other money may be there too)
Selcom actual balance < ASSET_PSP_SELCOM ❌ CRISIS — money is missing
The Selcom account may receive money from other business activities unrelated to JikoXpress. That is expected and normal. The check only validates that JikoXpress-tracked money is fully covered.
Daily Reconciliation Process
1. Pull Selcom transaction log for the day
2. Match each incoming payment to a TransactionEntity │by ├──reference/amount
3. Flag unmatched payments (other sources — expected, not a problem)
4. Flag any JikoXpress transaction with no matching Selcom record (problem)
5. Alert if ASSET_PSP_SELCOM > Selcom actual balance (crisis)
16. Reporting
Treasury Snapshot (Platform View)
WHAT WE HAVE:
At Selcom: ASSET_PSP_SELCOM balance
At Azampay: ASSET_PSP_AZAMPAY balance
In Escrowescrow: ASSET_ESCROW balance
WHAT WE OWE:
To all users (wallets): LIABILITY_WALLETS balance
Payouts in progress: LIABILITY_SETTLEMENTS balance
WHAT WE EARNED:
Service fees: REVENUE_SERVICE_FEE balance
Subscriptions: REVENUE_SUBSCRIPTION balance
Net profit: REVENUE_SERVICE_FEE + REVENUE_SUBSCRIPTION - EXPENSE_REFUNDS
Kitchen Owner Sales Report
Reads from orders + wallet transactions — NOT treasury directly:
SALES REPORT — Mama Lishe Kitchen | April 2026
────────────────────────────────────────────►
│ EscrowEntity status = REFUNDED │
│ JournalEntry: │
│ DEBIT ASSET_FLOAT │
│ CREDIT ASSET_ESCROW │
│ │
└──
AlreadyCHANNEL SettledBREAKDOWN:
App orders TZS 150,000 (in your JikoXpress wallet)
WhatsApp orders TZS 80,000 (in your JikoXpress wallet)
Cash (POS) TZS 200,000 (collected physically — in your till)
Lipa Voda (POS) TZS 50,000 (in your Lipa Voda account)
TOTAL SALES: TZS 480,000
───────────────────────────────────► │
KitchenEarnings debited │
JournalEntry: │
DEBIT LIABILITY_SETTLEMENTS │
CREDIT ASSET_FLOAT │
│
Refund Processed ◄─────┘──────
│JIKOXPRESS ▼WALLET:
DisbursementEntityAvailable createdbalance TZS 230,000 (purpose: REFUND)
│
▼
External provider API
(return moneyready to customer)
│
▼
JournalEntry:
DEBIT EXPENSE_REFUNDS
CREDIT ASSET_FLOATwithdraw)
Individual 5.5Wallet Kitchen Settlement FlowStatement
KitchenMY Owner requests payout
│
▼
KitchenEarningsEntity checked
(available balance)
│
▼
SettlementEntity created
status = PENDING
│
▼
JournalEntry:
DEBIT LIABILITY_SETTLEMENTS
CREDIT ASSET_FLOAT
│
▼
DisbursementEntity created
(purpose: KITCHEN_SETTLEMENT)
│
▼
External provider API called
(transfer to owner mobile/bank)
│
▼
Provider webhook confirms
│
▼
DisbursementEntity status = COMPLETED
SettlementEntity status = COMPLETED
KitchenEarningsEntity balance -= amount
5.6 Admin Profit Withdrawal Flow
Super Admin requests withdrawal
│
▼
WithdrawalEntity created
status = PENDING
│
▼
Second Admin approves
│
▼
WithdrawalEntity status = APPROVED
│
▼
JournalEntry:
DEBIT EQUITY_RETAINED_EARNINGS
CREDIT ASSET_FLOAT
│
▼
DisbursementEntity created
(purpose: PROFIT_WITHDRAWAL)
│
▼
External provider transfer
│
▼
WithdrawalEntity status = COMPLETED
5.7 Platform Offer Flow
App customer applies offer at checkout
│
▼
PlatformOfferEntity validated
- Is active?
- Budget available?
- User eligible?
- Conditions met?
│
▼
Offer applied
Customer pays reduced amount
│
▼
Two TransactionEntities created:
1. Customer payment (reduced amount)
2. Platform payment (offer subsidy amount)
- paidBy = PLATFORM
- offerRef = offer uuid
│
▼
PlatformOfferUsageEntity created
PlatformOfferEntity.budgetUsed += subsidy amount
│
▼
If budgetUsed >= budget
→ offer auto-deactivated
│
▼
JournalEntry:
DEBIT EXPENSE_OFFER_SUBSIDY
CREDIT ASSET_FLOAT
│
▼
Both transactions confirmed
→ Order created
→ Kitchen gets full amount
→ Platform covered the difference
6. User Journeys
6.1 CustomerWALLET — PayKibuti for Order (POS Cash)
Customer at counter
│
▼
Staff adds items to checkout session
│
▼
Staff selects: Pay Now → Cash
│
▼
System calculates total
│
▼
Staff collects cash from customer
│
▼
Staff confirms payment in system
│
▼
Transaction recorded (CASH, no escrow)
Money split immediately
│
▼
Order created → kitchen notified
│
▼
Receipt printed (frontend)
│
▼
Kitchen prepares → food ready
│
▼
Customer receives food
6.2 Customer — App Delivery with Mobile Money
App customer browses kitchens
│
▼
Adds items to cart
│
▼
Proceeds to checkout
Enters delivery address
│
▼
Selects Mobile Money payment
│
▼
System creates checkout session
Inventory held
│
▼
USSD prompt sent to customer phone
│
▼
Customer enters PIN on phone
│
▼
Provider confirms → webhook fires
│
▼
Transaction recorded
holdInEscrow = true
EscrowEntity created
│
▼
Order created → kitchen notified
│
▼
Kitchen prepares → packages food
│
▼
Delivery partner picks up
│
▼
Delivery partner confirms delivery
│
▼
Escrow released
Money split:
→ Kitchen earnings
→ Delivery partner paid
→ Platform commission recorded
│
▼
Customer receives food
6.3 Customer — Kiosk Cash Slip
Customer at kiosk machine
│
▼
Customer selects items
│
▼
Customer selects: Pay Cash
│
▼
System creates SLIP session
Inventory held
Slip expires in 15 min
│
▼
Slip printed (frontend handles)
│
▼
Customer walks to cashier with slip
│
▼
Cashier scans slip barcode
Finds session in queue
│
▼
Cashier collects cash
Confirms payment in system
│
▼
Transaction recorded (CASH, no escrow)
Money split immediately
│
▼
Order created → kitchen notified
│
▼
Kitchen prepares food
│
▼
Customer number called
Customer collects food
6.4 Customer — Dine In with Tab (Multiple Rounds)
Customer sits at table
│
▼
Waiter takes order → Round 1
Creates TAB session
New KitchenBillsEntity opened
│
▼
Kitchen ticket printed for Round 1
Kitchen prepares
│
▼
Customer orders more → Round 2
Waiter adds new session to same bill
│
▼
Kitchen ticket printed for Round 2
Kitchen prepares
│
▼
Customer asks for bill
Waiter prints bill (all rounds)
│
▼
Customer pays
Waiter selects payment method(s)
│
▼
Transaction(s) recorded
Money split
│
▼
KitchenBillsEntity closed
Receipt printed
6.5 Kitchen Owner — Request Settlement
Kitchen owner opens earnings screen
Sees available balance
│
▼
Clicks "Request Payout"
Enters amount and destination
│
▼
SettlementEntity created (PENDING)
│
▼
Platform processes
DisbursementEntity created
External transfer initiated
│
▼
Provider confirms
│
▼
SettlementEntity COMPLETED
KitchenEarnings balance updated
│
▼
Kitchen owner receives money
on mobile/bank account
6.6 Admin — Treasury Topup
Platform needs more float
Admin deposits to Selcom
│
▼
Admin records topup in system
TreasuryTopupEntity created
│
▼
JournalEntry:
DEBIT ASSET_FLOAT
CREDIT EQUITY_CAPITAL
│
▼
Platform float increases
Ready for disbursements
6.7 Admin — Profit Withdrawal
Platform has accumulated profits
Super admin requests withdrawal
│
▼
WithdrawalEntity created (PENDING)
Second admin notified for approval
│
▼
Second admin approves
│
▼
JournalEntry recorded
DisbursementEntity created
External transfer initiated
│
▼
WithdrawalEntity COMPLETED
Profits withdrawn
7. Payment Channels
KitchenMY Owner requests payout
│
▼
KitchenEarningsEntity checked
(available balance)
│
▼
SettlementEntity created
status = PENDING
│
▼
JournalEntry:
DEBIT LIABILITY_SETTLEMENTS
CREDIT ASSET_FLOAT
│
▼
DisbursementEntity created
(purpose: KITCHEN_SETTLEMENT)
│
▼
External provider API called
(transfer to owner mobile/bank)
│
▼
Provider webhook confirms
│
▼
DisbursementEntity status = COMPLETED
SettlementEntity status = COMPLETED
KitchenEarningsEntity balance -= amount
Super Admin requests withdrawal
│
▼
WithdrawalEntity created
status = PENDING
│
▼
Second Admin approves
│
▼
WithdrawalEntity status = APPROVED
│
▼
JournalEntry:
DEBIT EQUITY_RETAINED_EARNINGS
CREDIT ASSET_FLOAT
│
▼
DisbursementEntity created
(purpose: PROFIT_WITHDRAWAL)
│
▼
External provider transfer
│
▼
WithdrawalEntity status = COMPLETED
App customer applies offer at checkout
│
▼
PlatformOfferEntity validated
- Is active?
- Budget available?
- User eligible?
- Conditions met?
│
▼
Offer applied
Customer pays reduced amount
│
▼
Two TransactionEntities created:
1. Customer payment (reduced amount)
2. Platform payment (offer subsidy amount)
- paidBy = PLATFORM
- offerRef = offer uuid
│
▼
PlatformOfferUsageEntity created
PlatformOfferEntity.budgetUsed += subsidy amount
│
▼
If budgetUsed >= budget
→ offer auto-deactivated
│
▼
JournalEntry:
DEBIT EXPENSE_OFFER_SUBSIDY
CREDIT ASSET_FLOAT
│
▼
Both transactions confirmed
→ Order created
→ Kitchen gets full amount
→ Platform covered the difference
Customer at counter
│
▼
Staff adds items to checkout session
│
▼
Staff selects: Pay Now → Cash
│
▼
System calculates total
│
▼
Staff collects cash from customer
│
▼
Staff confirms payment in system
│
▼
Transaction recorded (CASH, no escrow)
Money split immediately
│
▼
Order created → kitchen notified
│
▼
Receipt printed (frontend)
│
▼
Kitchen prepares → food ready
│
▼
Customer receives food
App customer browses kitchens
│
▼
Adds items to cart
│
▼
Proceeds to checkout
Enters delivery address
│
▼
Selects Mobile Money payment
│
▼
System creates checkout session
Inventory held
│
▼
USSD prompt sent to customer phone
│
▼
Customer enters PIN on phone
│
▼
Provider confirms → webhook fires
│
▼
Transaction recorded
holdInEscrow = true
EscrowEntity created
│
▼
Order created → kitchen notified
│
▼
Kitchen prepares → packages food
│
▼
Delivery partner picks up
│
▼
Delivery partner confirms delivery
│
▼
Escrow released
Money split:
→ Kitchen earnings
→ Delivery partner paid
→ Platform commission recorded
│
▼
Customer receives food
Customer at kiosk machine
│
▼
Customer selects items
│
▼
Customer selects: Pay Cash
│
▼
System creates SLIP session
Inventory held
Slip expires in 15 min
│
▼
Slip printed (frontend handles)
│
▼
Customer walks to cashier with slip
│
▼
Cashier scans slip barcode
Finds session in queue
│
▼
Cashier collects cash
Confirms payment in system
│
▼
Transaction recorded (CASH, no escrow)
Money split immediately
│
▼
Order created → kitchen notified
│
▼
Kitchen prepares food
│
▼
Customer number called
Customer collects food
Customer sits at table
│
▼
Waiter takes order → Round 1
Creates TAB session
New KitchenBillsEntity opened
│
▼
Kitchen ticket printed for Round 1
Kitchen prepares
│
▼
Customer orders more → Round 2
Waiter adds new session to same bill
│
▼
Kitchen ticket printed for Round 2
Kitchen prepares
│
▼
Customer asks for bill
Waiter prints bill (all rounds)
│
▼
Customer pays
Waiter selects payment method(s)
│
▼
Transaction(s) recorded
Money split
│
▼
KitchenBillsEntity closed
Receipt printed
Kitchen owner opens earnings screen
Sees available balance
│
▼
Clicks "Request Payout"
Enters amount and destination
│
▼
SettlementEntity created (PENDING)
│
▼
Platform processes
DisbursementEntity created
External transfer initiated
│
▼
Provider confirms
│
▼
SettlementEntity COMPLETED
KitchenEarnings balance updated
│
▼
Kitchen owner receives money
on mobile/bank account
Platform needs more float
Admin deposits to Selcom
│
▼
Admin records topup in system
TreasuryTopupEntity created
│
▼
JournalEntry:
DEBIT ASSET_FLOAT
CREDIT EQUITY_CAPITAL
│
▼
Platform float increases
Ready for disbursements
Platform has accumulated profits
Super admin requests withdrawal
│
▼
WithdrawalEntity created (PENDING)
Second admin notified for approval
│
▼
Second admin approves
│
▼
JournalEntry recorded
DisbursementEntity created
External transfer initiated
│
▼
WithdrawalEntity COMPLETED
Profits withdrawn
8. Escrow System
When to Hold
Escrow flag (holdInEscrow) is set dynamically at transaction creation based on current business rules. Not hardcoded.
Current default rules:
DELIVERY fulfillment → hold until delivery confirmedAll others → no hold (immediate split)
Future rules can be added without code changes — just update the rule engine.
Release Conditions
9. Money Splitting
Splits are dynamic — configured per order based on what services are included.
Split Types
New split types can be added as new services are introduced — no entity changes needed.
10. Platform Offers
Offer Types
Offer Validation Rules
User must be eligible (e.g. first 2 orders check)Offer must be activeBudget must not be exhaustedOrder must meet minimum conditionsChannel must be APP — offers are app users only
Budget Control
PlatformOfferEntity.budget = TZS 1,000,000 (allocated)
PlatformOfferEntity.budgetUsed = TZS 850,000 (spent so far)
Remaining = TZS 150,000
When budgetUsed >= budget:
→ offer.active = false (auto-deactivated)
→ No more usage allowed
11. Settlements & Withdrawals
Kitchen Settlement Rules
Kitchen owner can request anytimeMinimum payout amount (configurable)Cannot exceed available KitchenEarnings balanceProcessing time depends on external providerFailed settlements → DisbursementEntity FAILED → retry
Admin Withdrawal Rules
Only SUPER_ADMIN can requestRequires second SUPER_ADMIN approvalCannot withdraw from escrow — those funds belong to customersCannot withdraw more than available ASSET_FLOATFull audit trail — who requested, who approved, when, where it went
12. P&L & Reporting
Revenue Report
Period:| April 2026
Commission:──────────────────────────────────────────────────────
Current Balance: TZS 2,500,000
Subscription fees: TZS 1,200,000
Service fees: TZS 300,45,000
──────────────────────────────────────────────────────
TotalDATE Revenue:DESCRIPTION TZSIN 4,000,OUT BALANCE
Apr 23 Top up via Mpesa 50,000 50,000
ExpenseApr Report
23 Period:Order #47 — Mama Lishe 5,000 45,000
Apr 22 Order #31 earnings 8,500 53,500
Apr 21 Withdrawal to Mpesa 15,000 38,500
Apr 20 Subscription — April 2026
Delivery payouts: TZS 800,15,000 Offer subsidies: TZS 400,000
Refunds: TZS 150,00053,500
─────────────────────────────────
Total Expenses: TZS 1,350,000─────────────────────
Same wallet, same statement format — whether customer, kitchen owner, or rider. Transaction type field enables per-role breakdown reports.
NetHow ProfitTreasury Gets Breakdown by Role
NetTotal Profitrider earnings in wallets =
TZSsum(WalletTransactionEntity 4,000,000where - TZS 1,350,000type = TZSDELIVERY_EARNING 2,650,000and not withdrawn)
Total kitchen earnings in wallets =
sum(WalletTransactionEntity where type = ORDER_EARNING and not withdrawn)
Balance
Treasury Sheetsees
.ASSETSLIABILITY_WALLETSFloat:= TZS 5,000,000Escrow:WalletTZStransaction300,000typesReceivables:explainTZSthe200,000breakdownTotalunderneathAssets:it.TZS
5,500,00017.
LIABILITIESSafetyKitchenRulessettlements&due:IntegrityTZSChecks2,000,000Primary
PlatformSafetywallets:CheckTZS(Runs500,000Continuously)TotalASSET_PSP_SELCOMLiabilities:+TZSASSET_PSP_AZAMPAY2,500,000+EQUITYASSET_ESCROWCapital:>=TZSLIABILITY_WALLETS1,000,000+RetainedLIABILITY_SETTLEMENTSearnings:IfTZSfalse2,000,000→TotalALERTEquity:IMMEDIATELYTZS 3,000,000🚨
Projections
Based on historical journal entries:
Month over month commission growthAverage order value trendOffer subsidy burn rateSettlement frequency per kitchen
13. Security &Immutability Rules
Immutability
JournalEntryEntity— never updated, neverdeleteddeleted. Append-only ledger.TransactionEntity— status only movesforwardforward:(PENDING → COMPLETED →. Never backwards.REFUNDED)REFUNDED— neverPlatformOfferUsageEntityWalletTransactionEntitydeleteddeleted.balanceBeforeandbalanceAfteron every record. History reconstructable at any point in time.
BalanceEscrow Integrity Check
AccountASSET_ESCROW balance = sum of allcreditEscrowEntityjournalwhereentriesstatus-=sumHELDofIfallmismatchdebit→entriesdiscrepancy Runningalertbalance🚨maintainedANDAdmin
verifiableWithdrawalbyHardrecalculationLimit
DiscrepancyWithdrawalalertsamount <= net profit available System rejects ifrunningexceeded.balanceNo!=override.calculatedNobalanceexceptions.
Access Control
| Action | Who |
|---|---|
| View treasury | SUPER_ADMIN |
| Request |
|
| Approve admin withdrawal | Second SUPER_ADMIN |
| SUPER_ADMIN | |
| View own |
|
| Create platform offers | SUPER_ADMIN |
Audit
Trail
18.
EveryDecision entityLog
has
| Decision | Choice | Rationale |
|---|---|---|
| Tax handling | Dropped for V1 | Reduces complexity. Kitchen owner responsible for TRA compliance. Add later if legal pressure arises. |
| Escrow rule location | Dynamic flag from checkout caller | Financial service stays dumb — just executes. Business logic lives in checkout. Decoupled. |
| Service fee refundability | Config flag in properties file | One switch changes behavior platform-wide. No code change needed. |
| Separate wallets per role | No — one wallet per person | Same account = customer, owner, rider. No artificial separation by role. |
| Treasury breakdown per role | Not in treasury — in wallet transaction types | Treasury sees total obligation. Breakdown via |
Full
|
Proper double-entry ledger, not QuickBooks | Platform holds other people's money — can't skip proper tracking. Kept fit-for-purpose, not over-engineered. |
| PSP accounts in chart | Yes — one per PSP | Need to know where money |
| Counter orders in treasury | No | Cash/kitchen custom never touch JikoXpress PSP. Platform is just a tool. Covered by subscription. |
| Admin withdrawal restriction | Revenue/profit only | Can never touch escrow or user wallet liabilities. Non-negotiable safety rule. |
| Payout eligibility | Any user | Customers, kitchen owners, riders — same wallet system, same payout flow. Role irrelevant. |
| Subscription payment method | User's choice | Flexibility. Auto-deduct from wallet if chosen and balance sufficient. Fallback to prompt. |
| Kitchen wallet | Completely separate from platform | Kitchen manages independently. Platform records usage only. No treasury involvement. |
QBIT SPARK CO LIMITED | JikoXpress Pro | April 2026
Internal financial architecture document — confidential