Download the PHP package ssbityukov/laravel-balance-engine without Composer

On this page you can find all versions of the php package ssbityukov/laravel-balance-engine. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-balance-engine

Laravel Balance Engine

A double-entry ledger for Laravel that does not lose money under load.

The problem

Almost every Laravel app starts here:

With balance = 100 and two simultaneous requests:

Request A Request B
reads 100 100
writes 200 200

Result: 200 instead of 300. Money is gone, and there is no trace of it — no record that a second request ever happened, nothing to reconcile against, no way to find out later.

The solution

Every one of those writes at least two ledger entries that sum to zero, takes row locks in a deterministic order, and leaves a record that cannot be edited or deleted.

Prove it

And when something is wrong, it says so and exits 1:

Seven invariants are checked: the global sum is zero, every cached balance matches its entries, no reservation is drawn past zero, every capture and release points at a reserve, nothing but a reservation chain has touched a hold account, no hold account is negative, and every owner type still resolves.

How this compares

bavix/laravel-wallet abivia/ledger Balance Engine
deposit / withdraw / transfer as an API yes no, journal-level API yes
holds with partial capture holds, no partial no yes, via a hold account inside the ledger
strict double entry, SUM = 0 no yes yes
invariant check command, exit 1 no no balance:verify
idempotency keys with fingerprint no no yes
immutable records, corrections by reversal no partial yes
race safety proven by tests no no forked-process tests on MySQL and PostgreSQL
barrier to entry low high (chart of accounts) low

The niche is between the two: the double-entry strictness of abivia/ledger with the developer experience of bavix/laravel-wallet. No chart of accounts, an application-level API rather than a journal one, and an invariant you can verify in CI.

Requirements

Installation

balance:install detects whether your owner models use integer, UUID, ULID or string keys and writes that into the published config, so the polymorphic columns match your application.

Register a morph map

This is not optional. The ledger stores owner types forever and its records are immutable, so a raw App\Models\User string becomes unfixable the day you rename or move the class:

balance:verify fails on any owner type it cannot resolve, so a forgotten entry here shows up as a failing check rather than as a mystery years later.

Usage

Add the trait to anything that holds money:

Every member is prefixed with balance, because most models already have a balance attribute of their own.

Reading balances

Amounts are integers in minor units. There is no float anywhere in this package.

Deposit and withdraw

Withdrawing more than is available throws InsufficientFunds, which carries the account, what was requested and what was there.

Transfer

Locks are always taken in ascending account id order, which is what stops two transfers running in opposite directions between the same pair from deadlocking.

Reservations

Money is moved onto a hold account, not marked with a flag:

Reserving and capturing usually happen in different requests. Keep the uuid and load the reservation back when you need it:

Hold accounts are not ordinary accounts. Money reaches one only through reserve() and leaves it only through capture() and release(); depositing, withdrawing or transferring against one throws HoldAccountNotDirectlyUsable. Otherwise balanceReserved() would report money that no reservation was holding.

The destination of a capture is mandatory. A default recipient would let money drift onto a system account unnoticed.

Nothing about a reservation is stored: it is the reserve transaction, captures and releases are its children, and every figure above is derived from the ledger. Expired reservations are returned by a scheduled command:

Reversal

Records are immutable. A mistake is corrected by writing its mirror image, never by editing or deleting:

A reversal is an ordinary ledger operation and obeys the ordinary rules, so reversing a deposit whose money has already been spent fails with InsufficientFunds rather than pushing an account negative.

Freezing

Freezing blocks debits only. Credits keep landing, which is the correct semantics for fraud and AML work: stop payouts without losing money already in flight.

Idempotency

A repeated call returns the stored transaction instead of moving money again. Reusing a key for a different operation throws IdempotencyKeyReused rather than silently replaying, because that would hide a bug in the caller.

Two independent mechanisms back this: a check before the transaction opens for the ordinary retry, and a unique index for two workers racing on the same key.

Named accounts and currencies

One owner can hold many accounts, separated by name and currency:

There is no cross-currency transfer. An exchange is two operations at a rate your application decides — see docs/recipes.md.

Currencies are validated against balance.currencies, so a typo throws UnsupportedCurrency instead of quietly opening an account in a currency that does not exist. Records are immutable, so that account would have been permanent.

Production notes

SQLite has no row-level locking. lockForUpdate() is a silent no-op there, so concurrent operations are not safe. Use MySQL or PostgreSQL in production; the package logs a warning if it finds itself on SQLite in a production environment.

Retries do not work inside your own transaction. Deadlock retries happen at the DB::transaction level. If you wrap a balance call in an outer transaction of your own, the inner one becomes a savepoint and the retry is lost. Call the package outside your transaction where you can.

Put the two commands to work:

balance:verify exits 1 on any discrepancy, so it works as a monitoring check. balance:rebuild can repair a drifted cached balance from the entries, but find out why it drifted first — an automatic repair hides the bug that caused it.

References use an integer morph. reference_type and reference_id are a standard nullableMorphs, so for UUID-keyed reference models put the identifier in meta instead.

Documentation

Every code block in this README and in docs/ is copied from a passing test in tests/Feature/DocumentationTest.php.

Testing

Changelog

See CHANGELOG.md.

License

MIT. See LICENSE.md.


All versions of laravel-balance-engine with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
illuminate/console Version ^13.0
illuminate/contracts Version ^13.0
illuminate/database Version ^13.0
illuminate/support Version ^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package ssbityukov/laravel-balance-engine contains the following files

Loading the files please wait ...