Passed
Push — main ( 948f42...af0b5b )
by Michael
03:48
created

CouponService::verifyCoupon()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 10
nc 5
nop 2
dl 0
loc 21
ccs 11
cts 11
cp 1
crap 6
rs 9.2222
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\Couponables\Services;
6
7
use Illuminate\Database\Eloquent\Model;
8
use MichaelRubel\Couponables\Events\CouponRedeemed;
9
use MichaelRubel\Couponables\Exceptions\CouponExpiredException;
10
use MichaelRubel\Couponables\Exceptions\InvalidCouponException;
11
use MichaelRubel\Couponables\Exceptions\NotAllowedToRedeemException;
12
use MichaelRubel\Couponables\Exceptions\OverLimitException;
13
use MichaelRubel\Couponables\Exceptions\OverQuantityException;
14
use MichaelRubel\Couponables\Models\Contracts\CouponContract;
15
use MichaelRubel\Couponables\Models\Contracts\CouponPivotContract;
16
use MichaelRubel\Couponables\Services\Contracts\CouponServiceContract;
17
use MichaelRubel\EnhancedContainer\Call;
18
use MichaelRubel\EnhancedContainer\Core\CallProxy;
19
20
class CouponService implements CouponServiceContract
21
{
22
    /**
23
     * @var CallProxy
24
     */
25
    protected CallProxy $model;
26
27
    /**
28
     * @var CallProxy
29
     */
30
    protected CallProxy $pivot;
31
32
    /**
33
     * @param CouponContract      $model
34
     * @param CouponPivotContract $pivot
35
     */
36 19
    public function __construct(CouponContract $model, CouponPivotContract $pivot)
37
    {
38 19
        $this->model = call($model);
39 19
        $this->pivot = call($pivot);
40
    }
41
42
    /**
43
     * Get the coupon model by the code.
44
     *
45
     * @param string|null $code
46
     *
47
     * @return CouponContract|null
48
     */
49 19
    public function getCoupon(?string $code): ?CouponContract
50
    {
51 19
        return $this->model
52 19
            ->where($this->model->getCodeColumn(), $code)
53 19
            ->first();
54
    }
55
56
    /**
57
     * Verify if coupon is valid otherwise throw an exception.
58
     *
59
     * @param string $code
60
     * @param Model  $redeemer
61
     *
62
     * @return CouponContract
63
     * @throws OverQuantityException
64
     * @throws OverLimitException
65
     * @throws NotAllowedToRedeemException
66
     * @throws CouponExpiredException
67
     * @throws InvalidCouponException
68
     */
69 19
    public function verifyCoupon(string $code, Model $redeemer): CouponContract
70
    {
71 19
        $coupon = call($this->getCoupon($code) ?? throw new InvalidCouponException);
72
73 16
        if ($coupon->isExpired()) {
74 1
            throw new CouponExpiredException;
75
        }
76
77 15
        if ($coupon->isOverQuantity()) {
78 1
            throw new OverQuantityException;
79
        }
80
81 15
        if ($this->isOverLimit($coupon, $redeemer, $code)) {
82 2
            throw new OverLimitException;
83
        }
84
85 15
        if ($coupon->isMorphColumnsFilled() && ! $coupon->redeemer()?->is($redeemer)) {
86 1
            throw new NotAllowedToRedeemException;
87
        }
88
89 14
        return $coupon->getInternal(Call::INSTANCE);
90
    }
91
92
    /**
93
     * Apply the coupon.
94
     *
95
     * @param CouponContract $coupon
96
     * @param Model          $redeemer
97
     *
98
     * @return CouponContract
99
     */
100 14
    public function applyCoupon(CouponContract $coupon, Model $redeemer): CouponContract
101
    {
102 14
        $redeemer->coupons()->attach($coupon, [
103 14
            $this->pivot->getRedeemedAtColumn() => now(),
104
        ]);
105
106 14
        if (! is_null($coupon->{$this->model->getQuantityColumn()})) {
107 3
            $coupon->decrement($this->model->getQuantityColumn());
0 ignored issues
show
Bug introduced by
The method decrement() does not exist on MichaelRubel\Couponables...ontracts\CouponContract. Since it exists in all sub-types, consider adding an abstract or default implementation to MichaelRubel\Couponables...ontracts\CouponContract. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
            $coupon->/** @scrutinizer ignore-call */ 
108
                     decrement($this->model->getQuantityColumn());
Loading history...
108
        }
109
110 14
        event(new CouponRedeemed($this, $coupon));
111
112 14
        return $coupon;
113
    }
114
115
    /**
116
     * @param mixed  $coupon
117
     * @param Model  $redeemer
118
     * @param string $code
119
     *
120
     * @return bool
121
     */
122 15
    protected function isOverLimit(mixed $coupon, Model $redeemer, string $code): bool
123
    {
124 15
        return ($coupon->isDisposable() && call($redeemer)->isCouponRedeemed($code))
125 15
            || $coupon->isOverLimitFor($redeemer);
126
    }
127
}
128