NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

A custom coupon model

Subclass Coupon and point coupons.model at it. Every manager, action and rule query, both Artisan commands and a redemption’s coupon relation go through it:

namespace App\Models;

use RoundlyConsulting\Coupons\Models\Coupon;

class ShopCoupon extends Coupon
{
    protected $table = 'coupons';

    // your own relations, scopes, accessors and model events
}
// config/coupons.php
'model' => App\Models\ShopCoupon::class,
  • Set $table = 'coupons' when your class name differs — Eloquent would otherwise derive the table from the class name. The redemptions relation keys on coupon_id explicitly, so any class name works.
  • A value that isn’t Coupon or a subclass of it throws the toolkit’s InvalidConfigurationException naming the key — it is never silently replaced by the packaged model.
  • CouponRedemption is final and isn’t swappable.

Reusing the eligibility guard

For most flows Coupons::check() is enough — it returns the first failing reason through the facade, and the fake sees it. Underneath, RedemptionGuard is the single source of truth for eligibility. firstFailure() returns the first failing RedemptionFailureReason — or null — without throwing; a soft-deleted coupon reports NotFound first, and a null price skips the currency and minimum-spend checks. throwFor() raises the matching exception, CouponNotFound included:

use RoundlyConsulting\Coupons\Support\RedemptionGuard;

$guard = app(RedemptionGuard::class);

$reason = $guard->firstFailure($coupon, $cartTotal, $user);   // ?RedemptionFailureReason — never throws

if ($reason !== null) {
    $message = __($reason->translationKey(), ['code' => $coupon->code]);   // customer-facing text

    $guard->throwFor($coupon, $reason, $cartTotal);                       // or raise the matching exception
}

Other hooks

  • Events — analytics, fraud checks, sold-out notices and audit logs on the five events.
  • Translations — publish coupons-translations to reword or translate messages and labels.
  • UUID or ULID redeemers — set coupons.key_type before migrating.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.