Cashflow: Double-Entry Balance Ledger for Laravel

Financial transactions are rarely simple. A single payment can involve gateway fees, taxes, refunds, and chargebacks — each with its own rules and dependencies. Cashflow gives you a structured, type-safe way to manage all of this in Laravel.

The Problem

Most apps start with a simple transactions table. But as the business grows, you need:

  • Multiple line items per transaction (payment, fee, tax, refund)
  • Derived amounts (gateway fee is 2.9% of the payment)
  • Dependency rules (can't refund without a payment)
  • Uniqueness constraints (only one gateway fee per order)
  • Atomic batches (all items saved together or not at all)

Cashflow solves all of these with a clean, PHP-native approach.

Define Your Balance Items

Instead of storing raw numbers in a generic table, you define typed PHP classes for each balance component:

use Laratusk\Cashflow\Contracts\BalanceItem;
use Laratusk\Cashflow\Attributes\Unique;
use Laratusk\Cashflow\Attributes\Rule;
use Laratusk\Cashflow\Enums\Direction;

#[Unique]
final class GatewayFee extends BalanceItem
{
    public function __construct(
        #[Rule('nullable', 'integer', 'min:1')]
        private readonly ?int $amount = null,

        #[Rule('nullable', 'numeric', 'gt:0', 'max:100')]
        private readonly ?float $percentage = null,
    ) {}

    public function amount(): int
    {
        if ($this->amount !== null) {
            return $this->amount;
        }

        return (int) ceil(
            $this->balance()->amountOf(Payment::class) * $this->percentage / 100
        );
    }

    public function direction(): Direction
    {
        return Direction::Debit;
    }
}

Fluent Balance API

Building a transaction is straightforward:

use Laratusk\Cashflow\Balance;

$balance = Balance::for($account)
    ->reference($order)
    ->currency('USD');

$balance->insert(new Payment(amount: 10000));
$balance->insert(new GatewayFee(percentage: 2.9));
$balance->insert(new SalesTax(taxRate: 8.0));
$balance->save();

All items are saved in a single database transaction with a shared batch_id UUID for easy auditing.

Querying Balances

Retrieve and inspect balances with a clean API:

$balance = Balance::for($account)->reference($order)->get();

$balance->amountOf(Payment::class);    // 10000
$balance->amountOf(GatewayFee::class); // 290
$balance->batchId();                   // "9b1deb4d-..."

PHP Attributes for Business Rules

Cashflow uses PHP attributes to enforce your business logic at the framework level:

  • #[Unique] — Only one instance per reference (no duplicate fees)
  • #[Requires(Payment::class)] — Can't add a refund without a payment
  • #[Rule(...)] — Laravel validation on constructor parameters

Built For Production

Cashflow was designed for real-world payment systems — SaaS platforms, marketplaces, and fintech apps where getting the numbers right matters. Every insert is validated, every batch is atomic, and every dependency is enforced before data hits the database.

Check out the full documentation on GitHub.

View on GitHubStar the repo, explore the source code, and get started.
Go to Repository
Share this post
Back to Blog