PHPackages                             frittenkeez/laravel-vouchers - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. frittenkeez/laravel-vouchers

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

frittenkeez/laravel-vouchers
============================

Voucher system for Laravel 11+

0.8.0(2mo ago)5822.0k↓54%14[1 issues](https://github.com/FrittenKeeZ/laravel-vouchers/issues)[1 PRs](https://github.com/FrittenKeeZ/laravel-vouchers/pulls)2MITPHPPHP ^8.3CI passing

Since Jul 3Pushed 1w ago4 watchersCompare

[ Source](https://github.com/FrittenKeeZ/laravel-vouchers)[ Packagist](https://packagist.org/packages/frittenkeez/laravel-vouchers)[ RSS](/packages/frittenkeez-laravel-vouchers/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (10)Dependencies (25)Versions (33)Used By (2)

Laravel Vouchers
================

[](#laravel-vouchers)

[![Packagist](https://camo.githubusercontent.com/a4c3b52679978e09b182ce2cb13470af1d1effe17ab7afbd1dedbce4d0a8ae03/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f4672697474656e4b65655a2f6c61726176656c2d766f7563686572732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/frittenkeez/laravel-vouchers)[![Downloads](https://camo.githubusercontent.com/ba57b412f3ba1d2039a124a7b708ceb060207211e6da79d569bf90db0d3a3eea/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f4672697474656e4b65655a2f6c61726176656c2d766f7563686572732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/frittenkeez/laravel-vouchers)[![License](https://camo.githubusercontent.com/e36cdfa8240318782e6a3e0b099790cda8d082dffebd2c3a310771b793ebd790/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f4672697474656e4b65655a2f6c61726176656c2d766f7563686572732e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![GitHub Workflow Status](https://camo.githubusercontent.com/21a023ee97a6edac6e18b2dea17ea2198b94c7a92cdad2fc06abdf478aa66283/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f4672697474656e4b65655a2f6c61726176656c2d766f7563686572732f776f726b666c6f772e796d6c3f6272616e63683d6d6173746572)](https://github.com/FrittenKeeZ/laravel-vouchers/actions)

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Upgrading](#upgrading)
- [Changelog](#changelog)
- [Configuration](#configuration)
- [Usage](#usage)
    - [Generate Codes](#generate-codes)
    - [Create Vouchers](#create-vouchers)
    - [Redeem Vouchers](#redeem-vouchers)
    - [Options](#options)
    - [Events](#events)
    - [Traits](#traits)
    - [Helpers](#helpers)
    - [Scopes](#scopes)
- [Testing](#testing)
- [License](#license)

Installation
------------

[](#installation)

Install this package via Composer:

```
composer require frittenkeez/laravel-vouchers
```

Upgrading
---------

[](#upgrading)

Please read the [upgrade guide](UPGRADING.md).

Changelog
---------

[](#changelog)

Please read the [release notes](CHANGELOG.md).

Configuration
-------------

[](#configuration)

Publish config using Artisan command:

```
php artisan vendor:publish --tag=config --provider="FrittenKeeZ\Vouchers\VouchersServiceProvider"
```

Publish migrations using Artisan command:

```
php artisan vendor:publish --tag=migrations --provider="FrittenKeeZ\Vouchers\VouchersServiceProvider"
```

Don't forget to run migrations:

```
php artisan migrate
```

Change basic configuration through `config/vouchers.php` - it should be well documented, so no need to describe all options here.

Usage
-----

[](#usage)

This package comes with an ease-of-use facade `Vouchers` with FQN `FrittenKeeZ\Vouchers\Facades\Vouchers`.

### Generate Codes

[](#generate-codes)

Generating codes without checking if they exist; defaults from config will be used if not specified.

```
Vouchers::generate(string|null $mask = null, string|null $characters = null): string;

$code = Vouchers::generate('***-***-***', '1234567890');
```

Batch generation of codes is also possible; these will be checked against existing codes.

```
Vouchers::batch(int amount): array;

$codes = Vouchers::batch(10);
```

### Create Vouchers

[](#create-vouchers)

Generating one or more vouchers is just as simple.

```
Vouchers::create(int $amount = 1): object|array;

$voucher = Vouchers::create();
$vouchers = Vouchers::create(10);
```

### Redeem Vouchers

[](#redeem-vouchers)

Redeeming vouchers requires that you provide a redeemer entity.
Additional metadata for the redeemer can be provided.

```
Vouchers::redeem(string $code, Illuminate\Database\Eloquent\Model $entity, array $metadata = []): bool;

try {
    $success = Vouchers::redeem('123-456-789', $user, ['foo' => 'bar']);
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherNotFoundException $e) {
    // Voucher was not found with the provided code.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherRedeemedException $e) {
    // Voucher has already been redeemed.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherUnstartedException $e) {
    // Voucher is not yet started.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherExpiredException $e) {
    // Voucher is expired.
}
```

Or if you don't care about the specific exceptions:

```
try {
    $success = Vouchers::redeem('123-456-789', $user, ['foo' => 'bar']);
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
    // Voucher was not possible to redeem.
}
```

### Unredeem Vouchers

[](#unredeem-vouchers)

Unredeeming voucher can be done by either providing a related redeemer entity, or by using a redeemer query filter to let the package find the redeemer for you.

```
Vouchers::unredeem(string $code, Illuminate\Database\Eloquent\Model|null $entity = null, Closure(Illuminate\Database\Eloquent\Builder)|null $callback = null): bool;

try {
    $success = Vouchers::unredeem('123-456-789', $user);
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherNotFoundException $e) {
    // Voucher was not found with the provided code.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherRedeemerNotFoundException $e) {
    // Voucher redeemer was not found.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherUnstartedException $e) {
    // Voucher is not yet started.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherExpiredException $e) {
    // Voucher is expired.
}
```

Or if you don't care about the specific exceptions:

```
try {
    $success = Vouchers::unredeem('123-456-789', $user);
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
    // Voucher was not possible to unredeem.
}
```

Without specifying the redeemer entity, which will use the first redeemer found:

```
try {
    $success = Vouchers::unredeem('123-456-789');
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
    // Voucher was not possible to unredeem.
}
```

With specifying a redeemer query filter:

```
try {
    $success = Vouchers::unredeem(code: '123-456-789', callback: fn (Illuminate\Database\Eloquent\Builder $query) => $query->where('metadata->foo', 'bar));
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
    // Voucher was not possible to unredeem.
}
```

### Options

[](#options)

Besides defaults specified in `config/vouchers.php`, you can override options when generating codes or creating vouchers.
Overriding model class names on runtime can be done using these methods.

```
// Override model class names.
Config::withModels(string|null $voucher = null, string|null $redeemer = null, string|null $entity = null);
// Reset model class names.
Config::resetModels();
```

Following methods apply to `Vouchers::generate()`, `Vouchers::batch()` and `Vouchers::create()` calls.

```
// Override characters list.
Vouchers::withCharacters(string|null $characters);
// Override code mask.
Vouchers::withMask(string|null $mask);
// Override code prefix.
Vouchers::withPrefix(string|null $prefix);
// Disable code prefix.
Vouchers::withoutPrefix();
// Override code suffix.
Vouchers::withSuffix(string|null $suffix);
// Disable code suffix.
Vouchers::withoutSuffix();
// Override prefix and suffix separator.
Vouchers::withSeparator(string|null $separator);
// Disable prefix and suffix separator.
Vouchers::withoutSeparator();
// Override code mask and disable prefix, suffix and separator.
Vouchers::withCode(string $code);
```

Following methods only apply to `Vouchers::create()` call.

```
// Add metadata to voucher.
Vouchers::withMetadata(array|null $metadata);
// Set voucher start time.
Vouchers::withStartTime(DateTime|null $timestamp);
// Set voucher start time using interval.
Vouchers::withStartTimeIn(DateInterval|null $interval);
// Set voucher start date - time component is zeroed.
Vouchers::withStartDate(DateTime|null $timestamp);
// Set voucher start date using interval - time component is zeroed.
Vouchers::withStartDateIn(DateInterval|null $interval);
// Set voucher expire time.
Vouchers::withExpireTime(DateTime|null $timestamp);
// Set voucher expire time using interval.
Vouchers::withExpireTimeIn(DateInterval|null $interval);
// Set voucher expire date - time component is set to end of day (23:59:59).
Vouchers::withExpireDate(DateTime|null $timestamp);
// Set voucher expire date using interval - time component is set to end of day (23:59:59).
Vouchers::withExpireDateIn(DateInterval|null $interval);
// Set related entities to voucher - using spread operater.
Vouchers::withEntities(Illuminate\Database\Eloquent\Model ...$entities);
// Set related entities to voucher - iterable.
Vouchers::withEntities(Illuminate\Database\Eloquent\Model[] $entities);
Vouchers::withEntities(Illuminate\Support\Collection $entities);
Vouchers::withEntities(Generator $entities);
// Set owning entity for voucher.
Vouchers::withOwner(Illuminate\Database\Eloquent\Model|null $owner);
```

All calls are chainable and dynamic options will be reset when calling `Vouchers::create()` or `Vouchers::reset()`.

```
$voucher = Vouchers::withMask('***-***-***')
    ->withMetadata(['foo' => 'bar'])
    ->withExpireDateIn(CarbonInterval::create('P30D'))
    ->create();
$voucher = Vouchers::withOwner($user)->withPrefix('USR');
```

Using a static code or a code mask without replacement asterisks, you can end up in an infinite loop when trying to create multiple vouchers or a voucher which already exists.
To prevent this, an exception will be thrown after a calculated number of attempts depending on the amount of asterisks and the replacement characters.

```
try {
    $vouchers = Vouchers::withCode('FIXED-CODE')->create(5);
} catch (FrittenKeeZ\Vouchers\Exceptions\InfiniteLoopException $e) {
    // Infinite loop detected when trying to create vouchers.
}
```

### Events

[](#events)

During events `Voucher::$redeemer` will be set to the active redeemer (`FrittenKeeZ\Vouchers\Models\Redeemer`).

By default vouchers will be marked as redeemed after one use, which is not always the desired outcome.
To allow a voucher to be redeemed multiple times, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::shouldMarkRedeemed()` event.

```
Voucher::shouldMarkRedeemed(function (Voucher $voucher) {
    // Do some fancy checks here.
    return false;
});
```

To prevent a voucher from being redeemed altogether, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::redeeming()` event.

```
Voucher::redeeming(function (Voucher $voucher) {
    // Do some fancy checks here.
    return false;
});
```

To prevent a voucher from being redeemed by anyone but the related user.

```
Voucher::redeeming(function (Voucher $voucher) {
    return $voucher->redeemer->redeemer->is($voucher->owner);
});
/* ... */
$voucher = Vouchers::withOwner($user)->create();
Vouchers::redeem($voucher->code, $user);
```

To perform additional actions after a voucher has been redeemed, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::redeemed()` event.

```
Voucher::redeemed(function (Voucher $voucher) {
    // Do some additional stuff here.
});
```

To prevent a voucher to from being marked as unredeemed after first unredeeming, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::shouldMarkUnredeemed()` event. Note that a voucher will still be marked as unredeemed if there are no more redeemers left.

```
Voucher::shouldMarkUnredeemed(function (Voucher $voucher) {
    // Do some fancy checks here.
    return false;
});
```

To prevent a voucher from being unredeemed altogether, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::unredeeming()` event.

```
Voucher::unredeeming(function (Voucher $voucher) {
    // Do some fancy checks here.
    return false;
});
```

To perform additional actions after a voucher has been unredeemed, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::unredeemed()` event.

```
Voucher::unredeemed(function (Voucher $voucher) {
    // Do some additional stuff here.
});
```

### Traits

[](#traits)

For convenience we provide some traits for fetching vouchers and redeemers for related entities, fx. users.
`FrittenKeeZ\Vouchers\Concerns\HasRedeemers`

```
// Associated redeemers relationship.
HasRedeemers::redeemers(): MorphMany;
// Get all associated redeemers.
$redeemers = $user->redeemers;
```

`FrittenKeeZ\Vouchers\Concerns\HasVouchers`

```
// Owned vouchers relationship.
HasVouchers::vouchers(): MorphMany;
// Get all owned vouchers.
$vouchers = $user->vouchers;

// Associated vouchers through VoucherEntity relationship.
HasVouchers::associatedVouchers(): MorphToMany;
// Get all associated vouchers.
$vouchers = $user->associatedVouchers;

// Associated voucher entities relationship.
HasVouchers::voucherEntities(): MorphMany;
// Get all associated voucher entities.
$entities = $user->voucherEntities;
```

You can also create vouchers owned by an entity using these convenience methods.

```
HasVouchers::createVoucher(Closure(FrittenKeeZ\Vouchers\Vouchers)|null $callback = null): object;

// Without using callback.
$voucher = $user->createVoucher();
// With using callback.
$voucher = $user->createVoucher(function (FrittenKeeZ\Vouchers\Vouchers $vouchers) {
    $vouchers->withPrefix('USR');
});

HasVouchers::createVouchers(int $amount, Closure(FrittenKeeZ\Vouchers\Vouchers)|null $callback = null): object|array;

// Without using callback.
$vouchers = $user->createVouchers(3);
// With using callback.
$vouchers = $user->createVouchers(3, function (FrittenKeeZ\Vouchers\Vouchers $vouchers) {
    $vouchers->withPrefix('USR');
});
```

### Helpers

[](#helpers)

Check whether a voucher code is redeemable without throwing any errors.

```
Vouchers::redeemable(string $code, Closure(FrittenKeeZ\Vouchers\Models\Voucher)|null $callback = null): bool;

// Without using callback.
$valid = Vouchers::redeemable('123-456-789');
// With using callback.
$valid = Vouchers::redeemable('123-456-789', function (FrittenKeeZ\Vouchers\Models\Voucher $voucher) {
    return $voucher->hasPrefix('foo');
});
```

Check whether a voucher code is unredeemable without throwing any errors.

```
Vouchers::unredeemable(string $code, Closure(FrittenKeeZ\Vouchers\Models\Voucher)|null $callback = null): bool;

// Without using callback.
$valid = Vouchers::unredeemable('123-456-789');
// With using callback.
$valid = Vouchers::unredeemable('123-456-789', function (FrittenKeeZ\Vouchers\Models\Voucher $voucher) {
    return $voucher->hasPrefix('foo');
});
```

Check whether a voucher code exists, optionally also checking a provided list.

```
Vouchers::exists(string $code, array $codes = []): bool;

$exists = Vouchers::exists('123-456-789', ['987-654-321']);
```

Additional helpers methods on Voucher model.

```
// Whether voucher has prefix, optionally specifying a separator different from config.
Voucher::hasPrefix(string $prefix, string|null $separator = null): bool;
// Whether voucher has suffix, optionally specifying a separator different from config.
Voucher::hasSuffix(string $suffix, string|null $separator = null): bool;
// Whether voucher is started.
Voucher::isStarted(): bool;
// Whether voucher is expired.
Voucher::isExpired(): bool;
// Whether voucher is redeemed.
Voucher::isRedeemed(): bool;
// Whether voucher is redeemable.
Voucher::isRedeemable(): bool;
// Whether voucher is unredeemable.
Voucher::isUnredeemable(): bool;
```

### Scopes

[](#scopes)

For convenience we also provide Voucher scopes matching the helper methods.

```
// Scope voucher query to a specific prefix, optionally specifying a separator different from config.
Voucher::withPrefix(string $prefix, string|null $separator = null);
// Scope voucher query to exclude a specific prefix, optionally specifying a separator different from config.
Voucher::withoutPrefix(string $prefix, string|null $separator = null);
// Scope voucher query to a specific suffix, optionally specifying a separator different from config.
Voucher::withSuffix(string $suffix, string|null $separator = null);
// Scope voucher query to exclude a specific suffix, optionally specifying a separator different from config.
Voucher::withoutSuffix(string $suffix, string|null $separator = null);
// Scope voucher query to started vouchers.
Voucher::withStarted();
// Scope voucher query to unstarted vouchers.
Voucher::withoutStarted();
// Scope voucher query to expired vouchers.
Voucher::withExpired();
// Scope voucher query to unexpired vouchers.
Voucher::withoutExpired();
// Scope voucher query to redeemed vouchers.
Voucher::withRedeemed();
// Scope voucher query to without redeemed vouchers.
Voucher::withoutRedeemed();
// Scope voucher query to redeemable vouchers.
Voucher::withRedeemable();
// Scope voucher query to without redeemable vouchers.
Voucher::withoutRedeemable();
// Scope voucher query to unredeemable vouchers.
Voucher::withUnredeemable();
// Scope voucher query to without unredeemable vouchers.
Voucher::withoutUnredeemable();
// Scope voucher query to have voucher entities, optionally of a specific type (class or alias).
Voucher::withEntities(string|null $type = null);
// Scope voucher query to specific owner type (class or alias).
Voucher::withOwnerType(string $type);
// Scope voucher query to specific owner.
Voucher::withOwner(Illuminate\Database\Eloquent\Model $owner);
// Scope voucher query to no owners.
Voucher::withoutOwner();
```

Testing
-------

[](#testing)

Running tests can be done either through composer, or directly calling the Pest binary.

```
composer test
./vendor/bin/pest --parallel
```

It is also possible to run the tests with coverage report.

```
composer test-coverage
./vendor/bin/pest --parallel --coverage
```

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

###  Health Score

62

—

FairBetter than 99% of packages

Maintenance93

Actively maintained with recent releases

Popularity41

Moderate usage in the ecosystem

Community21

Small or concentrated contributor base

Maturity79

Established project with proven stability

 Bus Factor1

Top contributor holds 91% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~98 days

Recently: every ~70 days

Total

30

Last Release

63d ago

PHP version history (5 changes)0.1.0PHP ^7.1.3

0.1.13PHP ^7.1.3|^8.0

0.4.0PHP ^8.1

0.7.0PHP ^8.2

0.8.0PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1186125?v=4)[Frederik Sauer](/maintainers/FrittenKeeZ)[@FrittenKeeZ](https://github.com/FrittenKeeZ)

---

Top Contributors

[![FrittenKeeZ](https://avatars.githubusercontent.com/u/1186125?v=4)](https://github.com/FrittenKeeZ "FrittenKeeZ (142 commits)")[![laravel-shift](https://avatars.githubusercontent.com/u/15991828?v=4)](https://github.com/laravel-shift "laravel-shift (6 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (5 commits)")[![patr612e](https://avatars.githubusercontent.com/u/44566563?v=4)](https://github.com/patr612e "patr612e (3 commits)")

---

Tags

coupondiscountlaravelvoucherlaravelvouchercoupondiscount

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/frittenkeez-laravel-vouchers/health.svg)

```
[![Health](https://phpackages.com/badges/frittenkeez-laravel-vouchers/health.svg)](https://phpackages.com/packages/frittenkeez-laravel-vouchers)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[illuminate/queue

The Illuminate Queue package.

21332.6M1.6k](/packages/illuminate-queue)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M131](/packages/laravel-pulse)[mike-bronner/laravel-model-caching

Automatic caching for Eloquent models.

2.4k90.5k1](/packages/mike-bronner-laravel-model-caching)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M194](/packages/laravel-ai)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
