tomatophp/tomato-payments
tomatophp/tomato-payments is a Laravel package for adding payment management to TomatoPHP apps. It provides gateway integration and transaction tracking so you can accept, record, and manage payments through a consistent, configurable workflow.
Read composer.json before you run composer require. This package's last release is 2023-08-10, it has one star and zero dependents. The require block tells you immediately whether it will even install on your Laravel/PHP version. If it caps laravel/framework at 9 or 10 and you're on 11+, stop and reconsider — payments is not a place to run --ignore-platform-reqs.
composer require tomatophp/tomato-payments
php artisan about # confirm the provider registered
php artisan vendor:publish --list # see what tags exist before publishing
Publish config and migrations as separate steps, and read the migration files before running them:
php artisan vendor:publish --provider="TomatoPHP\TomatoPayments\TomatoPaymentsServiceProvider" --tag=config
php artisan vendor:publish --provider="TomatoPHP\TomatoPayments\TomatoPaymentsServiceProvider" --tag=migrations
Verify the provider FQN from composer.json → extra.laravel.providers rather than copying it from a README.
Where to look, in this order:
src/TomatoPaymentsServiceProvider.php — bindings, config merge, how drivers get registered.First use case: one sandbox charge, outside your app. Pick a single gateway, put sandbox credentials in .env, and drive a charge end to end — create → confirm/redirect → verify → record — in php artisan tinker or a throwaway command. Do not wire the facade into controllers yet. You are testing whether a 2023 driver still speaks a 2025 provider API. That is the whole risk, and it takes about twenty minutes to find out.
Put your own interface in front of it. Non-negotiable here.
interface PaymentGateway
{
public function charge(Order $order, array $payload): PaymentResult;
public function verify(string $reference): PaymentResult;
public function refund(string $reference, ?int $amount = null): PaymentResult;
}
Implement it with a thin class that delegates to the package facade. This is what lets you (a) swap in provider SDKs directly when the package stalls, (b) stub payments in tests, (c) absorb the likely mixed/array returns from an older codebase behind a typed result. Everything in your app depends on PaymentGateway, never on TomatoPayments::.
Resolve the driver per call site, not globally. The whole point of 11 gateways is per-context selection — cards via one provider, wallets via another, a regional method for one market. If the facade only exposes the configured default, look in the source for a driver()/gateway() accessor. If none exists, you need a resolver that rebinds config or the container per request. Ugly, but contain it in one class.
Let the package own its tables; own your join keys. The published migrations create payment/transaction rows — that's fine, let them. But add nullable provider and provider_reference columns to your orders table (or a small payment_attempts pivot of your own). The package's models are not your domain model. When the package is eventually removed, backfilling from your own columns is cheap; reconstructing from a dropped table is not.
Write your own webhook controller. Even if the package ships a callback route, you want: signature verification, raw payload written to a log table first, idempotent order update keyed on the provider's event ID, then optionally call the package's verify. Providers retry — idempotency is not optional, and the package's callback shape is the thing most likely to be stale.
Fake the driver in tests. Either register a null/fake driver in config or bind a stub PaymentGateway in TestCase::setUp(). Never let CI reach a real provider — flaky outbound calls will get the package blamed and removed for the wrong reason.
Use events as the seam. The description claims Laravel-native events. Listen for them to fire emails, invoices, and analytics rather than bolting that into the charge flow. If the event list turns out thin, dispatch your own from your wrapper — keep the seam in your code.
Day-to-day admin work: if you're in the TomatoPHP panel ecosystem, the real productivity win is the panel integration — settings screens for credentials, a transactions resource for lookup and refunds. That's where this package saves hours compared to hand-rolling admin views over Stripe objects.
1.00 and confirm what the provider dashboard shows. A silent ×100 is the most expensive bug in this category.migrate. Better: edit them before first run to add indexes on the columns you'll query — order ID, provider transaction ID, status. Adding indexes later on a payments table is a maintenance window."tomatophp/tomato-payments": "1.0.*" or the exact tag, plus a note in your dependency review: date, version, Laravel/PHP it was verified against. With zero dependents there is no community pressure to fix anything.stripe/stripe-php (or similar) in its own require, that version is dictated by the package. A stale SDK is a security-review finding; overriding it at root level is fragile.env() only. The config file gets committed. Confirm every credential in the config reads from env(). If a driver reads a key the config doesn't expose, patch the config — it's your file now.mixed. Assert inside your adapter (if (! $result instanceof PaymentResult) throw ...) so a shape change surfaces in one place instead of in production.config('payments.drivers') map of name → class. Registering your own driver lets you add or fix a gateway without forking. Prefer this over composer patch.PaymentGateway interface — which is exactly why that interface exists. The package should be a swappable detail from day one, not a foundation.How can I help you explore Laravel packages today?