Open source
Coupons for Laravel
composer require roundly-consulting/coupons-for-laravelOverview
A complete coupon system for Laravel. Create fixed-amount, percentage and free-shipping coupons with currency locks, minimum spend, activation windows and global or per-customer usage limits, then redeem them with every rule checked and usage counted inside one locked database transaction — a cap of 100 means exactly 100, even under a rush. Every amount is an exact money-for-laravel Money, correct for zero- and three-decimal currencies. MIT-licensed and dependency-light: Laravel, ext-bcmath and two Roundly foundation packages — no third-party runtime dependencies.
What you get
Three discount types
Fixed amounts in minor units, percentages in basis points with an optional cap, and free shipping as a flag your checkout applies to its own shipping line.
Rules you control
Currency lock, minimum spend, activation and expiry windows, a global usage cap and a per-customer cap — with guest checkout supported.
Atomic redemption
Every check and the usage increment run in one transaction against a row read with lockForUpdate, so a cap of 100 means exactly 100.
One eligibility guard
Coupons::check(), redeem(), isRedeemableBy() and the Redeemable rule share one guard — a fixed check order, precise exceptions and translatable messages.
Exact money
Every amount is a money-for-laravel Money — exact for zero- and three-decimal currencies, never below zero, and composable in a DiscountStack.
Events, commands & fake
Five events, an expire kill-switch and a prune command, and Coupons::fake() — a recorder with assertions for every create, redeem, revoke and prune.
Facade, DI or actions
Call the Coupons facade, inject CouponManager for the same API, or run single-purpose action classes in your own jobs — one code path behind all three.
Documentation
Installation
Install via Composer, publish and run the two migrations, and optionally publish the config and the translation strings.
Configuration
Every config key, its default and env variable — coupon model, redeemer key type, default currency, tracking, code format and route key.
Money, units & currency lock
How a coupon’s value is stored — minor units or basis points — and when a coupon must be locked to a single currency.
The Coupons facade
One entry point for everything — create, look up, check, preview, redeem, revoke, expire and prune coupons, flat or through a Coupons::code() handle.
DI and actions
Inject CouponManager instead of calling the facade, or run the single-purpose actions yourself — plus the full facade method to action map.
Creating coupons
Create fixed, percentage and free-shipping coupons with CreateCouponData — named constructors, caps, minimum spend and validation rules.
Generated codes
Control the length and alphabet of auto-generated codes — drawn from a CSPRNG, case-insensitive like every code, validated on every use.
Redeeming coupons
Validate and redeem in one atomic step — the locked transaction, the fixed check order, RedemptionResult and an exception for every failure.
The Coupon model
Lifecycle mutators, state checks, casts and discount math on the Coupon model — activate, expire, cap, apply and discountFor.
Display helpers
Non-throwing helpers for views and APIs — remaining uses, usage percentage, can-this-customer-redeem and a safe discount preview.
Redeemers & per-customer limits
Add HasCoupons to your User model, cap uses per customer, read redemption history and support UUID or ULID redeemer keys.
Validation rule
Validate a promo-code field with the Redeemable rule — the same checks as redemption and a translatable message for the first failing reason.
Discount types & composing
The DiscountType enum — labels for admin UIs, conversion to a money Discount, and combining coupons with other discounts in a DiscountStack.
Query scopes & route binding
Find active, expired, exhausted and redeemable coupons with query scopes, and bind {coupon} route parameters by code or by id.
Events
Five events for analytics, fraud checks and sold-out notices — a coupon created, redeemed, rejected, exhausted or revoked.
Artisan commands
An admin kill-switch that expires coupons immediately, and a prune command that archives or deletes long-expired coupons.
Exceptions
One hierarchy under CouponException — catch the whole package, every “cannot redeem” reason, or each cause individually.
Testing
Coupons::fake() records creations, redemptions, refusals, revocations and prunes without writing a row — plus factory states for database tests.
Extending
Swap in your own Coupon subclass via coupons.model and reuse the eligibility guard in custom checkout flows.
Database schema
The coupons and coupon_redemptions tables column by column — types, defaults, the shared currency column and soft deletes.
Requirements
PHP 8.4+ with ext-bcmath, Laravel 12 or 13, MySQL/MariaDB, PostgreSQL or SQLite, and two Roundly packages Composer installs for you.
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 cryptoBy 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.