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! 🚀