Skip to content

Payments 6/7: Micropayment Credit Ledger & Multi-Party Revenue Distribution #1575

Description

@yusuftomilola

Problem / Context

Not every coworking charge is booking-sized. Per-minute resource usage, printing, and meeting-room overage are too small and too frequent to settle individually on-chain — fees and latency would dominate the payment. Separately, some payments need to split across multiple recipients (platform fee, hub operator payout, referral reward) instead of going to one account. Both problems share the same solution: an internal ledger that batches value movement instead of transacting per event.

This is issue 6 of 7 in the payment track.

Objective

Build an internal double-entry credit ledger for high-frequency, low-value charges with periodic batch settlement to the on-chain rail, plus a configurable multi-party split engine usable both by ledger settlement and by ordinary #1570 payments.

Technical approach

  • Double-entry ledger: ledger_accounts (one per user, plus system accounts for platform fee/treasury) and append-only ledger_entries (debit/credit pairs that always balance to zero per transaction) — the correct primitive for many small movements with occasional settlement, and a natural audit trail for free.
  • Spend path: an internal POST /credits/charge (called by resource-usage features, not directly by end-users) debits a user's ledger balance synchronously and cheaply — no blockchain call in the hot path — with overdraft protection (reject on insufficient balance, or allow a documented small overdraft ceiling if graceful degradation is wanted).
  • Top-up path: crediting the ledger from a completed Payments 1/7: Payment Domain Model, Initiation Flow & Idempotent Transaction Lifecycle #1570 payment (fiat) or Payments 5/7: On-Chain Escrow Rail — Soroban Integration & Chain-State Reconciliation #1574 on-chain payment — one payment can fund many future micro-charges.
  • Batch settlement job: periodically nets each account's movements since the last settlement and, only where balances need to move off-platform (e.g. operator payouts), executes a single on-chain transfer via Payments 5/7: On-Chain Escrow Rail — Soroban Integration & Chain-State Reconciliation #1574's rail instead of one transfer per micro-event.
  • Split engine: a RevenueSplitConfig (basis-point recipients) attachable to a Payment or a settlement batch, computed at settlement time and either written as ledger entries (internal) or executed as a multi-recipient on-chain transfer (external payouts). Validates that basis points sum to 100% and uses an explicit remainder-allocation rule (e.g. largest-remainder method) so rounding never loses or duplicates value.

Detailed scope

  • Ledger schema and service (debit/credit, balance query, overdraft policy).
  • Charge endpoint plus at least one real call site wired to it (the nearest existing metered feature).
  • Batch settlement job with admin visibility into pending/settled batches.
  • Split-config entity and calculation service, with rounding/remainder tests.
  • Integration with Payments 5/7: On-Chain Escrow Rail — Soroban Integration & Chain-State Reconciliation #1574 for the off-platform payout leg.

Important edge cases and failure scenarios

  • High-concurrency charges against the same account — ledger writes must be safe under concurrency (row-level locking or serializable transactions), never losing or double-applying an entry.
  • Overdraft race: two concurrent charges each individually valid against the last-known balance, but not both together — must not both succeed.
  • The settlement batch job crashes mid-run — must be resumable without double-settling entries already paid out (an idempotent per-entry settlement marker).
  • Split basis points don't sum to 10000 (config error) — rejected at configuration time, not discovered at settlement.
  • Rounding leaves a remainder across split recipients — deterministically allocated per the documented rule, never silently dropped or duplicated.
  • The on-chain payout leg of settlement fails (any Payments 5/7: On-Chain Escrow Rail — Soroban Integration & Chain-State Reconciliation #1574 failure mode) — the ledger must not already have marked those entries settled; settlement and payout confirmation are transactional/compensating, never assume-success.

Dependencies

Depends on #1570 (payment funding a top-up), #1574 (on-chain settlement leg and its failure/retry patterns).

Acceptance criteria

  • Concurrency test: N simultaneous charges against a fixed starting balance never overdraw past the configured ceiling.
  • Split-calculation test: basis-point configs across varied amounts always sum exactly to the original amount, no lost or duplicated remainder.
  • The batch settlement job is safe to re-run mid-failure without double-paying any recipient (idempotency test).
  • An admin can view a settlement batch's full breakdown: entries in, recipients out, on-chain transaction references.

Definition of done

Ledger and split engine deployed; at least one real metered feature charges through it in staging; the settlement job is scheduled and observable; overdraft and rounding policies are documented.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions