FinCore: Wallet & Double-Entry Ledger API

A versioned REST API for digital wallets where every credit, debit, transfer and reversal produces balanced ledger entries and a full audit trail. Built to show how money-handling code should behave when requests are retried, concurrent or malicious.

Open sourceLaravel 12
Illustration of the FinCore project
PHP 8.3
Laravel, Sanctum auth
Double-entry
immutable ledger
Idempotent
safe retries on every mutation
Tested
concurrency, reversals, reconciliation

The challenge

Money systems rarely fail loudly. They fail in quiet ways: a mobile client retries a request and the customer is charged twice, two transfers lock each other's wallets and deadlock, a cached balance drifts away from the real history, or someone edits a past record to hide a mistake.

Each of those needs a deliberate answer in the design. Hoping the framework handles it is how balances end up wrong in production, and wrong money is the kind of bug customers notice first.

How I approached it

  1. 01

    Every mutation is balanced or it doesn't happen

    Credits, debits, transfers and reversals all run inside a database transaction and must produce ledger entries where the sum of debits equals the sum of credits, using 4-decimal bcmath arithmetic instead of floats. If the entries don't balance, the whole transaction rolls back.

  2. 02

    Idempotency keys on every money-moving endpoint

    Credit, debit and transfer requests require an Idempotency-Key header. A repeated request with the same key replays the stored response instead of moving money twice, and the same key with a different payload is rejected with a 409 conflict.

  3. 03

    Deterministic lock ordering for transfers

    A transfer needs to lock two wallets. If two opposite transfers lock them in different orders they can deadlock, so wallets are always locked in a deterministic order. Row-level locking also stops concurrent debits from overspending a balance.

  4. 04

    History is never edited

    The ledger is append-only. A reversal is a new compensating transaction that points back to the original, so the audit trail always shows what happened and what corrected it.

  5. 05

    Reconciliation as a safety net

    A scheduled reconciliation command compares each wallet's cached balance against the ledger and reports any drift, so a bug can't silently corrupt balances for weeks.

What I built

  • Designed the schema: ULID wallets, ledger accounts, immutable transactions and ledger entries, idempotency keys and audit logs.
  • Built domain services (Wallet, Transfer, Ledger, Reconciliation) with typed exceptions for each failure mode.
  • Added token authentication with Laravel Sanctum, role-based admin endpoints and rate-limited financial mutations.
  • Wrote feature tests covering concurrent debits, idempotency, reversals, transfers, reconciliation and auth.
  • Documented the architecture and design decisions in docs/FINCORE_WALKTHROUGH.md in the repository.

What this project demonstrates

  • How to design money flows that stay correct under retries and concurrency
  • Writing tests for the failure modes, not just the happy path
  • Documenting decisions so another engineer can take the code over

Related case studies

API Integration / Payments / SecurityProduction · Private

Payment Gateway Integrations

Secure integration of third-party payment gateways and verification modules into custom PHP and Laravel applications and e-commerce platforms, built so a payment is never lost, double-counted or trusted without verification.

Laravel / PHP / MySQL / REST APIProduction · Private

PractiCal Backend Platform

A production backend serving meal-subscription operations: user profiles, multi-tier subscription plans, custom meal schedules, digital wallets, order histories and automated kitchen and delivery dispatch lists.

Have a project like this in mind?

Tell me what you're building. I'll reply by email with questions or a suggested approach.