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

Tomato Wallet Laravel Package

tomatophp/tomato-wallet

A Laravel wallet package from TomatoPHP for managing user balances and transactions. Add credit and debit operations, track wallet history, and integrate multi-wallet support into your app with a simple, fluent API.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you run composer require. This package is at 1 star, 0 dependents, single maintainer, last release May 2024. Treat it as a reference implementation you will fork, not a dependency you will lean on. Confirm three things in the source before you commit a sprint to it: which tomato-* packages are hard transitive dependencies, whether the balance is a mutable column or derived from transactions, and whether balance mutations use row locking. If the answer to the third is "nothing," stop and read the Gotchas section first.

Minimal install path.

  1. composer require tomatophp/tomato-wallet — package auto-discovery should register the service provider, but verify extra.laravel.providers in its composer.json rather than assuming.
  2. Publish config and migrations: php artisan vendor:publish --provider="TomatoPHP\TomatoWallet\Providers\TomatoWalletServiceProvider" (confirm the provider class name from src/ — the docs sources here do not name it).
  3. Read every published migration before running it. You are looking for: table names, whether balance is decimal/bigInteger minor units/float, indexes on the wallet owner polymorphic columns, and whether transactions have a uniqueness constraint you can hang idempotency on.
  4. php artisan migrate on a scratch database — not your dev DB — so you can inspect the resulting schema.
  5. Attach the HasWallet trait to one model (a User or Team) and confirm the relation works.

Where to look first, in order.

  • src/Traits/HasWallet.php (or equivalent) — this is the public surface. Every method you will call daily is defined here or delegated from here.
  • src/Models/ — the wallet and transaction models. The transaction model's $fillable and casts tell you the metadata contract.
  • config/wallet.php — currency, precision, transaction types, and any "gates" config. This is the extension surface.
  • src/Services/ or src/Actions/ if present — the sanctioned mutation path. Find out whether deposits/withdraws are supposed to go through a service class or by calling model methods directly. Most of the correctness risk lives in that distinction.
  • src/Filament/ — the admin resource. Useful as executable documentation of intended usage.

First use case to build. Do not start with payments. Start with an internal, auditable credit: refund-as-store-credit on an order model. It exercises the trait, a deposit, a transaction record, and the admin view — all without a gateway, webhook, or real money. Get that green, then write a parallel-debit test that fires 50 concurrent debits of the same wallet and asserts the final balance. That test is your go/no-go gate.

Sanity script. Before writing application code, run a throwaway tinker session: create a wallet, deposit 100, withdraw 30, transfer 20 to a second wallet, then WalletTransaction::all() and read the rows. What you learn about the transaction shape in five minutes saves a day of guessing.

Implementation Patterns

Wrap the package behind your own interface — day one, not later. No call site in your application should touch a TomatoWallet class. Define App\Contracts\WalletService with deposit, withdraw, transfer, balance, history, and implement it in App\Services\TomatoWalletAdapter. Every controller, job, and Filament action depends on the contract. This is the single highest-leverage thing you will do with this package: it converts "we adopted an unmaintained dependency" into "we adopted a swappable ledger adapter," and it costs half a day. It is also what makes a later replacement a rewrite of one class instead of a grep across the codebase.

Attach wallets polymorphically, but scope them deliberately. The HasWallet trait is designed for any Eloquent model. Resist attaching it to everything. Decide whether a wallet belongs to the User, the Team, or the Order, and enforce that decision in the adapter — if both a user and their team can hold balances, you now have a transfer-and-reconciliation problem the package will not solve for you.

Treat every mutation as a service-layer operation. Whatever the package's sanctioned path is, do not let controllers call $user->deposit() inline. Put the call in your adapter, wrapped in DB::transaction(), and add:

  • An idempotency key carried in the transaction metadata (order ID, payment intent ID, webhook event ID). Check for an existing transaction with that key before mutating.
  • A row lock on the wallet before reading the balance for any decision, even if the package claims atomicity. Wallet::whereKey($id)->lockForUpdate()->first() inside your transaction. Verify empirically whether the package already does this; if it does, you are paying a small redundancy cost, which is cheap insurance.
  • A post-condition assertion — re-read the balance and assert it matches the expected value before committing. If it does not, throw and let the transaction roll back.

Webhook and top-up flow. The pattern that survives contact with a real gateway:

  1. Gateway calls your controller. Verify the signature first, before touching the database.
  2. Look up the pending payment record by gateway reference. If it does not exist, return 200 and log — do not create a wallet credit from an unmatched webhook.
  3. Check for an existing wallet transaction with that idempotency key. If present, return 200 immediately (this is a replay).
  4. Inside a DB transaction: lock the wallet, deposit, write the transaction with full metadata (gateway, event_id, amount, currency, raw payload hash).
  5. Return 200 only after commit. Gateways retry on non-2xx, and you want retries on genuine failures.

