Weave 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 Swap Laravel Package

bavix/laravel-wallet-swap

A Laravel package that adds swap and exchange support to bavix/laravel-wallet, enabling atomic balance conversions between wallets or currencies with configurable rates and a clean, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

bavix/laravel-wallet-swap is an extension for bavix/laravel-wallet that adds currency conversion on top of ordinary wallet transfers: it resolves an exchange rate for a currency pair, calculates the counter-amount, and moves the money between two wallets (which may have different currencies and different decimal_places).

Minimal setup:

composer require bavix/laravel-wallet-swap
php artisan vendor:publish --tag=wallet-swap   # rate table migration/config
php artisan migrate

In 8.0.0 the hard constraint is bavix/laravel-wallet ^12. Do not install this alongside wallet 11 — Composer will refuse, and forcing it produces runtime type errors on the wallet contracts. Confirm your PHP version satisfies wallet 12's floor before starting.

Where to look first, in order:

  1. README.md for the facade/service entry point — the swap operation is invoked through a Swap-style service (facade available), not through the Wallet model itself.
  2. src/ for the Operation enum (Buy, Sell, Exchange) and the rate abstraction (Rate model backed by a table, plus an in-memory implementation for tests).
  3. Your own Wallet models — the package assumes they implement the wallet contracts and have a currency attached.

First use case worth wiring end-to-end: a user holds a USD wallet and wants EUR in a second wallet. Seed one rate pair (USD/EUR), call the exchange operation inside a DB transaction, assert the source wallet is debited by exactly the quoted amount and the target wallet credited by the converted amount, then reconcile the two Transaction rows. That single flow exercises rate lookup, rounding, and cross-currency accounting — the three things that actually break in production.

Implementation Patterns

Rates live in your app, not the package. The package gives you a rate store and lookup; it does not ship live FX data. In practice you register pairs during boot (or via a scheduled job that refreshes them) and read them through the package's rate resolution. Keep the storage numeric-as-string/decimal, never as a float column.

Bind the rate source behind an interface. The package exposes an in-memory rate implementation alongside the database one. Bind your chosen implementation in a service provider, and use the in-memory one in tests so the suite doesn't depend on seeded rate rows. This is also the seam for pulling rates from an external provider with a cache layer in front.

Model each business action as an operation, not a raw transfer. Deposits from a payment gateway map to the buy operation, payouts to sell, wallet-to-wallet conversion to exchange. Keeping these distinct matters because buy/sell normally pair the wallet movement with a counterparty record, while exchange is a pure internal move — mixing them means your reporting and fee logic has to guess intent later.

Always wrap the swap in DB::transaction(). The operation touches two wallets plus the rate lookup; a failure mid-way otherwise leaves one side debited. Refresh both wallet instances after commit before reading balances — the Wallet model caches its balance.

Route money through the wallet package, not around it. Use the wallet's transfer/deposit/withdraw methods for the actual movement so Transaction rows, holds, and any meta you attach are consistent. The swap layer should compute amounts and then delegate; hand-writing $wallet->balance -= $x bypasses the audit trail and breaks getBalance().

Cross-currency accounting: the debit is in the source wallet's currency, the credit in the target's. Store the rate and both amounts in transaction meta so a later audit can re-derive the calculation after rates have moved.

Testing pattern: RefreshDatabase for the rate table, freeze/seed rates per test, and assert on Money objects (getBalance()) rather than arrays. For the 8.0 upgrade, note that the package's own suite moved to PHPUnit 11 — if you extend or vendor any of its test helpers, expect attribute-based config instead of annotations.

Gotchas and Tips

Rate direction is the number-one source of wrong money. A stored USD/EUR = 0.92 means 1 USD buys 0.92 EUR; the inverse is 1 / 0.92, not 0.92. Decide once whether you store direct pairs, inverted pairs, or both, and write a test that converts 100 in each direction and checks the round-trip loss is only rounding.

Rounding is where money disappears. Convert at full precision, round once at the end, and round the same way every time. If the source and target wallets have different decimal_places, the intermediate value must be carried at the higher precision or you will silently lose a cent per swap — which is a lot of cents at volume.

laravel-wallet 12 is the breaking change in 8.0.0. The visible release notes are mostly dependency bumps (PHPUnit 11, GitHub Actions) but the payload is the wallet-12 support PR. Treat this as a coordinated upgrade: bump bavix/laravel-wallet to 12 first, run your own test suite against it, then bump the swap package. Check wallet 12's changelog for contract and model changes before assuming your Wallet subclasses still work.

Don't derive rates from a Money division unless both operands share a currency context — brick/money will throw on mixed-currency arithmetic. Build the converted amount explicitly against the target currency.

Cache rates but invalidate on write. Rates are read on every swap and written rarely; a cache without invalidation means a manual rate correction silently doesn't apply until TTL expiry — and it will be noticed during an incident.

Extension points to keep in mind: the rate interface binding (swap implementations, add caching or an external feed), the operation enum (add your own fee-bearing operations rather than overloading exchange), and transaction meta (store rate snapshot, source of rate, and any fee split there). Avoid subclassing the service class — it's the least stable surface across majors.

Debugging a failed swap: check Rate lookup returns a value for the pair (missing pair usually surfaces as a null/exception deep in the service), confirm both wallets belong to the same user and are not frozen, and verify the source balance covers amount plus any fee before the transaction opens — otherwise you get a constraint/driver rollback rather than a validation error.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums