Purpose
MHM Rentiva uses a financial engine called "Model B" β immutable and always auditable. This document explains how vendor earnings are calculated and how payout processes are managed atomically.
π³ Financial Engine & Payout Cycle
The system calculates vendor balances using an append-only ledger rather than a dynamic balance field.
ποΈ 1. Model B Ledger Structureβ
The Ledger is the single source of truth for all financial data in the system.
Immutability Principleβ
- Update/Delete Prohibited: No ledger row can be updated or deleted.
- Correction Entries: A faulty transaction can only be corrected by a reversal entry.
- SaaS Isolation: Each entry is associated with a
tenant_id.
Entry Typesβ
| Type | Direction | Description |
|---|---|---|
commission_credit | + | Earnings from a completed booking. |
payout_debit | β | Balance reduction from an approved payout request. |
refund | β | Refund from a cancelled booking. |
βοΈ 2. Atomic Payout Process (AtomicPayoutService)β
Payout approvals occur within a database-level Transaction. This ensures the entire process is rolled back if any step fails.
Process Steps:β
- Pre-flight Check: Balance and status verification (outside transaction).
- START TRANSACTION: DB lock initiated.
- Concurrent Guard:
post_statusis re-read from the DB (race condition prevention). - Ledger Write:
payout_pending_debitentry is created. - CPT Update: Payout post is set to
publishstatus. - COMMIT: All operations are persisted if successful.
π‘οΈ 3. Double-Spending Prevention Layersβ
Four layers of protection exist against double-spending risk:
| Layer | Mechanism | Level |
|---|---|---|
| L1: Application | vendor_has_pending_payout() check. | PHP / AJAX |
| L2: Balance | Ledger::get_balance() >= amount validation. | Domain Logic |
| L3: Transaction | InnoDB Row Lock and concurrent status guard. | Database |
| L4: Idempotency | Unique UUID in payout_{id} format. | DB Unique Key |
βοΈ 4. Technical API Referenceβ
Balance Queryβ
// Returns the sum of entries with 'cleared' and 'reserved' statuses only.
$balance = Ledger::get_balance($vendor_id);
Creating a Payout Requestβ
// PayoutService::request_payout()
// 1. Minimum limit (mhm_min_payout_amount) is checked.
// 2. Presence of a pending request is verified.
// 3. mhm_payout post is created.
π§ 5. Notifications & Hooksβ
| Hook | Trigger | Recipient |
|---|---|---|
mhm_rentiva_payout_approved | When payout is atomically approved. | Vendor |
mhm_rentiva_payout_rejected | When payout is rejected by the admin. | Vendor |
Section Summaryβ
- The Ledger table uses an
APPEND-ONLYarchitecture. AtomicPayoutServiceguarantees database consistency via transactions.- Double-spending prevention (Idempotency) is enforced at the UUID level.
Changelogβ
| Date | Version | Note |
|---|---|---|
| 23.04.2026 | 4.27.2 | English translation added. |
| 19.03.2026 | 4.21.2 | Model B Engine, Atomic Transactions, and SaaS isolation details added. |