bavix/laravel-wallet
bavix/laravel-wallet is a Laravel package for working with virtual wallets, supporting transactions, deposits, withdrawals, and multi-wallet setups with a documented API.
Architecture fit
HasWallet / CanPay / HasWalletFloat traits and Wallet / Customer / ProductInterface contracts to existing Eloquent models. This is a good fit for a Laravel codebase that already has User, Order, and Item aggregates — it avoids introducing a parallel domain entity and a synchronization problem with our own tables.wallets table keyed by a polymorphic holder (holder_type, holder_id) plus a slug for multi-wallet, a transactions ledger with payable morphs and a confirmed flag, and a separate transfers record for holder-to-holder movement. That maps cleanly onto the reports and reconciliations a finance stakeholder will inevitably request.decimal_places, with brick/math for arithmetic and an optional float-facing trait (balanceFloat). This is the correct default for a wallet system; the risk is that the float surface is available at all and gets used inside business logic.WALLET_CACHE_DRIVER, WALLET_CACHE_TTL, WALLET_LOCK_DRIVER, WALLET_LOCK_TTL since 10.x). This is the single most important architectural decision in the package and the one a TPM must insist on load-testing before commitment. Correctness of balances depends on our Redis/lock configuration, not just on the library.Integration feasibility
composer require bavix/laravel-wallet, publish and run migrations, add traits + interfaces to the User model, then deposit / withdraw / pay / refund work. A spike should reach a working balance in under a day.ProductInterface (unlimited) or ProductLimitedInterface (limited) is straightforward, but the docs explicitly warn against the limited interface for shopping carts due to N-query behavior and recommend overriding PurchaseServiceInterface instead. Any cart/checkout flow will need that work plus a bulk availability check.PurchaseQuery / PurchaseQueryHandlerInterface, transaction and transfer UUIDs) provides the idempotency hooks we need for retry-safe payment endpoints — this is a meaningful de-risking feature that should be treated as mandatory rather than optional.laravel-wallet-uuid (UUID model keys), laravel-wallet-swap (FX rates — defer unless multi-currency is in scope), laravel-wallet-warmup (balance refresh, useful if we hit drift).Technical risk
wallets.balance from the transaction sum. We need a scheduled reconcile-and-repair job and an alert on drift from day one, not post-launch.forceWithdraw permits negative balances by design. That is a policy lever, not a bug, and it needs an explicit product decision plus an authorization wrapper in our code.BookkeeperServiceInterface::missing → forget, MorphMany → HasMany on transfers, removed LockProviderNotFoundException). Semver is respected for majors but not all breaking behavior waits for one.rez1dent3), and 0 listed dependents. The dependent count is likely a dataset artifact, but it cannot be used as evidence of ecosystem validation. MIT license is fine; bus-factor risk is not.transactions and wallets and reports index size 685 → 329 MB (MySQL) and insert of 50k rows −63%. The win is real, but the migration runs DROP INDEX — on a high-write table this needs pt-online-schema-change / DROP INDEX CONCURRENTLY plus manual migrations table bookkeeping, as the release note itself says.Key questions
PurchaseServiceInterface work the first does not.slug segmentation, or true multi-currency with FX? Only the third pulls in laravel-wallet-swap.forceWithdraw), and who authorizes it?Stack fit
Migration path
transactions is already large.Compatibility
brick/math, doctrine/dbal historically) but must clear our dependency policy, including license and advisory scanning.bavix/laravel-wallet-uuid from the start rather than retrofitting.Sequencing
slug, float surface, and a 200-concurrent-write test against Redis locks with a drift check.User and Item; purchase service override if cart flows are in scope; external API with UUID idempotency on our payment endpoints.Maintenance
Support
@laravel_wallet) rather than a formal SLA or issue-triage commitment. For anything customer-facing, we are the support tier — no vendor escalation path exists.Scaling
with('wallet') / with('wallets') consistently; un-eager-loaded balance access is the classic N+1 in this package and will surface as a latency regression under load, not as an error.Failure modes
| Failure | Symptom | Mitigation |
|---|---|---|
| Lock acquisition failure / TTL too short | Concurrent withdrawals both succeed; negative balance | Load-test lock config; alert on lock exceptions; enforce non-negative invariant in application layer |
| Bookkeeper cache desync | wallets.balance ≠ sum of transactions |
Scheduled reconcile job + drift alert + repair command; adopt laravel-wallet-warmup if drift recurs |
| Cache/Redis outage | All wallet writes fail; checkout hard-down | Treat Redis as tier-0; circuit-break checkout gracefully; document degraded-mode policy |
| Duplicate submission on retry | Double debit | External API UUID idempotency on every payment endpoint |
| Unintended overdraft | forceWithdraw used outside policy |
Wrap in an authorization-guarded service method; forbid direct calls in code review/lint |
Migration locking on large transactions |
Write outage during deploy | Online schema change (pt-online-schema-change / DROP INDEX CONCURRENTLY) + manual migrations bookkeeping |
| Float arithmetic in business logic | Cent-level drift, reconciliation noise | Ban balanceFloat outside presentation; lint/enforce integer-unit math |
| Abandoned dependency | No upstream fixes for a framework CVE | Internal seam + pinned version + documented fork path |
Ramp-up
How can I help you explore Laravel packages today?