Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Balance Laravel Package

mreduar/laravel-balance

mreduar/laravel-balance adds balance and transaction tracking to Laravel Eloquent models. It provides a simple API for crediting and debiting accounts, recording balance history, and managing wallet-style ledgers for users, teams, or other models. Useful for credits, rewards, payments, and account b...

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require mreduar/laravel-balance
php artisan vendor:publish --provider="Mreduar\Balance\BalanceServiceProvider"
php artisan migrate

The publish step is where you get the balance-movement/transaction migration and any config file. If the tag names aren't obvious, run php artisan vendor:publish with no arguments and pick the package interactively — that's faster than guessing tags on a package with sparse docs.

Add the trait to the model that owns the balance

use Mreduar\Balance\Traits\HasBalance; // verify exact namespace in vendor/

class Wallet extends Model
{
    use HasBalance;

    protected $fillable = ['user_id', 'balance'];
}

Where to look first, in this order

  1. vendor/mreduar/laravel-balance/src/ — read the trait and the transaction model. It's a small package; the trait is the entire public API surface and reading it takes five minutes.
  2. The published migration — this tells you the column name, its type (integer vs decimal), and whether there's a polymorphic balanceable_id/balanceable_type pair.
  3. The config file, if one is published — check for table names and any default currency/precision settings.

Do this before writing application code. The package vocabulary from the description (increase, decrease, reset, set, "positive balance", "no balance") maps to method names, but exact casing and signatures vary — confirm them in the trait rather than trusting a blog post.

First use case: a wallet credit

$wallet = Wallet::find($id);
$wallet->increaseBalance(2500);   // e.g. 2500 minor units
$wallet->refresh()->balance;      // 2500

That's the whole happy path. Everything else is guarding it.

Implementation Patterns

Wrap every mutation in a DB transaction with a row lock. Balance math is a read-modify-write; without a lock two concurrent debits can both read the same starting balance.

DB::transaction(function () use ($walletId, $amount) {
    $wallet = Wallet::whereKey($walletId)->lockForUpdate()->firstOrFail();

    if ($wallet->balance < $amount) {
        throw new InsufficientFundsException();
    }

    $wallet->decreaseBalance($amount);
});

Put this behind a small domain service (WalletService::debit(), ::credit()) rather than calling the trait from ten controllers. When you eventually outgrow the package, the swap is contained to one class.

Treat the package's transaction history as the ledger, and your own reference as the idempotency key. The package records movements, but it almost certainly doesn't dedupe them. Add a reference or external_id column (nullable, unique per wallet) to the transactions table via your own migration, or keep an idempotency_keys table alongside. Payment webhooks will fire twice.

Model the ledger as append-only for anything financial. If the package exposes a "set balance" / "reset balance" method, reserve those for admin corrections and onboarding, never for normal flow — a set erases the audit story that made you pick this package. Route normal changes through increase/decrease so every change is a row.

Use the balance checks for UI, not for authorization.

$wallet->hasPositiveBalance();  // show "Pay" button
$wallet->hasNoBalance();        // show "Top up" prompt

The check and the subsequent debit are two separate reads. The authoritative check is the lockForUpdate + compare inside the transaction above.

Expose history through a scoped relation, not raw model queries in views:

class Wallet extends Model
{
    use HasBalance;

    public function recentMovements(int $limit = 20)
    {
        return $this->balanceTransactions()->latest()->limit($limit);
    }
}

Verify the relationship method name in the trait — it may be transactions(), balanceTransactions(), or a morph relation. Alias it in your model either way so the rest of the codebase doesn't depend on the package's naming.

Seed predictably in tests. RefreshDatabase plus the package migrations gets you a clean ledger per test. Build a factory state for the common setups:

Wallet::factory()->withBalance(10_000)->create();
// state calls increaseBalance() after create

Fire domain events around mutations, not inside the trait. BalanceCredited / BalanceDebited events dispatched from your service keep listeners working if the package is replaced, and let you trigger receipts, notifications, and analytics without patching vendor code.

Gotchas and Tips

Confirm the unit before you trust the number. The column is probably an integer of minor units (cents) or a decimal(15,2). Do the conversion once in your service layer and never pass a float. Floating-point money is the one bug that survives every review.

setBalance() vs. resetBalance() are different operations. reset almost certainly means "set to zero"; set means "set to a given value." Both are destructive to the audit narrative — restrict them with an admin gate or an artisan command, and log who called them yourself.

Negative balances may be permitted. Don't assume the package enforces a floor. Test it explicitly: debit more than the balance and see what happens. If it allows it, that's your guard to write, and write it as a test.

Stale model instances are the most common day-one confusion. After increaseBalance(), the in-memory $wallet->balance may or may not be synced. Call $wallet->refresh() or re-fetch before rendering. Don't assume $wallet->balance is current.

Check the Composer constraint against your Laravel version. Last release is from late 2023 and the changelog's only note is "support for latest Laravel versions" — that phrasing is a maintenance signal, not a guarantee of a wide support matrix. Before adopting on Laravel 11/12, run composer why-not mreduar/laravel-balance and check the package's composer.json for the illuminate/* and PHP constraints. If it's tight, either pin your framework accordingly or budget for a fork; the trait is small enough that a fork is cheap.

Budget for growth in the movements table. One row per credit/debit on a wallet with heavy traffic becomes a large table quickly. Add an index on (balanceable_type, balanceable_id, created_at) in your own migration, and decide early whether you archive old rows — you can prune anything older than N months into cold storage as long as your accounting export exists.

The package is community-maintained with a low star count. That's fine for a small, self-contained trait — but read the source rather than relying on docs, vendor nothing you can't replace, and keep the package behind your own service class so the swap is a day's work. Add a re-evaluation date to your dependency register.

Extension point worth knowing: because it's a trait, you can override any of its public methods in your model via use HasBalance { increaseBalance as protected baseIncrease; } or by defining your own method that calls the trait's. Use this to add logging, guard conditions, or a custom transaction model — it beats editing vendor/.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core