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

Open source

Coupons for Laravel

Install
composer require roundly-consulting/coupons-for-laravel
Requires: PHP ^8.4 · Laravel ^12.0|^13.0

Overview

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 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.