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

021/laravel-wallet

A Laravel package for adding wallet functionality to your app: manage multiple balances, credit and debit funds, record transactions, handle deposits, withdrawals and transfers, and track wallet history with an Eloquent-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install via Composer: composer require 021/laravel-wallet. Laravel auto-discovers the service provider (O21\LaravelWallet\LaravelWalletServiceProvider).
  2. Publish config and migrations (use the tags from the README — php artisan vendor:publish will list them), then run php artisan migrate. You get wallets and transactions tables by default; table names are configurable.
  3. Wire up your model. Add the HasWallet trait to User (add CanPay too if you want the payment façade). Both traits are in O21\LaravelWallet\Traits.
  4. Create a wallet — models can hold many wallets, addressed by slug:
$user->createWallet(['name' => 'Main', 'slug' => 'main']);
$wallet = $user->getWallet('main');
  1. Move money — deposit, withdraw, transfer, pay all return a Transaction model:
$wallet->deposit('150.00', ['reference' => 'stripe_123']);
$wallet->withdraw('20.00');
$wallet->balance;          // string decimal
$wallet->transactions;     // relation, newest last by default

Where to look first: the published wallet.php config (defaults for balance, confirmation, decimals, table names), then Wallet / Transaction models, then the HasWallet trait to see exactly which methods are on your user model.

First real use case: a user tops up (deposit) onto a main wallet, spends via withdraw/pay, and you render a paginated ledger from the transactions relation. That loop exercises 90% of the package.

Implementation Patterns

Multiple named wallets per model. Prefer slugs as a domain vocabulary (main, bonus, refund, cashback) over a single balance. Always null-check getWallet() — it returns null for unknown slugs:

$wallet = $user->getWallet('bonus') ?? $user->createWallet(['slug' => 'bonus']);

Ledger rendering. The transactions relation is query-builder-compatible, so paginate and filter by type directly instead of loading everything:

$wallet->transactions()->where('type', 'credit')->latest()->paginate(25);

Transfers between holders. Use the wallet-to-wallet transfer so both sides get a transaction row; don't compose withdraw + deposit yourself unless you need different amounts on each leg:

DB::transaction(function () use ($sender, $receiver, $amount) {
    $sender->getWallet('main')->transfer($receiver->getWallet('main'), $amount);
});

Payments via CanPay. $user->pay($wallet, $amount, $meta) is the idiomatic checkout call; keep payment-specific meta (order id, gateway ref) on the transaction rather than a parallel table.

Attach meta for reconciliation. deposit/withdraw/transfer accept a meta array as the trailing argument. Store external references there and use them as your idempotency key: look up an existing transaction by reference before creating a new one, so retried webhooks don't double-credit.

Wrap multi-step flows in DB::transaction. Individual operations are atomic on the wallet row, but "debit A, credit B, write an order" is not — compose it yourself.

Audit via events/observers. Rather than polling the ledger, register listeners for the package's transaction events (or an observer on Transaction) to push to your audit log, notify users, or trigger downstream jobs.

Extend by config, not by editing vendor code. The config lets you swap in your own Wallet / Transaction models — do that when you need extra columns, relations, or casts. Custom transaction handlers are the extension point for new domain operation types (refund, cashback, reversal): define the handler, register it in config, and call it like any other operation.

Reversals over deletes. For refunds and corrections, create a compensating transaction (crediting the opposite wallet) instead of deleting history; the ledger stays reconstructable.

Gotchas and Tips

  • Decimals are strings, not floats. The package deliberately avoids float math. Compare and sum with bccomp/bcadd or the package's decimal abstraction; $wallet->balance === '0.00' string comparisons will bite you.
  • Never UPDATE wallets SET balance = .... Balance is a maintained, denormalized column; mutate only through the wallet API or you'll desync it from transactions.
  • Concurrency is real. Two simultaneous withdrawals on the same wallet will race unless the underlying operation locks the wallet row. For any read-modify-write you drive yourself, wrap it in DB::transaction and lockForUpdate(). Load-test your hot path (e.g. a shared system wallet) — it's the classic failure mode.
  • Negative balances. Whether a withdrawal may overdraw is a config concern; verify the setting in wallet.php during staging rather than discovering it in production.
  • Currency mismatches are not enforced for you. Set currency per wallet and validate on transfer/pay — the package won't stop you crossing currencies.
  • Polymorphic holder mapping. Wallets are morph-related to the holder model. If you rename models or use a morph map, keep getMorphClass() stable or existing wallets become unreachable — a subtle data-migration trap.
  • Migration ordering. Publish and run wallet migrations before any seeding/backfill that credits balances; backfills that call the API need the tables and service provider present.
  • Confirmation flows change what "balance" means. If you enable transaction confirmation, pending transactions are visible but not settled. Make sure your ledger UI and your balance reads both respect status, otherwise dashboards drift.
  • Querying meta is DB-dependent. meta is JSON; filtering by a meta key works on MySQL/Postgres but is limited on SQLite. If you need heavy meta queries, promote those fields to real columns on your extended Transaction model.
  • Debugging stuck balances: inspect one row in transactions for type, amount, status, meta, plus the wallet's balance column. A mismatch almost always means a raw update or a failed multi-step flow that wasn't in a transaction.
  • Upgrade hygiene: with 277 stars and no downstream dependents, treat it as a small, actively shaped package — pin the version, skim the changelog on bumps, and keep your customizations in config/model subclasses so upgrades don't force forks.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core