tomatophp/tomato-coupons
A Laravel package for managing coupons and discount codes, letting you create, apply, and track promotional offers with flexible rules, expiration dates, and usage limits. Integrates smoothly with TomatoPHP and Filament admin panels or custom storefronts.
Architecture fit. Tomato Coupons is a Laravel package that sits inside the TomatoPHP/Filament ecosystem. Architecturally it is a thin back-office CRUD layer over two domain objects — Coupon and GiftCard — with publishable config and migrations, Eloquent models with polymorphic relations, and ready-made Filament resources for admin management. That is a definition store, not a promotions engine. The distinction matters for a TPM: the package owns the admin surface and the schema shape, but it does not own discount math, eligibility, stacking, minimum-spend checks, per-customer limit enforcement at the database level, or tax handling. Any roadmap that assumes "install package → ship promotions" is wrong by 2–3 weeks of engineering. The right architectural framing is: this package replaces the work you'd otherwise spend designing a coupon table and building admin screens, and nothing else. Put it behind your own CouponValidator interface so checkout depends on your code, never on the vendor model or the Filament resource directly — that single decision preserves the option to fork, replace, or drop the package later without touching checkout.
Integration feasibility. Two viable paths, both straightforward on paper. On a full TomatoPHP app, php artisan tomatophp:install coupons registers plugin, config, and migrations together. On plain Laravel without a Filament panel, publish config and migrations manually and migrate — the schema is portable, but the value collapses to "a coupon table you didn't have to design," which is roughly a day of work. The genuine feasibility question is not "can we install it" but "does our checkout need semantics the package doesn't provide," and it always does. Concurrency is the sharpest edge: two simultaneous redemptions of a single-use coupon, or parallel gift card debits, will both silently succeed without lockForUpdate() or a conditional update. Feasibility therefore depends on your team's willingness to own an append-only redemption ledger and a gift card transaction ledger from day one. Both are a few days of work, but they are your work, not the package's.
Technical risk. Elevated and mostly non-technical in origin. The package has 2 stars, 0 dependents, a last release of May 2024, and is MIT-licensed with community-only support — GitHub issues and the TomatoPHP Discord, no SLA. The dependency risk is concrete: TomatoPHP ships loose Composer constraints, so a later composer update can pull an untested Filament or Laravel minor and break the admin resource. Pin tomatophp/tomato-coupons:1.0.4 in the same commit you review it, and add a CI matrix job against your target Laravel/Filament versions so a Filament major shows up as a red build rather than a production incident. Secondary risks: unvalidated currency comparison on gift cards (assume it does not compare gift card currency to cart currency and validate yourself), timezone handling on active_from/active_to (store and compare in UTC, never evaluate client-side), and cascade deletes on redemption rows silently freeing up usage — prefer restrict or soft-delete and derive counters from the ledger. Gift cards as a financially material product carry liability, expiration, breakage, and possibly regulatory questions that no MIT package resolves.
Key questions. Before approval: (1) Are we on a TomatoPHP/Filament admin today, or would we be adopting the ecosystem to get one package? If the latter, skip it — write the tables yourself. (2) Is v1 a simple admin-managed code list, or a rules-based promotion engine? Only the former is served here. (3) Do gift cards ship in v1? If yes, finance and legal must be in the room before scoping, because balance liability and breakage are product decisions, not admin screens. (4) Who owns the redemption ledger, the gift card transaction ledger, and the concurrency tests — and is that 2–3 weeks in the current plan? (5) What is our fork policy: config class swaps first, fork only when the fix lives inside a resource or migration, and keep the fork on one branch with an upstream remote. (6) What is our exit criterion — under what conditions do we abandon the package and own the code outright?
Stack fit. High fit if and only if you are already on Laravel with Filament and preferably TomatoPHP conventions. The package's admin value is entirely Filament-shaped: the resource classes are inseparable from the Filament UI, so a headless, API-only, or Inertia-first front end gets the schema and models but none of the CRUD payoff. Keep checkout, API, and any non-Filament surface on the models through your validator interface — never on the resource classes — so a headless path stays open without rewriting the integration. If you are not on Filament, the package is a reference implementation and a schema sketch, not a dependency.
Migration path. Publish migrations and config into the app, run them, then immediately add your own adjacent tables: coupon_redemptions with a unique index on (coupon_id, customer_id, order_id) and an append-only gift_card_transactions table with balance treated as a cache. Add reporting indexes before volume arrives — unique on code, composite on (is_active, active_from, active_to) for the "currently valid" lookup, and timestamps on redemptions. Read five files in order before writing any integration code: config, migrations, Coupon/GiftCard models, the Filament resources (the most reliable documentation of which fields and validation rules are actually enforced), and the service provider. That hour tells you whether the package meets requirements; everything after is code you would write anyway.
Compatibility. Pin the exact version reviewed and re-verify on every Filament or Laravel minor. Prefer overriding model bindings in config/tomato-coupons.php over patching vendor/ — that is the designed extension surface. Fork only when the fix lives inside a resource or migration, and keep the fork small and rebaseable. Publish tags must match config('tomato-coupons.migrations_tag') exactly, which is a common first-run failure.
Sequencing. Phase the rollout to match the product decision, not the package's capabilities: (1) admin-only coupon CRUD — create one fixed-amount and one percentage coupon in Filament, confirm persistence via tinker, and stop there until it round-trips cleanly; (2) checkout redemption behind the CouponValidator interface with the redemptions ledger and locking in place; (3) offer eligibility rules — stacking, minimum spend, per-customer limits — all of which are yours to build; (4) gift card ledger with atomic conditional debits and currency validation; (5) reporting, fraud controls, and analytics. Do not compress phases 1 and 2; the first use case is proving CRUD works, not wiring checkout. Write parallel-process tests against a real database before enabling redemption in production.
Maintenance. You are the maintainer. MIT licensing makes forking legitimate, but with 0 dependents and a May 2024 release, upstream is unlikely to move in response to your issues. Budget recurring effort for dependency pinning and re-verification, plus internal documentation: a runbook covering schema ownership, deviations from upstream, and the redemption invariants your tests enforce. That runbook is what protects you when a Filament major lands. Prefer config-level extension over forks so rebasing stays cheap; each fork is a standing maintenance tax.
Support. Community-only: GitHub issues and the TomatoPHP Discord. No SLA, no security-patch commitment, no vendor relationship. For a promotions feature this is tolerable; for gift cards holding real customer money it is not, unless you have independently reviewed authorization, validation, and concurrency handling and are prepared to patch them yourself. Escalate the gift card decision to finance and legal early — balance liability, expiration policy, breakage recognition, and refund/store-credit treatment are compliance questions that outlive any package choice.
Scaling. The schema scales fine; the semantics do not. Redemption and gift card ledgers become reporting hotspots quickly, so index early. High-volume redemption, multi-currency, tax-aware gift cards, and BOGO/tiered/stackable promotion logic are all outside the package — if any of those are on the 12-month roadmap, plan the build-versus-fork decision now rather than discovering it mid-campaign. Because there is no discount math or eligibility engine, "scaling" here mostly means scaling your wrapper: the validator, the ledger, and the concurrency guarantees.
Failure modes. Ranked by likelihood and blast radius: (1) a Composer update pulls an untested Filament/Laravel minor and breaks admin — mitigated by pinning plus a CI matrix; (2) concurrent redemptions double-spend a single-use coupon or overdraw a gift card — mitigated by lockForUpdate() or conditional updates and parallel-process tests; (3) timezone skew honors an expired coupon — mitigated by UTC storage and server-side evaluation only; (4) currency mismatch on gift cards goes unvalidated — assume it does and check yourself; (5) cascade deletes on redemption rows silently restore usage — prefer restrict or soft-delete and derive counters from the ledger; (6) silent coupon rejection with no vendor feedback — log $coupon->toArray() and your validator's rejection reason separately, because the failure is almost always in the wrapper, not the package. Also note counter-versus-ledger drift: when uses and the redemptions table disagree, trust the redemptions table and treat the counter as a denormalized convenience.
Ramp-up. Low for a Laravel developer already fluent in Filament — the package is small and readable, and the five-file review is an hour. The real ramp is domain ramp: whoever owns checkout must internalize that the package stores definitions and enforces nothing, and must own the transaction boundaries around redemption. Budget 2–3 weeks of engineering for production-grade coupons plus gift cards, and staff it with someone who has written concurrency tests before. If you cannot commit that time, the honest recommendation is to skip the package and implement in-house — the schema is portable and thin, and owning the money math from day one is cheaper than inheriting it later.
How can I help you explore Laravel packages today?