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 Coupons Module Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're actually installing. tomatophp/tomato-coupons-module is a Filament-based admin module that gives you coupon CRUD (code, discount type, amount, expiry, usage limits, active flag) inside a TomatoPHP admin panel. It is not a checkout engine — it persists and manages coupons; you still own validation and application logic in your cart/order flow. Set expectations accordingly before you pitch this internally.

Minimal install:

composer require tomatophp/tomato-coupons-module
php artisan vendor:publish --tag=tomato-coupons-config
php artisan vendor:publish --tag=tomato-coupons-migrations
php artisan migrate
php artisan optimize:clear

Tag names vary by release; if a tag fails, run php artisan vendor:publish interactively and pick the provider. That interactive list is also the fastest way to discover every publishable asset the package ships.

Where to look first, in this order:

  1. composer.json — the Filament major constraint. This is the single most likely thing to block you; check it before composer require, not after.
  2. src/Models/Coupon.php — code, type, amount, expires_at, usage counters. The model is the contract; the admin UI is just a view over it.
  3. Migrations — read the columns and indexes directly. Casts in the model hide whether amount is a decimal or an integer in minor units, and that detail silently causes 100× discount bugs.
  4. The Filament resource under src/Filament/ (or src/TomatoCoupons/) — the form schema is the most reliable documentation of which fields the package genuinely supports, since the README is thin.
  5. config/tomato-coupons.php — table names and model bindings, so you can point at your own models if you need to.

First use case (do exactly this before anything else): create one fixed-amount coupon in the admin panel, add a coupon field to your checkout, validate the code server-side, apply the discount as a cart adjustment, and write a usage row on order completion. That single path exercises persistence, the admin UI, your validation layer, and the usage ledger — every part of the package you will actually depend on. Percentage coupons, stacking, and per-product eligibility come after that path is green in a test.

Honest caveat on maturity: zero stars, no dependents, and a last release (2024-03-16) that was a translation-only patch. Budget time to read the source rather than the docs, and treat the package as a starting schema plus admin UI that you will wrap heavily — not as a maintained promotions engine.

Implementation Patterns

Wrap validation in your own service, not in the model. The package gives you persistence; it does not give you a checkout policy. Keep one class — CouponService::validate(string $code, Cart $cart): CouponResult — that checks existence, is_active, date window, global usage cap, per-user cap, minimum order total, and eligibility. Return a value object, not a boolean, so the storefront can render why a code was rejected ("expired", "already used", "minimum not met"). This is the single highest-value abstraction you can add: it insulates your checkout from any internal change the package makes.

Apply the discount as a cart adjustment, never as a total overwrite. Persist both the coupon code and the computed discount amount on the order, then recompute the total from line items. If you mutate total directly, refunds and partial cancellations become unrecoverable, and you lose the audit trail of what the coupon was worth at purchase time — which matters because an admin can edit the coupon's amount tomorrow.

Record usage on order completion, inside a transaction, not on validation. Validating a code must never burn a use — users validate, back out, and retry constantly. Write the usage row in the same DB transaction that marks the order paid, and lockForUpdate() the coupon row (or use an atomic increment guarded by a where clause on remaining uses) so two concurrent checkouts can't both claim the last redemption of a limited coupon.

Don't edit vendor files for admin extensions. If you need extra fields ("restrict to logged-in users", "one per household"), subclass the Filament resource or register a form/table schema extension from a service provider. vendor/ changes vanish on the next composer update, and you will be running updates because the dependency graph is young.

Translate at the app level. Publish the lang files and override keys under lang/vendor/tomato-coupons/ rather than editing the package. The v1.0.1 release itself was a translation refresh — upstream translation churn is real here, so keep your overrides in a separate layer that merges cleanly.

Expose a thin API endpoint that reuses the same service. POST /api/coupons/validate → CouponService. One implementation means web checkout, admin panel preview, and mobile app can never disagree about what a valid coupon is.

Seed and test against a fixture coupon. A single CouponTestSeeder with a known 10%-off coupon and a known fixed-amount coupon gives you a regression baseline for money representation, rounding, and expiry arithmetic. Without it, every one of those bugs ships silently.

Gotchas and Tips

  • The v1.0.1 release is translation-only. If a bug appears "after upgrading to 1.0.1," it is not the upgrade. Equally, don't wait on a release for a behavioural fix — there is nothing in the pipeline here, and the last commit is from March 2024.
  • Migrations are publish-based, not auto-loaded. A fresh clone or a new CI environment that skips vendor:publish fails at runtime with "table not found," which reads like a model or config bug. Put the publish step in your deploy script and onboarding docs.
  • Money representation is the top silent-corruption risk. Verify whether amount is a decimal, a float, or integer minor units. If the package stores decimal and you compute in cents (or vice versa), you ship a 100× discount to production. Write an assertion test on a known coupon on day one.
  • Percentage rounding direction. Decide explicitly whether you round the discount or the resulting total, and whether a cap applies before or after rounding. Encode the decision in the service, and test at a boundary like 33% of 9.99.
  • Timezone on expires_at. Compare in a single timezone, cast the column to datetime, and consider treating expiry as end-of-day in the store's timezone rather than a precise UTC instant. Month- and midnight-boundary bugs here are near-universal.
  • is_active versus hard delete. If a coupon has been redeemed, deleting it can orphan usage rows or null out the order's coupon reference. Prefer deactivation; if you must delete, make the FK nullable or use restrictOnDelete and handle the exception in the UI.
  • Filament major-version coupling is the upgrade blocker, not coupon logic. When you bump anything in the TomatoPHP stack, check the Filament constraint in composer.json first, and do it in a branch. A Filament major bump breaks your resource customisations, not the coupon tables.
  • vendor:publish --force clobbers your overrides. After a translation refresh, diff the published lang files before forcing — otherwise your custom copy silently reverts to upstream.
  • Debugging checklist, in order: php artisan about (resolved Filament/TomatoPHP versions), php artisan config:show tomato-coupons (effective config), php artisan route:list | grep coupon (routes registered). Most "the module isn't working" reports resolve to a stale config cache or a skipped publish step.
  • Extension point worth knowing: config-level model bindings. If the package lets you swap the Coupon model, subclass it rather than forking — that is the cleanest seam for adding tenant scoping (BelongsToTenant) or your own casts without touching vendor code.
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.
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
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource