Skip to content

Commit e26b51b

Browse files
authored
Merge pull request #23 from FrittenKeeZ/release/unredeem
Release - Unredeem
2 parents 3599b3a + 01087be commit e26b51b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1595
-508
lines changed

.github/workflows/workflow.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ on:
55
push:
66
branches:
77
- master
8+
- "release/**"
89
paths:
910
- "**.json"
1011
- "**.php"
1112
- "**.yml"
1213
pull_request:
1314
branches:
1415
- master
16+
- "release/**"
1517
paths:
1618
- "**.json"
1719
- "**.php"
@@ -48,14 +50,9 @@ jobs:
4850
strategy:
4951
fail-fast: true
5052
matrix:
51-
php: [8.3, 8.2, 8.1]
53+
php: [8.4, 8.3, 8.2]
5254
testbench: [10.0, 9.0, 8.0] # Laravel 12.x, 11.x, 10.x
5355
version: [prefer-stable, prefer-lowest]
54-
exclude:
55-
- php: 8.1
56-
testbench: 10.0
57-
- php: 8.1
58-
testbench: 9.0
5956

6057
name: P${{ matrix.php }} - T${{ matrix.testbench }} - ${{ matrix.version }}
6158

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Release Notes
22

3+
## [v0.7.0 (2025-07-23)](https://github.com/FrittenKeeZ/laravel-vouchers/compare/0.6.2...0.7.0)
4+
5+
### Added
6+
- New `Vouchers::unredeem()` and `Vouchers::unredeemable()` methods
7+
- New `Voucher::unredeem()` and `Voucher::isUnredeemable()` methods
8+
- New `Voucher::unredeeming()` and `Voucher::unredeemed()` and `Voucher::shouldMarkUnredeemed()` events
9+
- Additional exceptions for redeeming and unredeeming vouchers:
10+
`VoucherExpiredException`
11+
`VoucherRedeemedException`
12+
`VoucherRedeemerNotFoundException`
13+
`VoucherUnstartedException`
14+
- Added extra query scopes for Vouchers:
15+
`Voucher::withUnredeemable()`
16+
`Voucher::withoutUnredeemable()`
17+
18+
### Changed
19+
- Updated `Vouchers::with*()` methods to accept `null` as a valid value for resetting to default
20+
- Improved PHPDocs comments for many methods and classes
21+
22+
### Deprecated
23+
- Dropped support for PHP 8.1
24+
25+
### Breaking Changes
26+
- Renamed `VoucherAlreadyRedeemedException` to `VoucherStateException`
27+
328
## [v0.6.2 (2025-07-10)](https://github.com/FrittenKeeZ/laravel-vouchers/compare/0.6.1...0.6.2)
429

530
### Changed