Refunds and adjustments. Issue refunds as a credit transaction with a distinct transaction type, not as a negative payment. Keep the type taxonomy in config, not as magic strings in your adapter. If the package's configurable transaction types are actually extensible, register your own enum there; if not, enforce the taxonomy in your adapter and document it.

Multi-currency. The package supports per-wallet balances by currency. Decide up front whether a wallet can hold multiple currencies or whether a user gets one wallet per currency — and enforce it in your adapter, because a transfer between wallets of different currencies is a conversion, and this package does not do FX. If you find yourself writing conversion logic, you have left the package's scope and should reconsider the approach.

Admin and support workflows. The Filament resource is where support staff will live. Verify it works against your Filament major version before promising it to anyone. Manual balance adjustments by support should go through your adapter with a required reason string in metadata — an unexplained admin credit is exactly what an auditor will ask about, and you will not have an answer.

Testing. Three tests carry the weight of the whole adoption:

  • Parallel debit: N concurrent debits of the same wallet, assert no overdraft and no lost update. If this fails, the package is a prototype only.
  • Webhook replay: fire the same signed payload twice, assert exactly one credit.
  • Partial failure: force an exception after the balance update but before the transaction record (or vice versa), assert the rollback leaves both consistent.

Everything else is CRUD.

Gotchas and Tips

Balance column vs. ledger — the question everything hinges on. If balance is a mutable column updated in place, you have a cache, not a ledger, and you must own reconciliation: a nightly job that sums transactions and compares to the stored balance, alerting on drift. If balances are derived from an append-only transaction table, you have something closer to auditable, but reads become aggregations and you will want an index on (wallet_id, created_at). Read the migrations and know which one you have. The description claims "atomic transactions" but that is a claim about the happy path until you test concurrency.

Precision will bite you. Check the migration column type. float or double anywhere in the balance path is a defect — money must be decimal or integer minor units. If it is decimal, set the scale explicitly and cast to string in your models, never to float. Rounding policy on transfers and partial withdrawals needs to be an explicit, documented decision, not an emergent one.

Do not read the balance outside a lock for any decision that writes. A read-then-write sequence without lockForUpdate will double-spend under load. The failure is silent and rare in testing, which is exactly what makes it dangerous. Lock, re-read, then write.

Pin everything and fork early. Last release May 2024, one maintainer, no dependents. Vendor the package or fork it under App\Vendor\TomatoWallet with a named internal owner and a maintenance budget. Track upstream as a reference branch, not a dependency. The cost of forking on day one is low; the cost of forking during an incident on a release that no longer exists is not.

Filament version drift. A May 2024 release predates current Filament majors. The admin resource may be broken on your stack even if the models work fine. Test the panel before scoping any support-facing features against it — and if it is broken, replacing it with your own small resource over the transaction table is cheap.

Config publishing is easy to get wrong. Publish config before migrations. If you change currency or precision defaults after data exists, you have a migration problem, not a config change. Decide the currency and precision policy in the first hour and treat it as immutable.

Extension points and where to put your code. The description advertises "configurable, extendable transaction types and gates." Locate those classes in src/ and extend via subclass plus container binding rather than editing vendor files — vendor edits disappear on the next composer update, and you will not remember what you changed. If a needed extension point does not exist, add it in your fork with a test, and keep the diff to upstream small and documented so you can rebase.

Debugging a balance discrepancy. Work in this order: (1) sum the transaction table for the wallet, (2) compare to the stored balance column, (3) diff against the payment provider's report for the same window. If (1) and (2) disagree, the mutation path has a bug — look for code paths that update the balance without writing a transaction. If they agree but the provider disagrees, it is an integration or webhook problem. Build this as an artisan command on day one; you will run it more than you expect.

Scope discipline. This is good for prepaid credits, refunds-as-store-credit, retainers drawn down against a balance, and internal floats on low-value cohorts. It is not escrow, not multi-party settlement, not double-entry accounting, and it will not survive a reconciliation or compliance conversation. Draw the line explicitly in the adapter's docblock so the next developer does not quietly extend it past its limits.

The one-line version. Adopt it behind a feature flag and your own interface, prove it with a parallel-debit test, reconcile daily, and assume you will replace the ledger layer — but keep the interface, because that is the part worth keeping.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle