v2

Ledger Phase 3 Guide

Purpose

This wiki captures the implemented Phase 3 Ledger foundation so future work in Payments, Access, Monetization, Promotions, and Admin Back Office can build on the actual money-moving primitives instead of reverse-engineering them from code.

What Phase 3 Includes

  • Immutable ledger_accounts, ledger_transactions, and ledger_entries storage
  • ILedgerService::post() as the sole application-level money mover
  • User wallet projection rows in wallets
  • Nightly-style reconciliation via ReconcileWalletsJob
  • wallet_drift_incidents for reconciliation mismatches
  • approval_requests for dual-approval finance workflows
  • Invariant coverage for zero-sum rules and random no-drift sequences

Implemented Files

  • Models: LedgerAccount, LedgerTransaction, LedgerEntry, Wallet, WalletDriftIncident, ApprovalRequest
  • Actions: RequestApprovalAction, ApproveRequestAction
  • Job: ReconcileWalletsJob
  • Contract: ILedgerService
  • Service: LedgerService
  • Tests:
    • tests/Unit/Ledger
    • tests/Feature/Ledger
    • tests/Invariant/Ledger

Posting Rules Covered In Tests

  • top_up
  • post_purchase
  • tier_subscription_payment
  • earnings_release
  • withdrawal
  • post_purchase_refund
  • referral_commission
  • welcome_credit
  • premium_subscription_payment

The invariant suite verifies that each rule balances to zero and that a 1000-transaction random sequence can reconcile without drift.

Wallet Projection Rules

  • wallets.available_balance_minor_units mirrors the sum of the user user_wallet account
  • wallets.pending_balance_minor_units mirrors the sum of the user user_pending_earnings account
  • lifetime_earnings_minor_units is recomputed from positive credits into user_pending_earnings
  • lifetime_spend_minor_units is recomputed from wallet debits caused by purchase/subscription purposes
  • lifetime_withdrawn_minor_units is recomputed from wallet debits caused by the withdrawal purpose

Reconciliation Flow

  1. Find all user-owned ledger accounts and wallet rows.
  2. Ensure a wallets projection row exists for each user.
  3. Recompute available and pending balances from immutable ledger entries.
  4. If a mismatch is detected, record WalletDriftIncident and log the delta.
  5. Repair the wallet projection row to the recomputed values.

Approval Workflow Rules

  • Use RequestApprovalAction to open a pending ApprovalRequest
  • Use ApproveRequestAction to finalize approval
  • The requester cannot be the approver
  • Expired requests cannot be approved
  • Resolved requests cannot be approved again
  • Scope::requiresDualApproval() remains the platform-level signal for when this workflow must be used

Current HTTP Surface

There are currently no routed Ledger HTTP controllers in routes/api.php.

That is expected at this phase:

  • Ledger is implemented as an internal domain service used by downstream domains
  • Public/user-facing money movement endpoints belong to later Payments, Access, and Admin phases
  • The http/Ledger.http file intentionally documents the current non-routed status instead of publishing fake requests

Follow-On Phases That Depend On This Work

  • Phase 4 Payments
  • Phase 6 Access & Entitlements
  • Phase 7 Monetization
  • Phase 11 Promotions
  • Phase 14 Admin Back Office

Notes For Future Changes

  • Do not add direct wallet balance writes anywhere outside Ledger projection repair paths
  • New money flows must define a posting rule before implementation
  • If a future phase exposes Ledger HTTP endpoints, update both http/Ledger.http and this wiki