CreditCardValidator::assertNotEmpty()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Larium\CreditCard;
6
7
use RuntimeException;
8
9
use function in_array;
10
use function range;
11
use function sprintf;
12
use function strlen;
13
14
class CreditCardValidator
15
{
16
    public const CONTEXT_CREDITCARD = 'credit-card';
17
18
    public const CONTEXT_TOKEN = 'token';
19
20
    private CreditCard $creditCard;
21
22
    private array $errors = [];
23
24
    private string $context = self::CONTEXT_CREDITCARD;
25
26 20
    public function __construct($context = self::CONTEXT_CREDITCARD)
27
    {
28 20
        $this->setContext($context);
29
    }
30
31
    /**
32
     * Sets the context that the validator will validate a CreditCard.
33
     *
34
     * @param string $context
35
     * @return void
36
     */
37 20
    public function setContext($context): void
38
    {
39 20
        $contexts = [self::CONTEXT_CREDITCARD, self::CONTEXT_TOKEN];
40 20
        if (!in_array($context, $contexts)) {
41 1
            throw new RuntimeException(
42 1
                sprintf("Invalid validation context '%s'", $context)
43 1
            );
44
        }
45
46 20
        $this->context = $context;
47
    }
48
49
    /**
50
     * Validates a CreditCard object.
51
     *
52
     * @param CreditCard $creditCard
53
     * @return array
54
     */
55 19
    public function validate(CreditCard $creditCard): array
56
    {
57 19
        $this->errors       = [];
58 19
        $this->creditCard   = $creditCard;
59
60 19
        if ($this->context == self::CONTEXT_CREDITCARD) {
61 15
            $this->validateNumber();
62 15
            $this->validateExpiration();
63 15
            $this->validateVerificationValue();
64 15
            $this->validateBrand();
65 15
            $this->validateCardHolder();
66 4
        } elseif ($this->context == self::CONTEXT_TOKEN) {
67 4
            $this->validateToken();
68
        }
69
70 19
        return $this->errors;
71
    }
72
73
    /**
74
     * Gets possible errors when validated a CreditCard.
75
     * Returns empty array if no errors found.
76
     * example:
77
     * [
78
     *     'number' => 'not a valid number',
79
     *     'month' => 'not a valid month',
80
     *     'brand' => 'not a valid card type',
81
     * ]
82
     *
83
     * @return array
84
     */
85 1
    public function getErrors(): array
86
    {
87 1
        return $this->errors;
88
    }
89
90 15
    private function validateNumber(): void
91
    {
92 15
        if (false === ($this->assertLength($this->creditCard->getNumber(), 12, 19)
93 15
            && $this->assertChecksum($this->creditCard->getNumber()))
94
        ) {
95 2
            $this->errors['number'] = 'not a valid number';
96
        }
97
    }
98
99 15
    private function validateExpiration(): void
100
    {
101 15
        $month = $this->creditCard->getExpiryDate()->getMonth();
102
103 15
        if (!in_array($month, range(1, 12))) {
104 1
            $this->errors['month'] = 'not a valid month';
105
106 1
            return;
107
        }
108
109 14
        if ($this->creditCard->getExpiryDate()->isExpired()) {
110 1
            $this->errors['date'] = 'not a valid date';
111
        }
112
    }
113
114 15
    private function validateVerificationValue(): void
115
    {
116 15
        if (false === $this->creditCard->isRequireCvv()) {
117 1
            return;
118
        }
119
120 14
        $length = $this->creditCard->getBrand() == CreditCard::AMEX
121 14
            ? 4 : 3;
122
123 14
        if (strlen($this->creditCard->getCvv()) !== $length) {
124 4
            $this->errors['cvv'] = 'not a valid cvv';
125
        }
126
    }
127
128 15
    private function validateBrand(): void
129
    {
130 15
        if ($this->assertNotEmpty($this->creditCard->getBrand()) === false) {
131 3
            $this->errors['brand'] = 'not valid card type';
132
        }
133
    }
134
135 15
    private function validateCardHolder(): void
136
    {
137 15
        if (false === $this->assertNotEmpty($this->creditCard->getHolderName())) {
138 2
            $this->errors['name'] = 'not a valid holder name';
139
        }
140
    }
141
142 4
    private function validateToken(): void
143
    {
144 4
        if (!$this->assertNotEmpty($this->creditCard->getToken())) {
145 1
            $this->errors['token'] = 'token value is empty';
146
147 1
            return;
148
        }
149
150 3
        if ($this->creditCard->getToken()->isExpired()) {
151 1
            $this->errors['token'] = 'token has been expired';
152
        }
153
    }
154
155 15
    private function assertLength(string $value, $min = 0, $max = 1): bool
156
    {
157 15
        $length = strlen($value);
158
159 15
        return $length >= $min && $length <= $max;
160
    }
161
162 19
    private function assertNotEmpty(mixed $value): bool
163
    {
164 19
        return !empty($value);
165
    }
166
167
    /**
168
     * Checks the validity of a card number by use of the the Luhn Algorithm.
169
     * Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
170
     *
171
     * @param string $number the number to check
172
     *
173
     * @return bool if given number has valid checksum
174
     */
175 15
    private function assertChecksum(string $number): bool
176
    {
177 15
        $map = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9];
178 15
        $sum = 0;
179 15
        $last = strlen($number) - 1;
180 15
        for ($i = 0; $i <= $last; $i++) {
181 15
            $sum += $map[$number[$last - $i] + ($i & 1) * 10];
182
        }
183
184 15
        return ($sum % 10 == 0);
185
    }
186
}
187