byjg / wallets
A robust PHP library for managing digital wallets and financial transactions with full audit trails, transaction chain integrity, and support for reserved funds.
Fund package maintenance!
byjg
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/byjg/wallets
Requires
- php: >=8.1 <8.4
- ext-openssl: *
- ext-pdo: *
- byjg/micro-orm: ^5.0
Requires (Dev)
- byjg/migration: ^5.0
- phpunit/phpunit: ^9.6
- vimeo/psalm: ^5.9
This package is auto-updated.
Last update: 2025-11-25 14:25:59 UTC
README
A robust PHP library for managing digital wallets and financial transactions with full audit trails, transaction chain integrity, and support for reserved funds.
Features
- Multiple wallets per user - Each user can have multiple wallets for different currencies
- Multiple currencies - Support for any currency type (fiat, crypto, points, etc.)
- Reserved funds - Pre-authorize and block funds for pending transactions
- Transaction chain integrity - Immutable transaction history with UUID linking and checksums
- Atomic operations - All balance updates are atomic and transactional
- Flexible scale - Support for different decimal places (cents, satoshis, whole units)
- Overdraft support - Optional negative balances with configurable minimum values
- Audit trail - Complete transaction history with balance snapshots
- Idempotent operations - UUID-based transaction deduplication
- Extensible - Easily extend wallets and transactions with custom fields
Installation
composer require byjg/wallets
Quick Start
use ByJG\Wallets\Service\WalletService; use ByJG\Wallets\Service\WalletTypeService; use ByJG\Wallets\Service\TransactionService; use ByJG\Wallets\Entity\WalletTypeEntity; use ByJG\Wallets\Repository\WalletRepository; use ByJG\Wallets\Repository\WalletTypeRepository; use ByJG\Wallets\Repository\TransactionRepository; use ByJG\Wallets\DTO\TransactionDTO; use ByJG\AnyDataset\Db\Factory; // Initialize database connection $dbDriver = Factory::getDbInstance('mysql://user:pass@localhost/dbname'); // Initialize repositories $walletTypeRepo = new WalletTypeRepository($dbDriver); $transactionRepo = new TransactionRepository($dbDriver); $walletRepo = new WalletRepository($dbDriver); // Initialize services $walletTypeService = new WalletTypeService($walletTypeRepo); $transactionService = new TransactionService($transactionRepo, $walletRepo); $walletService = new WalletService($walletRepo, $walletTypeService, $transactionService); // Create a wallet type $walletType = new WalletTypeEntity(); $walletType->setWalletTypeId('USD'); $walletType->setName('US Dollar'); $walletTypeService->update($walletType); // Create a wallet with $100.00 initial balance (10000 cents) $walletId = $walletService->createWallet('USD', 'user-123', 10000, 2); // Add funds: $50.00 $transaction = $transactionService->addFunds( TransactionDTO::create($walletId, 5000) ->setDescription('Deposit from bank account') ); // Withdraw funds: $30.00 $transaction = $transactionService->withdrawFunds( TransactionDTO::create($walletId, 3000) ->setDescription('Purchase payment') ); // Reserve funds for pending withdrawal: $20.00 $reserve = $transactionService->reserveFundsForWithdraw( TransactionDTO::create($walletId, 2000) ->setDescription('Pre-authorization') ); // Accept the reservation $transactionService->acceptFundsById($reserve->getTransactionId()); // Get wallet balance $wallet = $walletService->getById($walletId); echo "Available: " . ($wallet->getAvailable() / 100) . " USD\n";
Documentation
- Getting Started - Installation, database setup, and quick start guide
- Wallet Management - Create, retrieve, and manage wallets
- Transaction Operations - Add, withdraw, and query transactions
- Reserved Funds - Pre-authorize and manage pending transactions
- Extending Entities - Add custom fields to wallets and transactions
- Database Schema - Complete database schema documentation
Full documentation is available at https://opensource.byjg.com/docs/php/wallets
Core Concepts
Integer Storage with Scale
All monetary amounts are stored as integers (BIGINT) representing the smallest currency unit:
// USD with scale=2 (cents) $walletId = $walletService->createWallet('USD', 'user-123', 10000, 2); // 10000 represents $100.00 // Bitcoin with scale=8 (satoshis) $btcWalletId = $walletService->createWallet('BTC', 'user-123', 100000000, 8); // 100000000 represents 1.00000000 BTC // Loyalty points with scale=0 (whole units) $pointsWalletId = $walletService->createWallet('POINTS', 'user-123', 1000, 0); // 1000 represents 1000 points
Balance Components
Each wallet maintains three balance components:
- balance - Total funds (reserved + available)
- reserved - Funds held for pending transactions
- available - Funds available for immediate use
$wallet = $walletService->getById($walletId); echo "Balance: " . $wallet->getBalance(); // 10000 (total) echo "Reserved: " . $wallet->getReserved(); // 2000 (blocked) echo "Available: " . $wallet->getAvailable(); // 8000 (usable)
Transaction Types
| Type | Code | Description |
|---|---|---|
| Balance | B | Initial balance or reset |
| Deposit | D | Add funds immediately |
| Withdraw | W | Remove funds immediately |
| Deposit Blocked | DB | Reserve for incoming funds |
| Withdraw Blocked | WB | Reserve funds for withdrawal |
| Reject | R | Reverse a reserved transaction |
Transaction Chain Integrity
Every transaction links to the previous via uuid and previousuuid, creating an immutable audit trail:
$tx1 = $transactionService->addFunds($dto1); // previousuuid = null $tx2 = $transactionService->addFunds($dto2); // previousuuid = $tx1->uuid $tx3 = $transactionService->withdrawFunds($dto3); // previousuuid = $tx2->uuid
Each transaction includes a SHA-256 checksum for data integrity verification.
Use Cases
E-commerce Platform
// Reserve funds when order is placed $reserve = $transactionService->reserveFundsForWithdraw( TransactionDTO::create($walletId, 9999) ->setDescription('Order #12345') ->setReferenceSource('ecommerce') ->setReferenceId('order-12345') ); // Capture payment when order ships $transactionService->acceptFundsById($reserve->getTransactionId()); // Or cancel and release funds if order is cancelled // $transactionService->rejectFundsById($reserve->getTransactionId());
Multi-Currency Wallets
// Create multiple wallets for same user $usdWallet = $walletService->createWallet('USD', 'user-123', 100000, 2); $eurWallet = $walletService->createWallet('EUR', 'user-123', 50000, 2); $btcWallet = $walletService->createWallet('BTC', 'user-123', 5000000, 8); // Transfer between wallets (with exchange rate logic in your app) $walletService->transferFunds($usdWallet, $eurWallet, 10000);
Gaming/Betting Platform
// Reserve bet amount $betReserve = $transactionService->reserveFundsForWithdraw( TransactionDTO::create($walletId, 5000) ->setDescription('Bet on Game #789') ); // User wins - reject withdrawal and add winnings $transactionService->rejectFundsById($betReserve->getTransactionId()); $transactionService->addFunds( TransactionDTO::create($walletId, 10000) ->setDescription('Bet winnings') ); // User loses - accept withdrawal // $transactionService->acceptFundsById($betReserve->getTransactionId());
Testing
# Start MySQL container docker run --name mysql-container --rm \ -e MYSQL_ROOT_PASSWORD=password \ -p 3306:3306 -d mysql:8.0 # Run migrations vendor/bin/migrate up mysql://root:password@localhost/test -path=db # Run tests vendor/bin/phpunit
API Reference
WalletService
createWallet(string $walletTypeId, string $userId, int $balance, int $scale = 2, int $minValue = 0, ?string $extra = null): intgetById(int $walletId): WalletEntitygetByUserId(string $userId, string $walletType = ""): arraygetByWalletTypeId(string $walletTypeId): arrayoverrideBalance(int $walletId, int $newBalance, int $newScale = 2, int $newMinValue = 0, string $description = "Reset Balance"): ?intpartialBalance(int $walletId, int $balance, string $description = "Partial Balance"): TransactionEntitycloseWallet(int $walletId): ?inttransferFunds(int $walletSource, int $walletTarget, int $amount): array
TransactionService
addFunds(TransactionDTO $dto): TransactionEntitywithdrawFunds(TransactionDTO $dto): TransactionEntityreserveFundsForWithdraw(TransactionDTO $dto): TransactionEntityreserveFundsForDeposit(TransactionDTO $dto): TransactionEntityacceptFundsById(int $transactionId): intacceptFundsByUuid(string $uuid): intrejectFundsById(int $transactionId): intrejectFundsByUuid(string $uuid): intacceptPartialFundsById(int $transactionId, TransactionDTO $transactionDTO, TransactionDTO $transactionRefundDTO): TransactionEntitygetById(int $transactionId): TransactionEntitygetByWallet(int $walletId, int $limit = null, int $offset = null): arraygetByDate(int $walletId, string $startDate, string $endDate, int $limit = null, int $offset = null): arraygetByReference(string $referenceSource, string $referenceId): arraygetByUuid(string $uuid): ?TransactionEntityexistsTransactionByUuid(string $uuid): boolgetReservedTransactions(int $walletId): array
Dependencies
flowchart TD
byjg/wallets --> ext-pdo
byjg/wallets --> ext-openssl
byjg/wallets --> byjg/micro-orm
Loading