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

Display helpers

For views and APIs, read coupon state without manual math or try/catch. Each helper is safe to call anywhere:

$coupon->remainingUsage();              // ?int   — e.g. 37; null when unlimited (max_usage <= 0)
$coupon->remainingUsageFor($user);      // ?int   — null when untracked or per-redeemer unlimited
$coupon->usagePercentage();             // ?float — e.g. 92.6 (0..100); null when unlimited
$coupon->isRedeemableBy($user, $cart);  // bool   — pass a price to also check currency + minimum spend
$coupon->previewDiscount($cart);        // Money  — zero on a currency mismatch
MethodReturns
remainingUsage(): ?intUses left under the global cap; null when unlimited.
remainingUsageFor(Model $redeemer): ?intUses left for that redeemer; null when tracking is off or the per-redeemer cap is 0.
usagePercentage(): ?floatShare of the global cap used, 0..100, rounded to 2 decimals; null when unlimited.
isRedeemableBy(?Model $redeemer = null, ?Money $price = null): boolThe same checks as redemption; pass a price to also check currency and minimum spend.
previewDiscount(Money $price): MoneyLike discountFor(), but zero on a currency mismatch — safe in views.

isRedeemableBy() asks Coupons::check() — the same checks, in the same order, as redeem() and the Redeemable rule — so they never drift apart: a soft-deleted Coupon instance, for one, is NotFound for all of them, just as redeem() throws CouponNotFound. Use Coupons::check() itself when you need the reason, not just a yes or no; Coupons::code($code)->preview($cart) is the facade form of previewDiscount() when you only have a code.

Example: a coupon preview endpoint

use RoundlyConsulting\Coupons\Facades\Coupons;

$coupon = Coupons::findOrFail((string) $request->input('code'));

return [
    'code' => $coupon->code,
    'redeemable' => $coupon->isRedeemableBy($request->user(), $cartTotal),
    'you_save' => (string) $coupon->previewDiscount($cartTotal),   // e.g. "4.50 EUR"
    'uses_left' => $coupon->remainingUsage(),
    'free_shipping' => $coupon->appliesToShipping(),
];

One edge case: a Fixed row written around CreateCouponAction without a currency can’t be priced at all, so previewDiscount() still throws InvalidCouponDefinition for it.

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.