# JikoXpress Financial System Architecture

# JikoXpress Pro — Financial System Architecture

> Version 2.0 | April 2026 | QBIT SPARK CO LIMITED
> Refined architecture based on full design sessions — all scenarios, edge cases, and decisions captured.

---

## Table of Contents

1. [Overview & Core Principles](#1-overview--core-principles)
2. [Platform Context](#2-platform-context)
3. [Two Financial Worlds](#3-two-financial-worlds)
4. [Treasury Design](#4-treasury-design)
5. [Wallet System](#5-wallet-system)
6. [Chart of Accounts](#6-chart-of-accounts)
7. [Money Splitting](#7-money-splitting)
8. [Escrow System](#8-escrow-system)
9. [Payment Channels](#9-payment-channels)
10. [Full Money Journey — All Scenarios](#10-full-money-journey--all-scenarios)
11. [Subscription Payments](#11-subscription-payments)
12. [Settlements & Payouts](#12-settlements--payouts)
13. [Refunds & Cancellations](#13-refunds--cancellations)
14. [Admin Withdrawal](#14-admin-withdrawal)
15. [PSP Reconciliation](#15-psp-reconciliation)
16. [Reporting](#16-reporting)
17. [Safety Rules & Integrity Checks](#17-safety-rules--integrity-checks)
18. [Decision Log](#18-decision-log)

---

## 1. Overview & Core Principles

JikoXpress financial system is a **unified internal accounting engine** — a proper ledger that tracks every single money movement on the platform, regardless of amount (even TZS 0.00), channel, or purpose.

### Core Principles

| Principle | Description |
|---|---|
| Every event is a transaction | Cash, mobile money, wallet, kitchen wallet, TZS 0 promo orders — all recorded |
| Double entry accounting | Every debit has a credit. Books always balance |
| Single source of truth | Internal ledger is authoritative — not the external PSP |
| Dynamic money splitting | Splits are passed as parameters, not hardcoded |
| Escrow is a flag | Any transaction can be held — decided at runtime by the caller |
| Everyone 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 |

---

## 2. Platform Context

JikoXpress Pro connects customers with local chefs and restaurants (like Toasty for East Africa). The platform has:

- **7 sales channels**: POS, Kiosk, Table QR, Mobile App, WhatsApp, Drive-Through, Direct Counter
- **4 subscription tiers**: STARTER, GROWING, PROFESSIONAL, ENTERPRISE
- **Multiple fulfillment types**: Dine-in, Pickup, Delivery, Drive-Through
- **East Africa first**: Built for M-Pesa, Tigo Pesa, Airtel Money via Selcom, Azampay

### 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 Financial Worlds

Not all money flows through JikoXpress. There are two separate worlds:

```
┌──────────────────────────────────────────────────────┐
│              WORLD 1 — JikoXpress Pool                │
│                                                        │
│  App orders, WhatsApp orders                          │
│  Money flows through our PSP accounts                 │
│  Treasury tracks everything                           │
│  Platform earns service fee here                      │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│         WORLD 2 — Kitchen's Own World                 │
│                                                        │
│  POS 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 earns ZERO here (covered by subscription)  │
└──────────────────────────────────────────────────────┘
```

### Which Channels Belong to Which World?

| Channel | World | Service Fee | Treasury Involvement |
|---|---|---|---|
| Mobile App | JikoXpress Pool | Yes | Full |
| WhatsApp | 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.

---

## 4. Treasury Design

The treasury is the **central bank of JikoXpress**. It is a proper ledger using double entry accounting — every money movement has two sides (debit and credit), books always balance.

### What Double Entry Means

Money never appears or 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 shilling in the 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 fees (OURS)

EXPENSE_REFUNDS       — money returned to customers
```

### The 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 user gets one wallet automatically on account creation.

- One wallet per person, regardless of role
- Used to: top up, pay orders, receive earnings (kitchen revenue, delivery fees), withdraw to Mpesa
- Lives under `LIABILITY_WALLETS` in treasury — it's the user's money, not the platform's

### Kitchen Wallet (Kitchen-Level)

A completely separate system — **not** the platform wallet.

- Issued by the kitchen to their own loyal customers
- Kitchen manages it independently (deposits, deductions, balance)
- Has its own ledger — kitchen's books, not platform's
- Platform only records "paid via KITCHEN_WALLET" in the transaction — no treasury movement

### Wallet vs Treasury

| 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

Every wallet movement creates a record with:

- `type` — TOPUP, ORDER_PAYMENT, ORDER_EARNING, DELIVERY_EARNING, WITHDRAWAL, SUBSCRIPTION_PAYMENT
- `direction` — CREDIT (in) or DEBIT (out)
- `amount`
- `balanceBefore` and `balanceAfter` — full audit trail, history reconstructable at any point
- `referenceId` — order ID, payout ID etc

---

## 6. Chart of Accounts

```
ASSETS  (what platform has / controls)
├── ASSET_PSP_SELCOM          Physical money at Selcom
├── ASSET_PSP_AZAMPAY         Physical money at Azampay
└── ASSET_ESCROW              Held money — no owner yet

LIABILITIES  (what platform owes — never touch without permission)
├── LIABILITY_WALLETS         All user wallets combined
└── LIABILITY_SETTLEMENTS     Payout requests being processed

REVENUE  (platform's own earnings)
├── REVENUE_SERVICE_FEE       Cut from App/WhatsApp orders
└── REVENUE_SUBSCRIPTION      Kitchen monthly/yearly fees

EXPENSE  (money platform spent)
└── EXPENSE_REFUNDS           Refunds sent back to customers

EQUITY  (platform net worth)
├── EQUITY_CAPITAL            Admin deposits / investments into platform
└── EQUITY_RETAINED_EARNINGS  Accumulated platform profits
```

### P&L Formula

```
Platform Net Profit = REVENUE_SERVICE_FEE + REVENUE_SUBSCRIPTION - EXPENSE_REFUNDS

Admin can only withdraw from Net Profit — never from ASSETS covering LIABILITIES
```

---

## 7. Money Splitting

When an online order is paid, the total is split into buckets dynamically. The caller (checkout service) passes the split parameters — the financial service just executes. No business logic in the financial service.

### Split Buckets per Online Order

| Split Type | Goes To | Notes |
|---|---|---|
| Menu revenue | Kitchen owner's wallet | Main food items |
| Packaging fee | Kitchen owner's wallet | Kitchen sells packaging too |
| Delivery fee | Rider's wallet | Only on delivery orders |
| Service fee | `REVENUE_SERVICE_FEE` | App/WhatsApp 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.

### How It Works

Every transaction has:
- `holdInEscrow: true/false`
- `escrowReleaseCondition: DELIVERY_CONFIRMED | PICKUP_CODE_CONFIRMED | null`

Financial service does exactly what it's told. It doesn't know WHY — the checkout layer knows that.

### Default Business Rules (Set by Checkout, Not Financial Service)

| Fulfillment Type | Payment Channel | Hold in Escrow? | Release Condition |
|---|---|---|---|
| Delivery | Mobile Money | Yes | DELIVERY_CONFIRMED |
| Delivery | Platform Wallet | Yes | DELIVERY_CONFIRMED |
| Pickup | Mobile Money | Yes | PICKUP_CODE_CONFIRMED |
| Pickup | Platform Wallet | Yes | PICKUP_CODE_CONFIRMED |
| Dine-in | Any online | No | Immediate |
| Any | Cash | No (cash is physical) | Immediate |

**Rules can change without touching the financial service** — just update the checkout layer logic.

---

## 9. Payment Channels

| Channel | Hits Treasury? | External PSP API? | Escrow Possible? | Service Fee? |
|---|---|---|---|---|
| CASH | No | No | No | No |
| KITCHEN_CUSTOM (Lipa Voda etc) | No | No | No | No |
| KITCHEN_WALLET | No | No | No | No |
| MOBILE_MONEY (USSD) | Yes | Yes (Selcom/Azampay) | Yes | If online channel |
| PLATFORM_WALLET | Yes (internal) | No | Yes | If online channel |
| CARD | Yes | Yes (card gateway) | Yes | If online channel |

---

## 10. Full Money Journey — All Scenarios

### Chapter 1 — Counter Orders (World 2 — No Treasury Involvement)

---

**Scenario 1.1 — POS Cash, Any Fulfillment**

```
Customer pays TZS 10,000 cash at counter

→ Order recorded ✅
→ Transaction logged (channel: CASH) ✅
→ Receipt generated ✅
→ Money physically in kitchen till 💰
→ Treasury: nothing moves ❌
→ Platform earns: TZS 0 (covered by subscription)

Kitchen sales report shows: CASH += TZS 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 customer has TZS 20,000 in kitchen wallet
Orders food 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:         TZS 10,500
  Packaging:    TZS   500
  Service fee:  TZS 1,000

STEP 1 — Payment initiated:
  Checkout session created, inventory held
  USSD prompt sent to customer via Selcom API
  Customer confirms on phone
  Selcom webhook fires

STEP 2 — Money arrives, held in escrow (pickup not yet confirmed):
  DEBIT  ASSET_PSP_SELCOM    12,000
  CREDIT ASSET_ESCROW        12,000

STEP 3 — Order confirmed, 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 Payment (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 same 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 (Full Promo)**

```
Platform gives customer completely free order

TransactionEntity still created:
  amount: 0.00
  channel: PLATFORM
  holdInEscrow: false

Still recorded. Kitchen notified. Order flows normally.
No money moves. Event is captured. ✅
```

---

### Chapter 3 — Wallet Operations

---

**Scenario 3.1 — User Tops Up Wallet**

```
Kibuti tops up TZS 50,000 via Mpesa

  DEBIT  ASSET_PSP_SELCOM      50,000
  CREDIT LIABILITY_WALLETS     50,000  (belongs to Kibuti)

Kibuti wallet += TZS 50,000 ✅
```

---

**Scenario 3.2 — Anyone Requests Payout to Mpesa**

```
Kitchen owner (or rider, or customer) withdraws TZS 30,000

STEP 1 — Money earmarked:
  DEBIT  LIABILITY_WALLETS      30,000
  CREDIT LIABILITY_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 arrives on Mpesa ✅
Same flow for customers, kitchen owners, riders
```

---

**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.

### Rules

1. Amount cannot exceed available wallet balance
2. Minimum payout amount configurable in platform settings
3. Processing time depends on external PSP
4. Failed payouts → money returns to wallet automatically
5. 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 (Dispute)**

```
Order delivered, escrow already released, kitchen already paid
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

1. Only `SUPER_ADMIN` can request
2. Requires second `SUPER_ADMIN` approval
3. Can only withdraw from net profit (REVENUE minus EXPENSE minus already withdrawn)
4. Can never withdraw from escrow — belongs to customers
5. Can never withdraw from `LIABILITY_WALLETS` — belongs to users
6. 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 escrow:              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
──────────────────────────────────────────────
CHANNEL BREAKDOWN:
  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
──────────────────────────────────────────────
JIKOXPRESS WALLET:
  Available balance  TZS 230,000    (ready to withdraw)
```

### Individual Wallet Statement

```
MY WALLET — Kibuti | April 2026
──────────────────────────────────────────────────────
Current Balance: TZS 45,000
──────────────────────────────────────────────────────
DATE      DESCRIPTION                  IN        OUT   BALANCE
Apr 23    Top up via Mpesa         50,000           50,000
Apr 23    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              15,000  53,500
──────────────────────────────────────────────────────
```

Same wallet, same statement format — whether customer, kitchen owner, or rider. Transaction `type` field enables per-role breakdown reports.

### How Treasury Gets Breakdown by Role

```
Total rider earnings in wallets  =
  sum(WalletTransactionEntity where type = DELIVERY_EARNING and not withdrawn)

Total kitchen earnings in wallets =
  sum(WalletTransactionEntity where type = ORDER_EARNING and not withdrawn)
```

Treasury sees `LIABILITY_WALLETS = TZS 5,000,000`.
Wallet transaction types explain the breakdown underneath it.

---

## 17. Safety Rules & Integrity Checks

### Primary Safety Check (Runs Continuously)

```
ASSET_PSP_SELCOM + ASSET_PSP_AZAMPAY + ASSET_ESCROW
>=
LIABILITY_WALLETS + LIABILITY_SETTLEMENTS

If false → ALERT IMMEDIATELY 🚨
```

### Immutability Rules

- `JournalEntryEntity` — never updated, never deleted. Append-only ledger.
- `TransactionEntity` — status only moves forward: `PENDING → COMPLETED → REFUNDED`. Never backwards.
- `WalletTransactionEntity` — never deleted. `balanceBefore` and `balanceAfter` on every record. History reconstructable at any point in time.

### Escrow Integrity Check

```
ASSET_ESCROW balance = sum of all EscrowEntity where status = HELD

If mismatch → discrepancy alert 🚨
```

### Admin Withdrawal Hard Limit

```
Withdrawal amount <= net profit available

System rejects if exceeded. No override. No exceptions.
```

### Access Control

| Action | Who |
|---|---|
| View treasury | SUPER_ADMIN |
| Request payout | Any user (own wallet only) |
| Approve admin withdrawal | Second SUPER_ADMIN |
| View platform P&L | SUPER_ADMIN |
| View own wallet | Any user |
| Manual post-completion refund | SUPER_ADMIN + approval |
| Create platform offers | SUPER_ADMIN |

---

## 18. TODO — Pending Design Decisions

These are confirmed features that need to be designed when we reach the relevant service/module.

---

### TODO-001 — Kitchen Delivery Configuration
**Where:** Kitchen settings / Kitchen config entity
**What:** Kitchen owner needs ability to configure their delivery model:

```
deliveryModel:
  PLATFORM_RIDER   — use JikoXpress riders (default)
  OWN_RIDER        — kitchen has own boda, delivers themselves
  NO_DELIVERY      — kitchen does not offer delivery

absorbDeliveryFee: true/false
  true  — kitchen covers delivery cost for customer (free delivery for customer)
  false — customer pays delivery fee (default)

deliveryCoverageRange: Integer (km)
  — only absorb delivery fee for orders within this range
  — e.g. 3 = free delivery within 3km, customer pays beyond that
```

**Impact on splits:**
- `PLATFORM_RIDER` + `absorbDeliveryFee=true` → kitchen split reduced, rider still paid, customer sees free delivery
- `OWN_RIDER` → no rider split, no DisbursementEntity for rider, kitchen keeps full amount
- Checkout service reads kitchen config and builds splits accordingly
- Financial service just executes whatever splits it receives — no business logic inside

**Delivery fee deduction logic (checkout service responsibility):**
- Kitchen delivery subsidy is deducted from kitchen earnings on that specific order — no pre-loaded balance required
- Checkout service calculates net kitchen earning BEFORE sending splits to financial service:
  ```
  kitchenNet = kitchenEarning - deliveryFee
  ```
- If `kitchenNet < 0` (delivery fee exceeds kitchen earning on that order):
  → subsidy auto-disabled for this order
  → customer pays delivery fee normally
  → no debt created, no blocking
- Financial service receives already-calculated net splits — executes blindly
- `SplitFundedBy.KITCHEN` on delivery split records that kitchen funded it (for reporting)
- No minimum wallet balance required — self-regulating per order

---

## 19. Decision Log

| 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 `WalletTransactionEntity.type` queries. |
| Full accounting vs simple ledger | 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 physical money sits. Enables clean reconciliation. |
| 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 mobile money 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*