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

stephenjude/laravel-wallet

A Laravel wallet package for adding balances and transactions to any model. Create multiple wallets, deposit, withdraw, transfer funds, and track transaction history with a clean, simple API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets:

composer require stephenjude/laravel-wallet

php artisan vendor:publish --tag=wallet-config
php artisan vendor:publish --tag=wallet-migrations
php artisan migrate

Add the HasWallet trait to any model that should own a wallet — usually User, but it can also be Team, Vendor, etc.:

use Stephenjude\Wallet\Traits\HasWallet;

class User extends Authenticatable
{
    use HasWallet;
}

Look first at config/wallet.php and the published migration. They tell you the table names, column types, and whether balances are stored as integers or decimals. That detail determines how every amount in your application must be formatted and validated.

The first practical use case is a simple account-credit flow:

$user->deposit(5000, 'Wallet top-up');
$user->withdraw(1200, 'Order #123');
$user->transfer($recipient, 2500, 'Refund');

$user->wallet->balance;

Treat this as the baseline, then wrap it in your own application service rather than calling the trait directly from controllers.

Implementation Patterns

Create a thin wallet service. Keep controllers and jobs away from raw trait calls. A WalletService or action class gives you one place to enforce validation, idempotency, logging, and transaction boundaries:

class WalletService
{
    public function credit(User $user, int $amount, string $reference): void
    {
        DB::transaction(function () use ($user, $amount, $reference) {
            $user->deposit($amount, 'Top-up', ['reference' => $reference]);
        });
    }
}

Wrap money movement in database transactions. Deposits and withdrawals may update both the wallet and a transaction record. Wrap multi-step operations, especially transfers, in DB::transaction() so a failure cannot leave a debit without a matching credit.

Use the meta payload for idempotency and audit data. Store your own reference keys — payment gateway IDs, order numbers, payroll run IDs — and check for an existing transaction before crediting:

$alreadyCredited = $user->wallet->transactions()
    ->where('meta->reference', $reference)
    ->exists();

This is the simplest guard against duplicate webhooks and retried jobs.

Model transfers explicitly. For peer-to-peer payments, verify the sender has sufficient balance, then perform the debit and credit inside one transaction. If you are transferring between two wallets, lock both rows in a consistent order — for example by wallet ID — to reduce deadlock risk:

DB::transaction(function () use ($from, $to, $amount) {
    $wallets = Wallet::whereIn('id', [$from->wallet->id, $to->wallet->id])
        ->orderBy('id')
        ->lockForUpdate()
        ->get();

    $from->withdraw($amount, 'Transfer out', ['to' => $to->id]);
    $to->deposit($amount, 'Transfer in', ['from' => $from->id]);
});

Keep the transaction ledger append-only. The wallet balance is the current state; transactions are history. Never update or delete ledger rows from application code. If a correction is needed, post a reversing transaction with a clear description and meta reference.

Expose wallet data through API resources. Return the balance and recent transactions in a stable shape. Format amounts once, at the boundary, according to the package’s storage convention:

return [
    'balance' => $user->wallet->balance,
    'currency' => config('wallet.currency'),
    'transactions' => WalletTransactionResource::collection(
        $user->wallet->transactions()->latest()->paginate(20)
    ),
];

Use policies and form requests for withdrawal endpoints. A wallet endpoint is a high-risk surface. Authorize that the authenticated user owns the wallet, validate the amount as an integer within allowed bounds, and reject negative or zero values before the package sees them.

Reconcile regularly. Build a scheduled command that compares your wallet balance against the sum of transaction records, or against an external provider’s ledger. A simple invariant check catches bugs early:

$expected = $wallet->transactions()->sum('amount');

Test with RefreshDatabase and factories. Create a user, deposit, assert the wallet balance, and assert the transaction row was written. Also test failure paths: insufficient funds, duplicate reference, and transfer rollback.

Gotchas and Tips

Confirm the amount unit before writing any code. Many wallet packages store minor units (cents/kobo) as integers; others use decimals. Passing 10.50 when the package expects 1050 — or the reverse — is the most common and most expensive mistake. Check the migration column type and the config.

Do not assume the wallet exists. Depending on the package version, the wallet may be null until the first deposit or may be created lazily. Always handle the null case or ensure wallet creation in your registration flow. A firstOrCreate on the wallet relationship is a safe habit.

Check whether withdraw enforces sufficient funds. Do not rely on UI checks alone. Assert the balance inside the same transaction that performs the withdrawal, and reject overdrafts explicitly unless you intentionally support negative balances.

Transfers are not automatically atomic in every package. Wrap debit and credit in one transaction and add your own rollback test. If the package exposes a transfer method, verify in a test that a failure midway does not leave one side applied.

config:cache can surprise you. If you publish and edit config/wallet.php, run php artisan config:clear locally and remember that production config caches hide changes until recached.

Morph maps matter. If the wallet uses a morph relationship, enabling Relation::enforceMorphMap() after wallets already exist can break lookups. Add the morph map before creating production data, or write a migration for existing wallet rows.

Be careful with custom table names. If you rename the published tables or columns, update the config accordingly and re-check any raw queries or reporting jobs that reference those names directly.

Use decimal-safe formatting only at the edges. Keep arithmetic in the package’s native unit, and convert to a display string in API resources or Blade views. Mixing formatted strings back into service calls causes subtle bugs.

Extension points are usually config and model swaps. If you need extra wallet fields, a custom transaction model, or per-wallet currency, check whether the config allows overriding the wallet and transaction models. Otherwise, subclass them and point the config at your classes rather than editing vendor files.

Log the context around failures. When a deposit or transfer throws, log the user ID, amount, reference, and the exception. Wallet bugs are difficult to reconstruct later without the original reference and amount.

Add a database index on your reference lookup. If you filter transactions by meta->reference frequently, consider a dedicated indexed column or a generated column, depending on your database. JSON path queries can become slow at volume.

Keep currency handling explicit. If the package assumes a single configured currency, do not silently store multiple currencies in one wallet. Either create one wallet per currency or add a currency column and enforce it in your service layer.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin