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 Overview & Core Principles Platform Context Two Financial Worlds Treasury Design Wallet System Chart of Accounts Money Splitting Escrow System Payment Channels Full Money Journey — All Scenarios Subscription Payments Settlements & Payouts Refunds & Cancellations Admin Withdrawal PSP Reconciliation Reporting Safety Rules & Integrity 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 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 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 (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 Only SUPER_ADMIN can request Requires second SUPER_ADMIN approval 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 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