README.md

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,63 @@ Vouchers::redeem(string $code, Illuminate\Database\Eloquent\Model $entity, array
8484
try {
8585
$success = Vouchers::redeem('123-456-789', $user, ['foo' => 'bar']);
8686
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherNotFoundException $e) {
87-
// Code provided did not match any vouchers in the database.
88-
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherAlreadyRedeemedException $e) {
87+
// Voucher was not found with the provided code.
88+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherRedeemedException $e) {
8989
// Voucher has already been redeemed.
90+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherUnstartedException $e) {
91+
// Voucher is not yet started.
92+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherExpiredException $e) {
93+
// Voucher is expired.
94+
}
95+
```
96+
Or if you don't care about the specific exceptions:
97+
```php
98+
try {
99+
$success = Vouchers::redeem('123-456-789', $user, ['foo' => 'bar']);
100+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
101+
// Voucher was not possible to redeem.
102+
}
103+
```
104+
105+
### Unredeem Vouchers
106+
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.
107+
```php
108+
Vouchers::unredeem(string $code, Illuminate\Database\Eloquent\Model|null $entity = null, Closure(Illuminate\Database\Eloquent\Builder)|null $callback = null): bool;
109+
110+
try {
111+
$success = Vouchers::unredeem('123-456-789', $user);
112+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherNotFoundException $e) {
113+
// Voucher was not found with the provided code.
114+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherRedeemerNotFoundException $e) {
115+
// Voucher redeemer was not found.
116+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherUnstartedException $e) {
117+
// Voucher is not yet started.
118+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherExpiredException $e) {
119+
// Voucher is expired.
120+
}
121+
```
122+
Or if you don't care about the specific exceptions:
123+
```php
124+
try {
125+
$success = Vouchers::unredeem('123-456-789', $user);
126+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
127+
// Voucher was not possible to unredeem.
128+
}
129+
```
130+
Without specifying the redeemer entity, which will use the first redeemer found:
131+
```php
132+
try {
133+
$success = Vouchers::unredeem('123-456-789');
134+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
135+
// Voucher was not possible to unredeem.
136+
}
137+
```
138+
With specifying a redeemer query filter:
139+
```php
140+
try {
141+
$success = Vouchers::unredeem(code: '123-456-789', callback: fn (Illuminate\Database\Eloquent\Builder $query) => $query->where('metadata->foo', 'bar));
142+
} catch (FrittenKeeZ\Vouchers\Exceptions\VoucherException $e) {
143+
// Voucher was not possible to unredeem.
90144
}
91145
```
92146

@@ -95,26 +149,26 @@ Besides defaults specified in `config/vouchers.php`, one can override options wh
95149
Following methods apply to `Vouchers::generate()`, `Vouchers::batch()` and `Vouchers::create()` calls.
96150
```php
97151
// Override characters list.
98-
Vouchers::withCharacters(string $characters);
152+
Vouchers::withCharacters(string|null $characters);
99153
// Override code mask.
100-
Vouchers::withMask(string $mask);
154+
Vouchers::withMask(string|null $mask);
101155
// Override code prefix.
102-
Vouchers::withPrefix(string $prefix);
156+
Vouchers::withPrefix(string|null $prefix);
103157
// Disable code prefix.
104158
Vouchers::withoutPrefix();
105159
// Override code suffix.
106-
Vouchers::withSuffix(string $suffix);
160+
Vouchers::withSuffix(string|null $suffix);
107161
// Disable code suffix.
108162
Vouchers::withoutSuffix();
109163
// Override prefix and suffix separator.
110-
Vouchers::withSeparator(string $separator);
164+
Vouchers::withSeparator(string|null $separator);
111165
// Disable prefix and suffix separator.
112166
Vouchers::withoutSeparator();
113167
```
114168
Following methods only apply to `Vouchers::create()` call.
115169
```php
116170
// Add metadata to voucher.
117-
Vouchers::withMetadata(array $metadata);
171+
Vouchers::withMetadata(array|null $metadata);
118172
// Set voucher start time.
119173
Vouchers::withStartTime(DateTime|null $timestamp);
120174
// Set voucher start time using interval.
@@ -138,7 +192,7 @@ Vouchers::withEntities(Illuminate\Database\Eloquent\Model[] $entities);
138192
Vouchers::withEntities(Illuminate\Support\Collection<Illuminate\Database\Eloquent\Model> $entities);
139193
Vouchers::withEntities(Generator<Illuminate\Database\Eloquent\Model> $entities);
140194
// Set owning entity for voucher.
141-
Vouchers::withOwner(Illuminate\Database\Eloquent\Model $owner);
195+
Vouchers::withOwner(Illuminate\Database\Eloquent\Model|null $owner);
142196
```
143197
All calls are chainable and dynamic options will be reset when calling `Vouchers::create()` or `Vouchers::reset()`.
144198
```php
@@ -176,12 +230,32 @@ Voucher::redeeming(function (Voucher $voucher) {
176230
$voucher = Vouchers::withOwner($user)->create();
177231
Vouchers::redeem($voucher->code, $user);
178232
```
179-
To perform additional actions after a vouchers has been redeemed, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::redeemed()` event.
233+
To perform additional actions after a voucher has been redeemed, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::redeemed()` event.
180234
```php
181235
Voucher::redeemed(function (Voucher $voucher) {
182236
// Do some additional stuff here.
183237
});
184238
```
239+
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.
240+
```php
241+
Voucher::shouldMarkUnredeemed(function (Voucher $voucher) {
242+
// Do some fancy checks here.
243+
return false;
244+
});
245+
```
246+
To prevent a voucher from being unredeemed altogether, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::unredeeming()` event.
247+
```php
248+
Voucher::unredeeming(function (Voucher $voucher) {
249+
// Do some fancy checks here.
250+
return false;
251+
});
252+
```
253+
To perform additional actions after a voucher has been unredeemed, subscribe to the `FrittenKeeZ\Vouchers\Models\Voucher::unredeemed()` event.
254+
```php
255+
Voucher::unredeemed(function (Voucher $voucher) {
256+
// Do some additional stuff here.
257+
});
258+
```
185259

186260
### Traits
187261
For convenience we provide some traits for fetching vouchers and redeemers for related entities, fx. users.
@@ -199,7 +273,7 @@ HasVouchers::vouchers(): MorphMany;
199273
// Get all owned vouchers.
200274
$vouchers = $user->vouchers;
201275

202-
// Associated vouchers relationship.
276+
// Associated vouchers through VoucherEntity relationship.
203277
HasVouchers::associatedVouchers(): MorphToMany;
204278
// Get all associated vouchers.
205279
$vouchers = $user->associatedVouchers;
@@ -211,7 +285,7 @@ $entities = $user->voucherEntities;
211285
```
212286
You can also create vouchers owned by an entity using these convenience methods.
213287
```php
214-
HasVouchers::createVoucher(Closure|null $callback = null): object;
288+
HasVouchers::createVoucher(Closure(FrittenKeeZ\Vouchers\Vouchers)|null $callback = null): object;
215289

216290
// Without using callback.
217291
$voucher = $user->createVoucher();
@@ -220,7 +294,7 @@ $voucher = $user->createVoucher(function (FrittenKeeZ\Vouchers\Vouchers $voucher
220294
$vouchers->withPrefix('USR');
221295
});
222296

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

225299
// Without using callback.
226300
$vouchers = $user->createVouchers(3);
@@ -233,7 +307,7 @@ $vouchers = $user->createVouchers(3, function (FrittenKeeZ\Vouchers\Vouchers $vo
233307
### Helpers
234308
Check whether a voucher code is redeemable without throwing any errors.
235309
```php
236-
Vouchers::redeemable(string $code, Closure|null $callback = null): bool;
310+
Vouchers::redeemable(string $code, Closure(FrittenKeeZ\Vouchers\Models\Voucher)|null $callback = null): bool;
237311

238312
// Without using callback.
239313
$valid = Vouchers::redeemable('123-456-789');
@@ -242,6 +316,17 @@ $valid = Vouchers::redeemable('123-456-789', function (FrittenKeeZ\Vouchers\Mode
242316
return $voucher->hasPrefix('foo');
243317
});
244318
```
319+
Check whether a voucher code is unredeemable without throwing any errors.
320+
```php
321+
Vouchers::unredeemable(string $code, Closure(FrittenKeeZ\Vouchers\Models\Voucher)|null $callback = null): bool;
322+
323+
// Without using callback.
324+
$valid = Vouchers::unredeemable('123-456-789');
325+
// With using callback.
326+
$valid = Vouchers::unredeemable('123-456-789', function (FrittenKeeZ\Vouchers\Models\Voucher $voucher) {
327+
return $voucher->hasPrefix('foo');
328+
});
329+
```
245330
Check whether a voucher code exists, optionally also checking a provided list.
246331
```php
247332
Vouchers::exists(string $code, array $codes = []): bool;
@@ -262,6 +347,8 @@ Voucher::isExpired(): bool;
262347
Voucher::isRedeemed(): bool;
263348
// Whether voucher is redeemable.
264349
Voucher::isRedeemable(): bool;
350+
// Whether voucher is unredeemable.
351+
Voucher::isUnredeemable(): bool;
265352
```
266353

267354
### Scopes
@@ -285,12 +372,16 @@ Voucher::withExpired();
285372
Voucher::withoutExpired();
286373
// Scope voucher query to redeemed vouchers.
287374
Voucher::withRedeemed();
288-
// Scope voucher query to unredeemed vouchers.
375+
// Scope voucher query to without redeemed vouchers.
289376
Voucher::withoutRedeemed();
290377
// Scope voucher query to redeemable vouchers.
291378
Voucher::withRedeemable();
292-
// Scope voucher query to unredeemable vouchers.
379+
// Scope voucher query to without redeemable vouchers.
293380
Voucher::withoutRedeemable();
381+
// Scope voucher query to unredeemable vouchers.
382+
Voucher::withUnredeemable();
383+
// Scope voucher query to without unredeemable vouchers.
384+
Voucher::withoutUnredeemable();
294385
// Scope voucher query to have voucher entities, optionally of a specific type (class or alias).
295386
Voucher::withEntities(string|null $type = null);
296387
// Scope voucher query to specific owner type (class or alias).
@@ -302,9 +393,15 @@ Voucher::withoutOwner();
302393
```
303394

304395
## Testing
305-
Running tests can be done either through composer, or directly calling the PHPUnit binary.
396+
Running tests can be done either through composer, or directly calling the Pest binary.
306397
```bash
307398
composer test
399+
./vendor/bin/pest --parallel
400+
```
401+
It is also possible to run the tests with coverage report.
402+
```bash
403+
composer test-coverage
404+
./vendor/bin/pest --parallel --coverage
308405
```
309406

310407
## License

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
}
1717
],
1818
"require": {
19-
"php": "^8.1",
19+
"php": "^8.2",
2020
"illuminate/config": "^10.0|^11.0|^12.0",
2121
"illuminate/console": "^10.0|^11.0|^12.0",
2222
"illuminate/database": "^10.0|^11.0|^12.0",
@@ -46,12 +46,13 @@
4646
],
4747
"psr-4": {
4848
"FrittenKeeZ\\Vouchers\\Tests\\": "tests",
49-
"Database\\Factories\\FrittenKeeZ\\Vouchers\\Tests\\Models\\": "tests/database/factories"
49+
"FrittenKeeZ\\Vouchers\\Tests\\Database\\Factories\\": "tests/database/factories"
5050
}
5151
},
5252
"scripts": {
5353
"pint": "pint",
54-
"test": "pest --parallel"
54+
"test": "pest --parallel",
55+
"test-coverage": "pest --parallel --coverage"
5556
},
5657
"extra": {
5758
"laravel": {

src/Concerns/HasVouchers.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public function voucherEntities(): MorphMany
3838

3939
/**
4040
* Create a single voucher with this entity related.
41+
*
42+
* @param \Closure(\FrittenKeeZ\Vouchers\Vouchers)|null $callback Optional callback for extra configuration.
4143
*/
4244
public function createVoucher(?Closure $callback = null): object
4345
{
@@ -46,6 +48,8 @@ public function createVoucher(?Closure $callback = null): object
4648

4749
/**
4850
* Create an amount of vouchers with this entity related.
51+
*
52+
* @param \Closure(\FrittenKeeZ\Vouchers\Vouchers)|null $callback Optional callback for extra configuration.
4953
*/
5054
public function createVouchers(int $amount, ?Closure $callback = null): array|object
5155
{

0 commit comments

Comments
 (0)