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

Require the package with Composer, then publish and run the migrations. The service provider is auto-discovered, and money-for-laravel and package-toolkit-for-laravel come along as dependencies — no manual registration:

composer require roundly-consulting/coupons-for-laravel

php artisan vendor:publish --tag="coupons-migrations"
php artisan migrate

Migrations are publish-only: the package loads none itself, so running migrate before publishing creates nothing. Publishing copies coupons, then coupon_redemptions, into database/migrations under timestamps one second apart, so the referenced table is always created first. Republishing overwrites the same files instead of duplicating them.

Optional publishing

The config file is optional — every key has a default and all but one are env-backed. Publish it only to override:

php artisan vendor:publish --tag="coupons-config"

Publish the translation strings to reword or translate the validation-rule and discount-type messages:

php artisan vendor:publish --tag="coupons-translations"
TagPublishes to
coupons-migrationsdatabase/migrations/<timestamp>_create_coupons_table.php, …_create_coupon_redemptions_table.php
coupons-configconfig/coupons.php
coupons-translationslang/vendor/coupons/

Before you migrate

If your redeemer models — usually User — use UUID or ULID primary keys, set coupons.key_type before running the migration. It decides the type of the polymorphic redeemer column on coupon_redemptions (see Redeemers & per-customer limits).

At a glance

The package adds a Coupons section to Laravel’s about command:

php artisan about --only=coupons
  • Model — the resolved coupon model class.
  • Default currency — coupons.default_currency, or INVALID: … with the reason when it is not a string.
  • Redeemer tracking — ON or OFF.
  • Generated codes — length and alphabet size only, e.g. “6 chars from a 36-symbol alphabet”. The alphabet itself is never printed; an unusable code.* value shows as INVALID: … naming the key instead of failing the command.
  • Route key — the column {coupon} binds by, or INVALID: … with the reason.

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.