Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
34.62% covered (danger)
34.62%
9 / 26
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CouponService
34.62% covered (danger)
34.62%
9 / 26
0.00% covered (danger)
0.00%
0 / 2
44.82
0.00% covered (danger)
0.00%
0 / 1
 resolve
50.00% covered (danger)
50.00%
9 / 18
0.00% covered (danger)
0.00%
0 / 1
22.50
 redeem
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Services\Cart;
4
5use App\Models\Coupon;
6use App\Models\CouponRedemption;
7
8/**
9 * Validates coupon codes against a cart subtotal and records redemptions.
10 */
11class CouponService
12{
13    /**
14     * Resolve a usable coupon for this code + subtotal + customer, or return
15     * a [null, reason] pair explaining why it can't be applied.
16     *
17     * @return array{0: ?Coupon, 1: ?string}
18     */
19    public function resolve(string $code, float $subtotal, ?int $customerId = null, ?string $email = null): array
20    {
21        if (! feature('coupons')) {
22            return [null, 'Coupons are turned off.'];
23        }
24
25        $coupon = Coupon::query()->where('code', strtoupper(trim($code)))->first();
26
27        if (! $coupon) {
28            return [null, 'That coupon code is not valid.'];
29        }
30
31        if (! $coupon->isLive()) {
32            return [null, 'This coupon has expired or is no longer available.'];
33        }
34
35        if ($subtotal < (float) $coupon->min_order) {
36            return [null, 'Add ' . money($coupon->min_order - $subtotal) . ' more to use this coupon.'];
37        }
38
39        if ($coupon->per_customer_limit !== null && ($customerId || $email)) {
40            $used = CouponRedemption::query()
41                ->where('coupon_id', $coupon->id)
42                ->when($customerId, fn ($q) => $q->where('customer_id', $customerId))
43                ->when(! $customerId && $email, fn ($q) => $q->where('email', $email))
44                ->count();
45
46            if ($used >= $coupon->per_customer_limit) {
47                return [null, 'You have already used this coupon.'];
48            }
49        }
50
51        return [$coupon, null];
52    }
53
54    public function redeem(Coupon $coupon, float $amount, ?int $orderId = null, ?int $customerId = null, ?string $email = null): void
55    {
56        CouponRedemption::query()->create([
57            'coupon_id' => $coupon->id,
58            'order_id' => $orderId,
59            'customer_id' => $customerId,
60            'email' => $email,
61            'amount' => $amount,
62        ]);
63
64        $coupon->increment('used_count');
65    }
66}