@@ -84,9 +84,63 @@ Vouchers::redeem(string $code, Illuminate\Database\Eloquent\Model $entity, array
8484try {
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
95149Following 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.
104158Vouchers::withoutPrefix();
105159// Override code suffix.
106- Vouchers::withSuffix(string $suffix);
160+ Vouchers::withSuffix(string|null $suffix);
107161// Disable code suffix.
108162Vouchers::withoutSuffix();
109163// Override prefix and suffix separator.
110- Vouchers::withSeparator(string $separator);
164+ Vouchers::withSeparator(string|null $separator);
111165// Disable prefix and suffix separator.
112166Vouchers::withoutSeparator();
113167```
114168Following 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.
119173Vouchers::withStartTime(DateTime|null $timestamp);
120174// Set voucher start time using interval.
@@ -138,7 +192,7 @@ Vouchers::withEntities(Illuminate\Database\Eloquent\Model[] $entities);
138192Vouchers::withEntities(Illuminate\Support\Collection<Illuminate \Database\Eloquent\Model > $entities);
139193Vouchers::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```
143197All 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();
177231Vouchers::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
181235Voucher::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
187261For 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.
203277HasVouchers::associatedVouchers(): MorphToMany;
204278// Get all associated vouchers.
205279$vouchers = $user->associatedVouchers;
@@ -211,7 +285,7 @@ $entities = $user->voucherEntities;
211285```
212286You 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
234308Check 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+ ```
245330Check whether a voucher code exists, optionally also checking a provided list.
246331``` php
247332Vouchers::exists(string $code, array $codes = []): bool;
@@ -262,6 +347,8 @@ Voucher::isExpired(): bool;
262347Voucher::isRedeemed(): bool;
263348// Whether voucher is redeemable.
264349Voucher::isRedeemable(): bool;
350+ // Whether voucher is unredeemable.
351+ Voucher::isUnredeemable(): bool;
265352```
266353
267354### Scopes
@@ -285,12 +372,16 @@ Voucher::withExpired();
285372Voucher::withoutExpired();
286373// Scope voucher query to redeemed vouchers.
287374Voucher::withRedeemed();
288- // Scope voucher query to unredeemed vouchers.
375+ // Scope voucher query to without redeemed vouchers.
289376Voucher::withoutRedeemed();
290377// Scope voucher query to redeemable vouchers.
291378Voucher::withRedeemable();
292- // Scope voucher query to unredeemable vouchers.
379+ // Scope voucher query to without redeemable vouchers.
293380Voucher::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).
295386Voucher::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
307398composer 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
0 commit comments