laravel-wallet

repository·master·Indexed 23 days ago

https://github.com/bavix/laravel-wallet

A library for managing virtual wallets, transactions, and purchases within Laravel applications. It provides tools for deposits, withdrawals, and purchasing systems via Customer and Product interfaces. The package supports integer and floating-point balances, eager loading to prevent N+1 queries, and atomic operations via AtomicServiceInterface. Additional extensions include laravel-wallet-swap for exchanging balances and laravel-wallet-uuid for UUID/ULID identifier support.

Tokens
32.2K
Snippets
93
Records
142
Agent score
77%

What's inside laravel-wallet

  1. Compare transactions() vs walletTransactions() in multi-wallet setups

    master

    In a multi-wallet environment, the distinction between the owner's global transaction history and a specific wallet's history is handled by two different methods:

    1. Global Transactions: $user->transactions() returns the sum of all transactions across all wallets owned by the user.
    2. Wallet-Specific Transactions: $wallet->walletTransactions() returns only the transactions that occurred within that specific wallet.

    Example behavior:

    • If a user has a default wallet with 3 transactions and a 'USD' wallet with 1 transaction, $user->transactions()->count() will return 4, while $usd->walletTransactions()->count() will return 1.
  2. Use Merchant Fee Deductible to change commission behavior

    master

    By default, the Taxable interface adds fees to the customer's payment (Customer pays Price + Fee).

    If you want the customer to pay only the product price and have the fee deducted from the merchant's payout instead, implement the MerchantFeeDeductible interface in your Item model.

    Comparison:

    • Taxable (Default): Product $100, Fee 5% $\rightarrow$ Customer pays $105, Merchant receives $100.
    • MerchantFeeDeductible: Product $100, Fee 5% $\rightarrow$ Customer pays $100, Merchant receives $95.
  3. Use MerchantFeeDeductible to deduct fees from merchant payouts

    master

    The MerchantFeeDeductible interface allows you to implement a fee structure where the customer pays only the listed product price, and the fee is deducted from the merchant's payout. This is the opposite of the Taxable interface, where fees are added to the customer's payment.

    Comparison:

    • Taxable: Customer pays $105 (Price + 5% fee); Merchant receives $100.
    • MerchantFeeDeductible: Customer pays $100 (Price only); Merchant receives $95 (Price - 5% fee).
  4. Execute atomic operations across multiple wallets

    master

    If you need to perform operations involving multiple wallets simultaneously (for example, debiting from two different wallets at once), use the blocks() method.

    This method locks all provided wallets and starts a transaction. The entire operation is considered successful only if all actions within the closure succeed. If any single operation fails (e.g., one wallet has insufficient funds), the entire operation is canceled and rolled back.

    Note: Using blocks() is an expensive operation as it generates $N$ requests to the lock service, where $N$ is the number of wallets provided.

    use Bavix\Wallet\Services\AtomicServiceInterface;
    
    app(AtomicServiceInterface::class)->blocks([$wallet1, $wallet2], function () use ($wallet1, $wallet2) {
        $wallet1->withdraw(100);
        $wallet2->withdraw(100);
    });
  5. Configure the User model for multi-wallet support

    master

    To use wallets and the swap functionality, your model (e.g., User) must implement the Bavix\Wallet\Interfaces\Wallet interface and use the HasWallet and HasWallets traits.

    use Bavix\Wallet\Interfaces\Wallet;
    use Bavix\Wallet\Traits\HasWallets;
    use Bavix\Wallet\Traits\HasWallet;
    
    class User extends Model implements Wallet
    {
        use HasWallet, HasWallets;
    }
  6. Configure the Item model for MerchantFeeDeductible

    master

    To make an item (product) deduct fees from the merchant, implement the MerchantFeeDeductible interface (which extends Taxable) and the ProductInterface or ProductLimitedInterface. You must also include the HasWallet trait.

    Required methods:

    • getFeePercent(): Returns the fee percentage (0 to 100).
    use Bavix\Wallet\Traits\HasWallet;
    use Bavix\Wallet\Interfaces\Customer;
    use Bavix\Wallet\Interfaces\MerchantFeeDeductible;
    use Bavix\Wallet\Interfaces\ProductLimitedInterface;
    
    class Item extends Model implements ProductLimitedInterface, MerchantFeeDeductible
    {
        use HasWallet;
    
        public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
        {
            return true; 
        }
    
        public function getAmountProduct(Customer $customer): int|string
        {
            return 100;
        }
    
        public function getMetaProduct(): ?array
        {
            return [
                'title' => $this->title, 
                'description' => 'Purchase of Product #' . $this->id,
            ];
        }
    
        /**
         * Specify the percentage of the amount. 
         * Minimum 0; Maximum 100
         */
        public function getFeePercent(): float|int
        {
            return 5.0; // 5%
        }
    }
  7. Register an Exchange Service

    master

    Once you have implemented your ExchangeServiceInterface, you must register it in the config/wallet.php configuration file so the library can resolve it. Add your class name to the services.exchange key.

    return [
        // ...
        'services' => [
            'exchange' => MyExchangeService::class,
            // ...
        ],
        // ...
    ];
  8. Implement limited products with ProductLimitedInterface

    master

    If a product has constraints on how many times it can be purchased (e.g., a one-time service), implement ProductLimitedInterface on your Item model.

    You must implement the canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool method to define your constraint logic.

    Recommendation: For shopping cart implementations, do not use the limited interface. Instead, use PurchaseQuery and PurchaseQueryHandlerInterface as the primary API for checking purchase availability.

    use Bavix//
    use Bavix//
    use Bavix//
    use Bavix//
    
    class Item extends Model implements ProductLimitedInterface
    {
        use HasWallet;
    
        public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
        {
            // Implement constraint logic here
            return true; 
        }
        
        public function getAmountProduct(Customer $customer): int|string
        {
            return 100;
        }
    
        public function getMetaProduct(): ?array
        {
            return [
                'title' => $this->title, 
                'description' => 'Purchase of Product #' . $this->id,
            ];
        }
    }