Weave Code
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

bavix/laravel-wallet

Virtual wallet system for Laravel: attach wallets to models, track balances, perform deposits/withdrawals/transfers, handle atomic transactions and events, and support multi-currency and bookkeeping. Includes docs, benchmarks, and upgrade guide.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require bavix/laravel-wallet
   php artisan vendor:publish --tag=laravel-wallet-config

Publish the config file to customize settings like lock (race condition handling) and cache (state storage).

  1. Basic Model Integration: Add traits and interfaces to your model (e.g., User):

    use Bavix\Wallet\Traits\HasWallet;
    use Bavix\Wallet\Interfaces\Wallet;
    
    class User extends Model implements Wallet
    {
        use HasWallet;
    }
    

    This adds balance, balanceInt, deposit(), withdraw(), and other wallet methods.

  2. First Transaction:

    $user = User::first();
    $user->deposit(100); // Deposit 100 units
    $user->withdraw(20); // Withdraw 20 units
    

    Verify balance:

    $user->balance; // Returns float (e.g., 80.00)
    $user->balanceInt; // Returns integer (e.g., 80)
    
  3. Eager Loading: Optimize queries with:

    User::with('wallet')->get(); // For single wallet
    User::with('wallets')->get(); // For multi-wallet
    

Implementation Patterns

Core Workflows

  1. Standard Transactions:

    • Use deposit()/withdraw() for basic operations.
    • Example: Refund logic:
      $user->refund($product); // Reverts a purchase
      
  2. Purchases with CanPay:

    • Extend User with CanPay trait and Customer interface:
      use Bavix\Wallet\Traits\CanPay;
      use Bavix\Wallet\Interfaces\Customer;
      
      class User extends Model implements Customer
      {
          use CanPay, HasWallet;
      }
      
    • Define products (e.g., Item) with ProductInterface or ProductLimitedInterface:
      class Item implements ProductInterface
      {
          use HasWallet;
      
          public function getAmountProduct(Customer $customer): int|string
          {
              return 100;
          }
      }
      
    • Execute purchases:
      $user->pay($item); // Throws exception if insufficient funds
      $user->safePay($item); // Returns bool (false if failed)
      
  3. Fractional Currency Support:

    • Use HasWalletFloat for decimal balances:
      class User implements Wallet, WalletFloat
      {
          use HasWalletFloat;
      }
      
    • Example:
      $user->depositFloat(1.99); // Deposit fractional amount
      $user->balanceFloat; // Returns 1.99
      
  4. Multi-Wallet Systems:

    • Attach multiple wallets to a model (e.g., User):
      $user->wallets()->attach($walletId, ['type' => 'premium']);
      
    • Access wallets via wallets() relationship.
  5. Atomic Operations:

    • Use AtomicServiceInterface for race-condition-safe operations:
      app(AtomicServiceInterface::class)->block($wallet, function () {
          $wallet->withdraw(100);
          $user->update(['premium_until' => now()->addDays(30)]);
      });
      
    • Block multiple wallets:
      app(AtomicServiceInterface::class)->blocks([$wallet1, $wallet2], function () {
          $wallet1->withdraw(50);
          $wallet2->withdraw(50);
      });
      

Integration Tips

  1. Transactions:

    • Wallet locks are automatic during transactions. Minimize transaction duration:
      DB::transaction(function () use ($wallet) {
          $wallet->withdraw(100); // Locked until commit/rollback
          // Other DB operations...
      });
      
  2. Race Conditions:

    • Configure wallet.php for lock/cache drivers (e.g., Redis for production):
      'lock' => ['driver' => 'redis', 'seconds' => 1],
      'cache' => ['driver' => 'redis'],
      
  3. Custom Logic:

    • Override methods like canBuy() for ProductLimitedInterface to enforce rules:
      public function canBuy(Customer $customer, int $quantity = 1): bool
      {
          return $customer->hasRole('premium') || $quantity <= 3;
      }
      
  4. Extensions:

    • Use extensions like laravel-wallet-swap for exchange rates or laravel-wallet-uuid for UUID support.
  5. Events:

    • Listen for wallet events (e.g., wallet.deposited, wallet.withdrawn) via Laravel’s event system.

Gotchas and Tips

Pitfalls

  1. Wallet Locking:

    • Long transactions can cause timeouts or race conditions. Keep transactions short.
    • Nested transactions: Avoid calling wallet methods inside nested transactions (e.g., DB::transaction inside another).
  2. Race Conditions:

    • Without Redis/lock configuration, concurrent requests may fail with WalletIsLockedException.
    • Test race scenarios locally with lock.driver = 'array' (default) before switching to Redis.
  3. Fractional Precision:

    • balanceFloat uses PHP’s float type, which may introduce rounding errors. For financial apps, consider:
      • Storing cents as integers (e.g., balanceCents = 199 for $1.99).
      • Using bcmath for precise calculations.
  4. Multi-Wallet Quirks:

    • Default wallet is determined by the default pivot value. Ensure consistency when attaching wallets:
      $user->wallets()->attach($walletId, ['default' => true]);
      
  5. Atomic Service Limits:

    • Blocking multiple wallets (blocks()) creates N lock requests, which can be slow. Use sparingly.

Debugging Tips

  1. Lock Issues:

    • Check wallet.log (if configured) for blocked wallets:
      'log' => [
          'enabled' => true,
          'path' => storage_path('logs/wallet.log'),
      ],
      
    • Manually release locks (if stuck) via Redis CLI:
      redis-cli DEL "wallet:lock:{wallet_id}"
      
  2. Balance Mismatches:

    • Verify balance vs. balanceInt for fractional values. Use balanceFloat for debugging:
      dd($user->balance, $user->balanceInt, $user->balanceFloat);
      
  3. Performance:

    • Profile with Cart:Pay benchmarks. If slow, check:
      • Eager loading (with('wallet')).
      • Database indexes on wallets table (user_id, currency).
      • Lock driver (Redis is faster than array).
  4. Event Debugging:

    • Listen for events in AppServiceProvider:
      public function boot()
      {
          Wallet::deposited(function ($wallet, $amount) {
              Log::info("Deposited {$amount} to wallet {$wallet->id}");
          });
      }
      

Extension Points

  1. Custom Wallets:

    • Extend Bavix\Wallet\Models\Wallet for custom logic (e.g., PremiumWallet):
      class PremiumWallet extends Wallet
      {
          public function premiumWithdraw($amount)
          {
              if ($this->user->hasRole('premium')) {
                  return $this->withdraw($amount);
              }
              throw new \RuntimeException('Premium required');
          }
      }
      
  2. Dynamic Pricing:

    • Override getAmountProduct() to apply discounts:
      public function getAmountProduct(Customer $customer): int
      {
          return $this->price * (1 - ($customer->discount / 100));
      }
      
  3. Webhooks:

    • Dispatch events for third-party integrations:
      event(new WalletDeposited($wallet, $amount));
      
    • Listen in another service:
      WalletDeposited::listen(function ($event) {
          // Notify Stripe/PayPal
      });
      
  4. Testing:

    • Use WalletTestCase (if provided) or mock the Wallet facade:
      $this->partialMock(Wallet::class, ['withdraw'])
           ->expects($this->once())
           ->method('withdraw')
           ->with(100);
      
  5. Fallback Logic:

    • Handle WalletIsLockedException gracefully
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport