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

Ti Ext Coupons Laravel Package

tastyigniter/ti-ext-coupons

TastyIgniter Coupons extension for adding fixed amount or percentage discounts, advanced usage restrictions, location-based and time-sensitive promotions to boost restaurant sales.

View on GitHub
Deep Wiki
Context7

Getting Started

Requires a working TastyIgniter install (v4.x; v4.2.0 of the extension pins tastyigniter/core to ^v4.4 — check your lockfile before upgrading). This is not a general-purpose Laravel package: it registers models, admin UI, cart/checkout hooks, and API resources through TastyIgniter's extension system. Do not try to install it into a plain Laravel app.

composer require tastyigniter/ti-ext-coupons
php artisan igniter:up        # runs extension migrations + publishes assets
php artisan migrate

Then enable it in Admin → System → Extensions (or php artisan igniter:extension:enable TastyIgniter.Coupons). The menu entry appears under Marketing / Promotions → Coupons.

Read these files first, in this order:

  1. database/migrations/*coupons* and *coupon_history* — the real schema is the source of truth for fields like validity, order_restriction, is_limited_to_cart_item, auto_apply, and the redemption counters. Faster than guessing from the README.
  2. src/Models/Coupon.php — especially isValid() / the validation scope methods and the menus / categories / locations relations. This is the entire business logic surface.
  3. The admin form config (src/Controllers/Coupons.php + models/coupon/fields.yaml or equivalent) — it shows which restrictions are actually wired vs. merely documented.
  4. src/CartConditions/ (or wherever the coupon registers as a cart condition) — how the discount lands on the order total.
  5. routes/ or src/Api/ — confirm the coupons:read / coupons:write abilities before you promise an integration.

First use case to build end-to-end: a percentage coupon with a minimum order total, limited to one collection location, with usage_limit_per_customer = 1. Create it in the admin, place a test order through the storefront, then verify the row in coupon_history. That single loop exercises validation, cart condition application, and the redemption ledger — the three things that will bite you later.

Seeding for dev: you can create coupons programmatically, but the model has many relation-based attributes; build a small factory in database/seeders using the model's create() with locations, menus, categories synced afterwards rather than mass-assigning arrays. Keep the seeder in your app, not in the vendor directory.

Implementation Patterns

Programmatic coupon creation. Treat the admin form fields as the contract:

$coupon = Coupon::create([
    'name'                 => 'First Order 10%',
    'code'                 => 'WELCOME10',
    'type'                 => 'P',          // fixed 'F' / percentage 'P' — verify constants on the model
    'discount'             => 10,
    'minimum_total'        => 25,
    'validity'             => 'period',
    'valid_from'           => now(),
    'valid_to'             => now()->addMonth(),
    'order_restriction'    => 'delivery',   // or 'collection'
    'auto_apply'           => false,
    'status'               => true,
]);

$coupon->locations()->sync([$locationId]);

Confirm type, order_restriction, and validity literals against the model constants/migration enum before shipping a seeder — the README does not document them.

Validation as a single gate. Route every UI and API check through one place — Coupon::isValid() (or the service that wraps it) — rather than reimplementing minimum-total / time-window logic in your controller. When support reports "the code doesn't work," you want exactly one function to add logging to. Wrap it:

$result = $coupon->isValid($cart); // returns bool or a reason depending on version
if (! $result) {
    Log::info('coupon.rejected', ['code' => $coupon->code, 'cart' => $cart->id]);
}

Redemption lifecycle — respect the two-phase flow. Since v1.10.3/1.10.4 the extension logs the coupon before payment and marks it redeemed after payment succeeds. If you add a custom payment gateway or an off-site checkout, replicate that ordering; otherwise abandoned payments burn through usage_limit_per_customer. Any custom code that writes coupon_history must be idempotent per order.

Auto-apply as a checkout condition. With auto_apply = true, the coupon attaches without a code. That is the "discount already applied" UX. Note that auto-apply and manual codes can collide — decide in your own code whether a manual code overrides the auto-applied one, and clear the previous condition explicitly when it does.

Location scoping for multi-site. validity is per-coupon, but locations is a pivot. For franchise rollouts, create one coupon per location (or a shared coupon synced to a set of locations) and name them with a predictable prefix (LAUNCH-LONDON-10) so marketing can filter in the admin list. The v4.1.3 change moved this to the Location class directly — if you have an older fork/override reading a serialized blob, adapt it.

Back-office/API integration. Use the JSON:API endpoints for a marketing dashboard only after issuing a token scoped to coupons:read / coupons:write. Do the read-only dashboard first; write access from an external tool is where you'll discover the validation gaps. Beware created_at showing as -0001-11-30 00:00:00 in documented examples — normalize/guard timestamps in your transformers rather than surfacing them to users.

Reporting. Build finance/support views on the coupon_history table, not on the coupon's counter column. The history carries coupon, order, customer, amount, and timestamp — that's your audit trail. Join to orders and sum discounts to reconcile against till totals.

Testing. Write contract tests around the validation path with a stubbed cart: valid window, expired window, below minimum, wrong day/time, delivery vs collection, restricted customer, exhausted usage. These are cheap to write and are the only thing protecting you on a core upgrade, since upstream triage routes through the TastyIgniter core tracker.

Gotchas and Tips

  • redemptions: 0 almost certainly means "unlimited," but verify it against the validation code before you build any reporting or enforcement on top. Same for null date columns — the docs show sentinel dates (-0001-11-30), which are usually the zero-value of the date type, not real data.
  • Pin the version. The changelog records meaningful bug fixes inside the validation path: delivery-only condition logic (4.1.4), customer-restricted coupons not redeemable (4.1.1), coupon menus/categories not saving (4.0.6), foreign-key breakage in checkout when writing coupon history (1.8.2). Running an old minor means shipping a known bug in checkout.
  • Migration index names: 4.0.4 renamed indexes to work around MySQL's identifier length limit. If you fork or backport, keep those names short.
  • Currency decimals: 4.2.0 switched discount and minimum-total validation to a currency-aware decimal rule. Test with a 0-decimal currency (JPY) and a 3-decimal one (KWD) if you operate multi-currency; percentage rounding on 2-decimal currencies can produce 1-cent drift versus your tax engine.
  • Stacking is not solved. The documented model is one-coupon-per-order shaped. If product wants coupon + loyalty points + free-delivery threshold, that's your code, and you should decide priority order before two conditions each compute a discount off the original subtotal.
  • Tax interaction is not documented. Whether a discount reduces the taxable subtotal or is applied after tax is a business decision and a code-path question. Test it explicitly for both percentage and fixed amounts, and again on the delivery fee (a discount target added in 1.11.0).
  • Refunds and partial refunds are uncovered. If an order is refunded, the coupon's redemption count and history row are not automatically rolled back. Add your own reversal handler or your usage caps will silently drift.
  • Time windows and timezone. Recurring day-of-week/time restrictions are evaluated in the app timezone. A multi-region deployment needs those windows interpreted per-location, or your "happy hour" fires at the wrong hour. Verify how config('app.timezone') is consumed in isValid() before trusting it.
  • Delivery-only bug history. Because order_restriction once evaluated incorrectly, add a regression test for both delivery and collection against a collection-only cart, not just a happy path.
  • No fraud controls. Code guessing, rate limiting, and abuse detection are yours. Put a throttle on the coupon-apply endpoint.
  • Vendor risk. ~4 stars, 0 dependents, issues filed against the core TastyIgniter repo, not this one. Assume you are the maintainer of last resort: keep the dependency pinned, isolate your customisation in your own extension classes rather than editing vendor files, and keep a fork branch ready.
  • Extension points that work well: model events (Coupon::saved) for syncing to a CDN-cached promo banner, a custom cart condition that wraps the coupon for extra eligibility rules, and overriding the admin form fields in your own extension to add metadata columns. Editing vendor/tastyigniter/ti-ext-coupons directly is a dead end on the next composer update.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin