How I Built a Laravel Virtual Wallet (And Why You Might Need One Too)
Managing user balances, transactions, and wallets can get messy — especially if you're building products where users deal with digital assets, in-app credits, rewards, or any kind of financial flow.
In this post, I want to share the story behind building the Laravel Virtual Wallet, how it evolved from a small use case into a flexible package, and why it might be the tool you're missing in your Laravel stack.
🔥 The Real-World Frustration
While working on a foreign project, I had to implement a wallet system where each user needed more than one wallet. There were additional needs like locking transactions temporarily, tracking wallet history, and ensuring the system was both extendable and secure.
That challenge sparked the idea — but the more I talked to devs, the more I realized this was a common problem across many apps:
- Fintech apps needing wallet logs and multi-currency
- SaaS tools with customer credits or bonuses
- Gaming and reward platforms with custom wallet logic
- Marketplaces with internal balances
💡 From Idea to Package
I didn’t want to just solve one use case. I wanted a developer-friendly wallet system that could adapt to any project. So I designed a package with:
- ⚙️ Multiple wallets per user (or any model)
- 💸 Simple credit/debit API
- 🔐 Support for freezing transactions
- 🧠 Metadata on transactions for logging & audit
- 🚀 Clean architecture using Laravel standards
It's called: haxneeraj/laravel-virtual-wallet
📦 Installation
composer require haxneeraj/laravel-virtual-wallet
php artisan vendor:publish --provider="Haxneeraj\LaravelVirtualWallet\LaravelVirtualWalletServiceProvider"
php artisan migrate
🔧 Configuration
After publishing the config file, you can modify config/laravel-virtual-wallet.php
to override models, table names, and enums if needed.
📥 Setup in Your Model
Add the trait and implement the interface in your User
(or any Eloquent model):
use Haxneeraj\LaravelVirtualWallet\Interfaces\WalletInterface;
use Haxneeraj\LaravelVirtualWallet\Traits\HasVirtualWallet;
class User extends Authenticatable implements WalletInterface
{
use HasVirtualWallet;
}
💡 Usage
Create Wallets
$user->wallets()->create([
'wallet_type' => 'main', // Type of wallet (e.g., 'main', 'bonus', 'savings'). Define these in your WalletType enum.
'currency' => 'usd', // ISO currency code. Ensure 'usd' or your required currencies are defined in your Currency enum.
'balance' => 100, // Initial wallet balance. Usually set to 0 or default starting value.
'currency_type' => 'fiat_currency', // Define whether the currency is fiat, crypto, token, etc. Set values in CurrencyType enum.
'status' => 'active' // Current status of wallet (e.g., 'active', 'frozen', 'closed'). Defined in WalletStatus enum.
]);
Deposit
$paymentData = new PaymentData([
'owner_type' => User::class,
'owner_id' => $this->user->id,
'txid' => 'test-txid',
'amount' => 100,
'description' => 'Test deposit',
'wallet_type' => 'wallet1',
'method' => 'automatic',
'transaction_type' => 'deposit',
'status' => 'approved',
'currency' => 'usd',
'currency_type' => 'fiat_currency'
]);
$user->deposit($paymentData);
Withdraw
$paymentData = new PaymentData([
'owner_type' => User::class,
'owner_id' => $this->user->id,
'txid' => 'test-txid-withdraw1',
'amount' => 50,
'description' => 'Test withdrawal',
'wallet_type' => 'wallet1',
'method' => 'automatic',
'transaction_type' => 'withdraw',
'status' => 'approved',
'currency' => 'usd',
'currency_type' => 'fiat_currency'
]);
$user->pay($paymentData);
Get Balance
$balance = $user->getBalance('main');
Check Balance
$user->hasSufficientBalance(50, 'main');
Available Methods
Wallet Management
Method | Parameters | Returns | Description |
---|---|---|---|
wallets() |
string $walletType = null |
MorphMany |
Get all wallets or filter by type |
getBalance() |
string $walletType = null |
`int | float` |
hasBalance() |
string $walletType = null |
bool |
Check if wallet has positive balance |
hasSufficientBalance() |
`int | float $amount, string $walletType = null` | bool |
Payment Processing
Method | Parameters | Returns | Description |
---|---|---|---|
pay() |
PaymentData $paymentData |
void |
Process payment from wallet(s) |
Deposit Handling
Method | Parameters | Returns | Description |
---|---|---|---|
deposit() |
PaymentData $paymentData |
void |
Deposit funds into wallet |
Data Objects
PaymentData
The PaymentData
object is used for both payments and deposits. It accepts the following parameters:
-
owner_type
: Owner model type -
owner_id
: Owner model ID -
amount
: The amount to process -
wallet_type
: Type of wallet (optional) -
description
: Transaction description -
status
: Transaction status -
method
: Payment method -
transaction_type
: Type of transaction -
txid
: Transaction ID
Exceptions
The package throws the following exceptions:
-
InvalidWalletException
: When wallet type is invalid or wallet not found -
InsufficientBalanceException
: When wallet balance is insufficient
⚙️ Testing
This package comes with feature and unit tests to ensure everything works smoothly.
🏃 Run Tests
composer test
📦 Key Features
- Create any number of wallets per user
- Multi-currency wallet support
- Clean API for deposits, withdrawals, locking
- Track wallet history & transaction metadata
- Built with Laravel Service Container for flexibility
🚀 What’s Next?
I'm working on a Pro version of the package with:
- 🪙 Cryptocurrency support
- 🔐 Transaction-level 2FA
- ❄️ Transaction freezing/unfreezing
- 🧾 Admin panel (via Nova/Filament integration)
💬 Final Thoughts
This project started from a small need but grew into something powerful because the developer community kept asking for more. Whether you’re building a small cashback app or a full-on financial platform, a good wallet system is essential.
I hope this package saves you time and gives your users the reliability they deserve. If it helps you — give it a ⭐ on GitHub and let me know how you're using it!
Repo: github.com/haxneeraj/laravel-virtual-wallet
Thanks for reading — and happy coding! 🚀