PHPackages                             3neti/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. 3neti/laravel-vouchers

ActiveLibrary

3neti/laravel-vouchers
======================

Voucher system for Laravel 10+

v1.0.0(yesterday)08↑2525%MITPHPPHP ^8.2

Since Apr 4Pushed yesterdayCompare

[ Source](https://github.com/3neti/laravel-vouchers)[ Packagist](https://packagist.org/packages/3neti/laravel-vouchers)[ RSS](/packages/3neti-laravel-vouchers/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

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)

Acknowledgement
---------------

[](#acknowledgement)

This package is a fork of frittenkeez/laravel-vouchers. If upstream adds official Laravel 13 support, consider migrating back to the original package.

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

42

—

FairBetter than 89% of packages

Maintenance100

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 90.1% 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

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/586e1ed70140038e6348728222adbcf68bfc4455b1f94a4f8bcbe57917a63d57?d=identicon)[3neti](/maintainers/3neti)

---

Top Contributors

[![FrittenKeeZ](https://avatars.githubusercontent.com/u/1186125?v=4)](https://github.com/FrittenKeeZ "FrittenKeeZ (137 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)")[![3neti](https://avatars.githubusercontent.com/u/89447696?v=4)](https://github.com/3neti "3neti (1 commits)")

---

Tags

laravelvouchercoupondiscount

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

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

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

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M641](/packages/barryvdh-laravel-ide-helper)[laravel/pulse

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

1.7k12.1M96](/packages/laravel-pulse)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M24](/packages/tucker-eric-eloquentfilter)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M94](/packages/roots-acorn)[cmgmyr/messenger

Simple user messaging tool for Laravel

2.6k2.4M6](/packages/cmgmyr-messenger)[laravel-zero/framework

The Laravel Zero Framework.

3371.4M340](/packages/laravel-zero-framework)

PHPackages © 2026

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