frittenkeez / laravel-vouchers
Voucher system for Laravel 9+
Installs: 9 203
Dependents: 1
Suggesters: 0
Security: 0
Stars: 55
Watchers: 4
Forks: 15
Open Issues: 1
Requires
- php: ^8.1
- illuminate/config: ^9.0|^10.0|^11.0
- illuminate/console: ^9.0|^10.0|^11.0
- illuminate/database: ^9.0|^10.0|^11.0
- illuminate/support: ^9.0|^10.0|^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.4
- illuminate/support: ^9.45|^10.0|^11.0
- laravel/pint: ^1.16
- nesbot/carbon: ^2.63
- orchestra/testbench: ^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.6|^10.5
README
Table of Contents
Installation
Install this package via Composer:
composer require frittenkeez/laravel-vouchers
Upgrading
Please read the upgrade guide.
Changelog
Please read the release notes.
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
This package comes with an ease-of-use facade Vouchers
with FQN FrittenKeeZ\Vouchers\Facades\Vouchers
.
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
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
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. }
Options
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');
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 vouchers has been redeemed, subscribe to the FrittenKeeZ\Vouchers\Models\Voucher::redeemed()
event.
Voucher::redeemed(function (Voucher $voucher) { // Do some additional stuff here. });
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 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'); });
Helpers
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;
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 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();
Testing
Running tests can be done either through composer, or directly calling the PHPUnit binary.
composer test
License
The MIT License (MIT). Please see License File for more information.