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 Wallet Laravel Package

depsimon/laravel-wallet

A flexible wallet package for Laravel that lets Eloquent models hold balances and track transactions. Add credits and debits safely, manage multiple wallets and currencies, and keep an auditable ledger through a simple, Laravel-friendly API.

Deep Wiki
Context7

Getting Started

Install and publish

composer require depsimon/laravel-wallet
php artisan vendor:publish --provider="Depsimon\LaravelWallet\LaravelWalletServiceProvider"
php artisan migrate

Publishing gives you the wallet/transaction migrations and (if present) a config file. Read the migrations before running them — the column types for balance and amount decide whether you can trust the arithmetic.

Wire the trait into your user model

use Depsimon\LaravelWallet\Traits\HasWallet;

class User extends Authenticatable
{
    use HasWallet;
}

Where to look first

  1. The published migrations — table names, decimal precision, foreign keys.
  2. The Wallet model (Depsimon\LaravelWallet\Models\Wallet) — this is the actual API surface (deposit, withdraw, balance, transactions). The README is thin; the model is the documentation.
  3. The HasWallet trait — confirms whether the relation is hasOne (one wallet per user) or polymorphic.
  4. config/wallet.php if published — model bindings and table names you may need to override.

First use case

Store credit, refunds, and promotional balances for a single-currency app:

$wallet = $user->wallet;          // may be null — see Gotchas
$wallet->deposit(2500);           // credit
$wallet->withdraw(1000);          // debit
$wallet->balance;                 // current balance
$wallet->transactions()->latest()->get();

Sanity-check the exact method names and signatures against the source before writing anything else — the package is archived and README examples have drifted from the code.

Implementation Patterns

Wrap every mutation in a service class. Do not call deposit()/withdraw() from controllers. A thin WalletService gives you one place to add locking, idempotency, and logging, and makes the eventual migration to another package a one-file change.

class WalletService
{
    public function credit(User $user, int $minorUnits, string $reference): Transaction
    {
        return DB::transaction(function () use ($user, $minorUnits, $reference) {
            $wallet = Wallet::where('user_id', $user->id)->lockForUpdate()->firstOrFail();

            // Idempotency guard — add a unique `reference` column to transactions.
            if ($existing = $wallet->transactions()->where('reference', $reference)->first()) {
                return $existing;
            }

            return $wallet->deposit($minorUnits, ['reference' => $reference]);
        });
    }
}

Always take a row lock. The package does a read-modify-write on balance. Under concurrency that is a lost update, not a transaction. DB::transaction + lockForUpdate() on the wallet row is mandatory, not optional.

Treat amounts as integer minor units. Store cents. If the migration uses decimal(8,2), cast carefully and never pass floats through deposit() — float drift will poison balances after a few hundred operations.

Use it for credits first, debits second. Deposits (refunds, top-ups, admin grants) are low-risk. Withdrawals introduce the double-spend surface. Ship credits, watch the ledger for a week, then enable debits with locking in place.

Expose history, not balances, to support. The transaction rows are the only audit trail. Add a read-only admin view filtered by user_id and date; do not give admins a "set balance" button — express adjustments as a deposit/withdraw with a reason, so the ledger stays complete.

Testing pattern. RefreshDatabase, a UserFactory that creates the wallet in an afterCreating hook, and a test that fires 50 concurrent-style increments in a loop to confirm the final balance matches the sum. If that test fails, your locking is wrong, not the package.

Integration tip for payment webhooks. Never mutate the wallet inside the webhook handler directly. Enqueue a job keyed by the gateway's transaction ID (unique index on your reference column) and let the job do the credit. Webhooks retry; without idempotency you will double-credit.

Gotchas and Tips

It is archived (last release March 2020). Expect Composer constraints to fight with Laravel 9/10/11 and PHP 8.x. Options, in order of preference: (a) fork it, relax composer.json, and pin to your own VCS repo; (b) migrate to bavix/laravel-wallet or overtrue/laravel-wallet, both maintained and both with locking and multi-wallet support; (c) write a 150-line double-entry ledger yourself. For anything handling real money, (b) or (c). For internal credits on a legacy app, (a) is fine.

$user->wallet can be null. The trait gives you the relation but not the row. Create the wallet in a created model event or a firstOrCreate accessor, otherwise the first deposit throws a confusing "call to member function on null" in production.

No idempotency, no unique references, no events. There is nothing stopping the same logical operation from being applied twice, and nothing to hook into for notifications. Add a reference column with a unique index, and either use model observers on Transaction or dispatch your own event from the service layer.

Negative balances may be allowed. Read withdraw() in the source: if it doesn't guard balance >= amount, a race or a missing check will silently overdraw. Enforce the check inside your locked transaction, and consider a DB CHECK (balance >= 0) constraint.

Ledger rows are mutable. Eloquent gives anyone with the model the ability to update() or delete() a transaction. Guard with $guarded = ['*'] on a subclassed transaction model, or restrict grants at the DB level. An append-only ledger that can be edited is not an audit trail.

Precision is your problem, not the package's. decimal(8,2) caps you at ~1,000,000 units; float/double is unacceptable. Verify the published migration and change it before going live — altering a populated balance column later is painful.

Mass assignment and validation. Never accept balance or amount from a request body into the wallet. Validate amounts server-side: positive integers, upper bound per transaction, and a per-day cap for withdrawals.

Debugging. In local, wrap operations in DB::listen and log wallet_id, amount, balance_before, balance_after. When a balance looks wrong, reconstruct it with SUM(amount) over transactions and compare — if they diverge, something wrote to balance outside the package.

Extension points are limited. There's likely a config entry for model/table names; if not, subclass Wallet and override the trait's relation method in your user model. Don't edit files in vendor/ — your changes vanish on the next composer install.

Rollback order. The migrations likely create wallets before transactions with a cascade delete. Deleting a user silently erases their financial history. Switch to restrictOnDelete or soft deletes on the wallet before you ship.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core