Install this package via Composer:
$ composer require frittenkeez/laravel-vouchers
Please read the upgrade guide.
Please read the release notes.
Publish config using Artisan command:
$ php artisan vendor:publish --tag=config --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.
This package comes with an ease-of-use facade Vouchers
with FQN FrittenKeeZ\Vouchers\Facades\Vouchers
.
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);
Generating one or more vouchers is just as simple.
Vouchers::create(int $amount = 1): object|array;
$voucher = Vouchers::create();
$vouchers = Vouchers::create(10);
Redeeming vouchers requires that one provides 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) {
// Code provided did not match any vouchers in the database.
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherAlreadyRedeemedException $e) {
// Voucher has already been redeemed.
}
Besides defaults specified in config/vouchers.php
, one can override options when generating codes or creating vouchers.
Following methods apply to Vouchers::generate()
, Vouchers::batch()
and Vouchers::create()
calls.
// Override characters list.
Vouchers::withCharacters(string $characters);
// Override code mask.
Vouchers::withMask(string $mask);
// Override code prefix.
Vouchers::withPrefix(string $prefix);
// Disable code prefix.
Vouchers::withoutPrefix();
// Override code suffix.
Vouchers::withSuffix(string $suffix);
// Disable code suffix.
Vouchers::withoutSuffix();
// Override prefix and suffix separator.
Vouchers::withSeparator(string $separator);
// Disable prefix and suffix separator.
Vouchers::withoutSeparator();
Following methods only apply to Vouchers::create()
call.
// Add metadata to voucher.
Vouchers::withMetadata(array $metadata);
// Set voucher start time.
Vouchers::withStartTime(DateTime $timestamp);
// Set voucher start time using interval.
Vouchers::withStartTimeIn(DateInterval $interval);
// Set voucher start date - time component is zeroed.
Vouchers::withStartDate(DateTime $timestamp);
// Set voucher start date using interval - time component is zeroed.
Vouchers::withStartDateIn(DateInterval $interval);
// Set voucher expire time.
Vouchers::withExpireTime(DateTime $timestamp);
// Set voucher expire time using interval.
Vouchers::withExpireTimeIn(DateInterval $interval);
// Set voucher expire date - time component is set to end of day (23:59:59).
Vouchers::withExpireDate(DateTime $timestamp);
// Set voucher expire date using interval - time component is set to end of day (23:59:59).
Vouchers::withExpireDateIn(DateInterval $interval);
// Set related entities to voucher.
Vouchers::withEntities(Illuminate\Database\Eloquent\Model ...$entities);
// Set owning entity for voucher.
Vouchers::withOwner(Illuminate\Database\Eloquent\Model $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');
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 vouchers has been redeemed, subscribe to the FrittenKeeZ\Vouchers\Models\Voucher::redeemed()
event.
Voucher::redeemed(function (Voucher $voucher) {
// Do some additional stuff here.
});
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 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|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|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');
});
Check whether a voucher code is redeemable without throwing any errors.
Vouchers::redeemable(string $code, Closure|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 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;
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 unredeemed vouchers.
Voucher::withoutRedeemed();
// Scope voucher query to redeemable vouchers.
Voucher::withRedeemable();
// Scope voucher query to unredeemable vouchers.
Voucher::withoutRedeemable();
// 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();
Running tests can be done either through composer, or directly calling the PHPUnit binary.
$ composer test
To run tests with code coverage, please make sure that phpdbg
exists and is executable.
$ composer test-coverage
$ open tests/_reports/index.html
The MIT License (MIT). Please see License File for more information.