nishant / wallet
A Laravel package for managing multiple wallets per user with deposit, withdraw, and transaction tracking
Installs: 9
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/nishant/wallet
Requires
- php: ^8.0
- illuminate/database: ^11.0
- illuminate/support: ^11.0
Requires (Dev)
- orchestra/testbench: ^9.0
- phpunit/phpunit: ^10.0
README
A comprehensive Laravel package for managing multiple wallets per user with deposit, withdraw, and transaction tracking capabilities. Built for Laravel 11 and PHP 8.0+.
Features
- ✅ Multiple wallets per user
- ✅ Deposit and withdraw functionality
- ✅ Pending transactions with confirmation support
- ✅ Transaction history tracking
- ✅ Get transactions by wallet name
- ✅ Interface-based architecture
- ✅ Trait-based implementation
- ✅ RESTful API endpoints
- ✅ Soft deletes support
- ✅ Balance validation
Requirements
- PHP >= 8.0
- Laravel >= 11.0
Installation
Step 1: Install the Package
Option A: Install from Packagist (Production)
If the package is published to Packagist, use:
composer require nishant/wallet
Note: If you get a stability error, make sure the package has a stable version tag (v1.0.0) in the Git repository. You can also install a specific version:
composer require nishant/wallet:^1.0
Or if you need to allow dev stability temporarily:
composer require nishant/wallet --dev
Option B: Local Development Setup
If you're developing the package locally or testing it before publishing, add it to your Laravel project's composer.json:
{
"require": {
"nishant/wallet": "*"
},
"repositories": [
{
"type": "path",
"url": "./nishant-wallet-laravel"
}
]
}
Important Notes for Local Development:
- The package folder (
nishant-wallet-laravel) should be in the same directory as your Laravel project - After adding this, run
composer updateto link the package - Changes in the package will be immediately available (no need to reinstall)
- The package will be symlinked, so you can edit it directly
- For production, remove the
repositoriessection and usecomposer require nishant/walletinstead
Project Structure:
your-laravel-project/
├── composer.json
├── app/
├── ...
└── nishant-wallet-laravel/ ← Package folder (same level)
├── composer.json
├── src/
└── ...
After adding the repository configuration, run:
composer update nishant/wallet
Step 2: Publish Configuration and Migrations
Publish the configuration file:
php artisan vendor:publish --tag=wallet-config
Publish the migrations:
php artisan vendor:publish --tag=wallet-migrations
Step 3: Run Migrations
Run the database migrations:
php artisan migrate
Step 4: Add Trait to User Model
Add the HasWallets trait to your User model:
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Nishant\Wallet\Traits\HasWallets; class User extends Authenticatable { use HasWallets; // ... rest of your User model }
Configuration
The configuration file is located at config/wallet.php. You can customize:
- user_model: The User model class (default:
App\Models\User) - route_prefix: API route prefix (default:
api/wallet) - middleware: Route middleware (default:
['api']) - decimal_places: Number of decimal places for balances (default:
2)
Usage
Creating Wallets
Using the Trait (Recommended)
use App\Models\User; $user = User::find(1); // Create a new wallet $wallet = $user->createWallet('Main Wallet', 'Primary wallet for transactions'); // Create another wallet $savingsWallet = $user->createWallet('Savings', 'Savings account');
Using the Service
use Nishant\Wallet\Services\WalletService; $walletService = app(WalletService::class); $wallet = $walletService->createWallet( userId: 1, name: 'Main Wallet', description: 'Primary wallet' );
Depositing Amount
Using the Wallet Model
$wallet = $user->getWalletByName('Main Wallet'); // Immediate deposit (confirmed by default) $transaction = $wallet->deposit( amount: 100.00, reference: 'DEP-001', description: 'Initial deposit', meta: ['source' => 'bank_transfer'] ); // Pending deposit (not confirmed - balance won't change until confirmed) $pendingTransaction = $wallet->deposit( amount: 100.00, reference: 'DEP-002', description: 'Pending deposit', meta: null, confirmed: false );
Using the Service
// By wallet ID - confirmed deposit $transaction = $walletService->deposit( walletId: $wallet->id, amount: 100.00, reference: 'DEP-001', description: 'Initial deposit', meta: null, confirmed: true // default is true ); // By wallet ID - pending deposit $pendingTransaction = $walletService->deposit( walletId: $wallet->id, amount: 100.00, reference: 'DEP-002', description: 'Pending deposit', meta: null, confirmed: false // balance won't change ); // By wallet name $transaction = $walletService->depositByName( userId: 1, walletName: 'Main Wallet', amount: 100.00, reference: null, description: null, meta: null, confirmed: true );
Withdrawing Amount
Using the Wallet Model
$wallet = $user->getWalletByName('Main Wallet'); try { // Immediate withdrawal (confirmed by default) $transaction = $wallet->withdraw( amount: 50.00, reference: 'WD-001', description: 'Payment for service', meta: ['recipient' => 'vendor-123'] ); // Pending withdrawal (not confirmed - balance won't change until confirmed) $pendingTransaction = $wallet->withdraw( amount: 50.00, reference: 'WD-002', description: 'Pending withdrawal', meta: null, confirmed: false ); } catch (\Exception $e) { // Handle insufficient balance or other errors echo $e->getMessage(); }
Using the Service
// By wallet ID - confirmed withdrawal $transaction = $walletService->withdraw( walletId: $wallet->id, amount: 50.00, reference: null, description: null, meta: null, confirmed: true // default is true ); // By wallet ID - pending withdrawal $pendingTransaction = $walletService->withdraw( walletId: $wallet->id, amount: 50.00, reference: 'WD-002', description: 'Pending withdrawal', meta: null, confirmed: false // balance won't change ); // By wallet name $transaction = $walletService->withdrawByName( userId: 1, walletName: 'Main Wallet', amount: 50.00, reference: null, description: null, meta: null, confirmed: true );
Getting Transactions
Get All Transactions for a Wallet
$wallet = $user->getWalletByName('Main Wallet'); $transactions = $wallet->transactions;
Get Transactions by Wallet Name
// Using the trait $transactions = $user->getTransactionsByWalletName('Main Wallet'); // Using the service $transactions = $walletService->getTransactionsByWalletName( userId: 1, walletName: 'Main Wallet' );
Get Transactions by Type
$deposits = $wallet->getTransactionsByType('deposit'); $withdrawals = $wallet->getTransactionsByType('withdraw');
Confirming Pending Transactions
When you create a transaction with confirmed: false, the wallet balance is not affected. You can confirm the transaction later to apply the balance change.
Using the Service
use Nishant\Wallet\Services\WalletService; $walletService = app(WalletService::class); // Confirm a pending transaction $confirmedTransaction = $walletService->confirmTransaction($transactionId); // The wallet balance will now be updated based on the transaction type // - For deposits: balance increases // - For withdrawals: balance decreases (if sufficient balance exists)
Check Transaction Status
$transaction = Transaction::find($transactionId); if ($transaction->confirmed) { // Transaction is confirmed, balance has been updated } else { // Transaction is pending, balance has not been updated }
Getting Wallet Information
// Get all wallets $wallets = $user->wallets; // Get active wallets only $activeWallets = $user->activeWallets(); // Get wallet by name $wallet = $user->getWalletByName('Main Wallet'); // Get wallet balance $balance = $wallet->getBalance(); // Check if wallet has sufficient balance if ($wallet->hasBalance(100.00)) { // Proceed with transaction } // Get total balance across all wallets $totalBalance = $user->getTotalBalance();
API Endpoints
The package provides RESTful API endpoints. Make sure you have authentication middleware configured.
Wallet Endpoints
GET /api/wallets- Get all wallets for authenticated userPOST /api/wallets- Create a new walletGET /api/wallets/{id}- Get a specific wallet with transactionsPOST /api/wallets/{id}/deposit- Deposit amount to walletPOST /api/wallets/{id}/withdraw- Withdraw amount from walletGET /api/wallets/{id}/transactions- Get transactions for a walletPOST /api/wallets/transactions/{transactionId}/confirm- Confirm a pending transaction
Transaction Endpoints
GET /api/transactions/by-wallet-name?wallet_name=Main Wallet- Get transactions by wallet namePOST /api/transactions/deposit-by-name- Deposit to wallet by namePOST /api/transactions/withdraw-by-name- Withdraw from wallet by name
API Request Examples
Create Wallet
POST /api/wallet/wallets
Content-Type: application/json
Authorization: Bearer {token}
{
"name": "Main Wallet",
"description": "Primary wallet"
}
Deposit Amount
POST /api/wallets/1/deposit
Content-Type: application/json
Authorization: Bearer {token}
{
"amount": 100.00,
"reference": "DEP-001",
"description": "Initial deposit",
"meta": {
"source": "bank_transfer"
},
"confirmed": true
}
Deposit Amount (Pending)
POST /api/wallets/1/deposit
Content-Type: application/json
Authorization: Bearer {token}
{
"amount": 100.00,
"reference": "DEP-002",
"description": "Pending deposit",
"confirmed": false
}
Withdraw Amount
POST /api/wallets/1/withdraw
Content-Type: application/json
Authorization: Bearer {token}
{
"amount": 50.00,
"reference": "WD-001",
"description": "Payment for service",
"confirmed": true
}
Withdraw Amount (Pending)
POST /api/wallets/1/withdraw
Content-Type: application/json
Authorization: Bearer {token}
{
"amount": 50.00,
"reference": "WD-002",
"description": "Pending withdrawal",
"confirmed": false
}
Confirm Pending Transaction
POST /api/wallets/transactions/{transactionId}/confirm
Authorization: Bearer {token}
Response:
{
"success": true,
"message": "Transaction confirmed successfully",
"data": {
"id": 2,
"wallet_id": 1,
"type": "withdraw",
"amount": "50.00",
"confirmed": true,
"balance_before": "1000.00",
"balance_after": "950.00",
"reference": "WD-002",
"description": "Pending withdrawal"
}
}
Get Transactions by Wallet Name
GET /api/transactions/by-wallet-name?wallet_name=Main Wallet
Authorization: Bearer {token}
Interfaces
The package uses interfaces for better code organization:
WalletInterface
use Nishant\Wallet\Contracts\WalletInterface; interface WalletInterface { public function getBalance(): float; public function deposit(float $amount, ?string $reference = null, ?string $description = null, ?array $meta = null, bool $confirmed = true); public function withdraw(float $amount, ?string $reference = null, ?string $description = null, ?array $meta = null, bool $confirmed = true); public function hasBalance(float $amount): bool; public function transactions(); public function getTransactionsByType(string $type); }
TransactionInterface
use Nishant\Wallet\Contracts\TransactionInterface; interface TransactionInterface { public function getType(): string; public function getAmount(): float; public function getBalanceBefore(): float; public function getBalanceAfter(): float; public function getReference(): ?string; public function getDescription(): ?string; public function getMeta(): ?array; public function isConfirmed(): bool; }
Traits
HasWallets Trait
Add this trait to your User model to enable wallet functionality:
use Nishant\Wallet\Traits\HasWallets; class User extends Model { use HasWallets; }
This trait provides methods like:
wallets()- Relationship to walletscreateWallet()- Create a new walletgetWalletByName()- Get wallet by namegetTransactionsByWalletName()- Get transactions by wallet namegetTotalBalance()- Get total balance across all wallets
Walletable Trait
This trait is automatically used by the Wallet model and provides:
deposit()- Deposit amount (with optionalconfirmedparameter)withdraw()- Withdraw amount (with optionalconfirmedparameter)hasBalance()- Check balancegetBalance()- Get current balance
Database Schema
Wallets Table
id- Primary keyuser_id- Foreign key to users tablename- Wallet namebalance- Current balance (decimal 15,2)is_active- Active statusdescription- Optional descriptiontimestamps- Created/updated timestampsdeleted_at- Soft delete timestamp
Wallet Transactions Table
id- Primary keywallet_id- Foreign key to wallets tabletype- Transaction type (deposit/withdraw)amount- Transaction amountbalance_before- Balance before transactionbalance_after- Balance after transactionreference- Optional reference numberdescription- Optional descriptionmeta- JSON metadataconfirmed- Boolean flag indicating if transaction is confirmed (default: true)timestamps- Created/updated timestamps
Note: When confirmed is false, the transaction is created but the wallet balance is not updated. The balance will only be updated when the transaction is confirmed using the confirmTransaction() method.
Error Handling
The package throws exceptions for various scenarios:
- Insufficient Balance: When trying to withdraw more than available balance (or when confirming a withdrawal)
- Inactive Wallet: When trying to deposit/withdraw from inactive wallet
- Invalid Amount: When amount is zero or negative
- Wallet Not Found: When wallet doesn't exist
- Transaction Already Confirmed: When trying to confirm an already confirmed transaction
- Transaction Not Found: When trying to confirm a non-existent transaction
Always wrap wallet operations in try-catch blocks:
try { $transaction = $wallet->withdraw(100.00); } catch (\Exception $e) { // Handle error logger()->error('Wallet operation failed: ' . $e->getMessage()); } // Confirming transactions try { $confirmedTransaction = $walletService->confirmTransaction($transactionId); } catch (\Exception $e) { // Handle error (e.g., insufficient balance, already confirmed, etc.) logger()->error('Transaction confirmation failed: ' . $e->getMessage()); }
Testing
To test the package, you can use Laravel's testing features:
use Tests\TestCase; use App\Models\User; use Nishant\Wallet\Traits\HasWallets; class WalletTest extends TestCase { public function test_can_create_wallet() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $this->assertNotNull($wallet); $this->assertEquals('Test Wallet', $wallet->name); } public function test_can_deposit_amount() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $transaction = $wallet->deposit(100.00); $this->assertEquals(100.00, $wallet->fresh()->balance); $this->assertEquals('deposit', $transaction->type); } public function test_can_withdraw_amount() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $wallet->deposit(100.00); $transaction = $wallet->withdraw(50.00); $this->assertEquals(50.00, $wallet->fresh()->balance); $this->assertEquals('withdraw', $transaction->type); } public function test_cannot_withdraw_insufficient_balance() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $wallet->deposit(50.00); $this->expectException(\Exception::class); $wallet->withdraw(100.00); } public function test_can_create_pending_transaction() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $wallet->deposit(100.00); // Create pending withdrawal - balance should not change $pendingTransaction = $wallet->withdraw(50.00, null, null, null, false); $this->assertFalse($pendingTransaction->confirmed); $this->assertEquals(100.00, $wallet->fresh()->balance); // Balance unchanged } public function test_can_confirm_pending_transaction() { $user = User::factory()->create(); $wallet = $user->createWallet('Test Wallet'); $wallet->deposit(100.00); // Create pending withdrawal $pendingTransaction = $wallet->withdraw(50.00, null, null, null, false); $this->assertEquals(100.00, $wallet->fresh()->balance); // Confirm the transaction $walletService = app(\Nishant\Wallet\Services\WalletService::class); $confirmedTransaction = $walletService->confirmTransaction($pendingTransaction->id); $this->assertTrue($confirmedTransaction->confirmed); $this->assertEquals(50.00, $wallet->fresh()->balance); // Balance updated } }
License
This package is open-sourced software licensed under the MIT license.
Support
For issues, questions, or contributions, please open an issue on the GitHub repository.
Changelog
Version 1.1.0
- Added pending transaction support with
confirmedcolumn - Transactions can now be created with
confirmed: falseto prevent immediate balance changes - Added
confirmTransaction()method to confirm pending transactions - Added API endpoint for confirming transactions
- Updated interfaces to support confirmed parameter
- Balance is only updated when transactions are confirmed
Version 1.0.0
- Initial release
- Multiple wallets per user
- Deposit and withdraw functionality
- Transaction tracking
- Get transactions by wallet name
- Interface and trait implementation
- RESTful API endpoints