tomatophp/tomato-coupons-module
A Laravel module that adds coupon and discount code management to TomatoPHP apps, letting you create, apply, and validate promo codes on orders with a ready-made admin interface and database migrations.
Architecture fit. tomatophp/tomato-coupons-module is a Composer-installed Laravel package that assumes the host application already runs the TomatoPHP admin stack (Filament-based). It is a feature module inside an admin ecosystem, not a standalone promotions service. That makes fit binary rather than gradient: if the product is a Laravel app on the TomatoPHP/Filament admin stack, it slots in with near-zero architectural friction; if the product is on anything else — a headless storefront, Shopify/custom commerce, a Next.js front end with a non-Laravel backend, or a Laravel app that does not use Filament — the package is effectively not adoptable, and the real decision becomes build-in-house versus a hosted promotions/discount service. The TPM-level framing: this is a time-to-market accelerator for coupon CRUD, not a discount engine. Discount calculation, stacking policy, eligibility rules, redemption accounting, and refund reversal all remain our code.
Integration feasibility. Mechanically low effort: composer require, vendor publish of config/migrations/lang, php artisan migrate, and an admin menu entry. Expected effort for a first end-to-end slice (create a fixed-amount coupon, validate at checkout, apply as a cart adjustment, record usage on payment) is on the order of days for a Laravel developer familiar with Filament, provided the environment checks above pass. The real integration work is not the package — it is the surrounding policy layer we must own regardless (see Integration Approach).
Technical risk. This is where the assessment is uncomfortable and the reason the package scores 0.33:
Key questions.
Stack fit and gating. Gate the decision on two checks before any engineering: (a) host app is Laravel with the TomatoPHP admin ecosystem and a Filament major matching the module's composer.json constraint; (b) PHP and database versions fall inside that constraint set. Both are cheap to verify and both are disqualifying if they fail. Because the package is Composer + service-provider auto-discovery, there is no sidecar, no new infrastructure, no additional runtime — the operational footprint is one library and a handful of tables.
Migration path. Assume dormancy, plan for ownership. The path is: install into a staging app, publish config/migrations/lang, run migrations against a production-like schema copy, seed admin permissions, smoke-test admin CRUD and both discount types, then integrate checkout. Two non-negotiables: (1) vendor-fork on adoption — pin the exact version in composer.json, mirror the repository internally, and treat upstream as a source of patches rather than a dependency; (2) extend, never edit — Filament resource customizations and lang overrides happen in our app or a thin internal package, so a future fork-and-patch stays cheap. Re-publishing lang with --force will overwrite overrides; publish to a distinct path or diff before forcing.
Compatibility. The v1.0.1 delta from v1.0.0 is translation-only and requires no code changes, so there is no upgrade sequencing burden from that release. Compatibility risk is concentrated in three places: Filament/TomatoPHP major constraints (the realistic break event), database engine differences in the published migrations, and any cache/session-driven behavior in validation. Treat the Filament major as a locked platform decision for the lifetime of this integration — document it in the upgrade runbook so a future platform bump triggers a planned coupons spike rather than an incident.
Sequencing. Staged, each stage independently shippable or abortable:
CouponService as the single validation authority — exists, active, date window, global cap, per-user cap, minimum order, eligibility — returning a value object with a rejection reason, not a boolean. Same service backs admin, web checkout, and any API route so the three can never drift.expires_at to a single timezone and prefer end-of-day expiry. Do this before any percentage coupon is enabled in production.lockForUpdate() or an atomic increment on the coupon row. Apply discounts as an order-level adjustment (storing code + computed amount) so refunds, partial cancellations, and later edits to the coupon are recoverable and auditable.Deliberately out of scope for v1: gift-card balance semantics and partial redemption unless the admin resource proves support, and stacking unless the business case is signed off.
Maintenance. Low run-rate, moderate ownership burden. Nobody upstream is doing the maintenance, so the internal cost is "we own a small fork" rather than "we consume a library." Budget: an initial hardening sprint (the CouponService, money/time/concurrency decisions, tests) plus a recurring contingency whenever Filament or Laravel majors move. Ongoing patch traffic from upstream should be assumed to be zero; do not plan capacity for consuming releases.
Support. Support load is customer-facing, not developer-facing. Expect the bulk of tickets to be "my code didn't work" — which means the rejection-reason value object from CouponService is an operational investment, not just code hygiene: it turns support conversations into readable causes (expired, already used, minimum not met, inactive). Product and support need a short runbook covering creating, expiring, deactivating, auditing, and troubleshooting a coupon, plus the explicit rule that used coupons are deactivated rather than deleted (hard deletes orphan usage rows and can null out order references).
Scaling. Scale is a property of our checkout design, not the package. Before any high-volume promotion: index the code lookup and the usage table by coupon and user; ensure limited-quantity coupons cannot oversell under concurrent checkout (the lock/increment pattern above); confirm admin list queries stay paginated and indexed as the coupon table grows; verify cache invalidation behaves when a coupon is deactivated mid-campaign. A single flash-sale coupon with a hard usage cap is the highest-risk scaling scenario and should be load-tested explicitly — it is also the scenario where an unmaintained package's lack of test evidence is most likely to surface.
Failure modes. Ranked by operational severity:
vendor:publish fails at runtime with "table not found," which reads like a model bug. Put the publish step in the deploy script and onboarding doc, and treat stale config cache (optimize:clear) as the first diagnostic.Ramp-up. Laravel developers onboard in hours; the package's surface is small and the admin UI is the clearest documentation of what fields are actually supported. The ramp-up cost that matters is organizational: a named owner for the internal fork, a documented platform-version lock (Filament major, Laravel major, PHP), a deployed runbook for support, and finance alignment if gift cards are enabled (unredeemed-balance liability is an accounting question, not a feature flag). Recommend a decision checkpoint around the six-month mark: if promotions have become revenue-relevant and the internal fork is carrying meaningful patches, promote it to a first-class internal module with a real test suite and a roadmap, rather than continuing to describe it as "a package we installed."
How can I help you explore Laravel packages today?