Completed
Push — master ( ed9a3b...365ea7 )
by Darius
02:02
created

Card::name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace LVR\CreditCard\Cards;
4
5
use LVR\CreditCard\Exceptions\CreditCardException;
6
use LVR\CreditCard\Exceptions\CreditCardCvcException;
7
use LVR\CreditCard\Exceptions\CreditCardNameException;
8
use LVR\CreditCard\Exceptions\CreditCardTypeException;
9
use LVR\CreditCard\Exceptions\CreditCardLengthException;
10
use LVR\CreditCard\Exceptions\CreditCardPatternException;
11
use LVR\CreditCard\Exceptions\CreditCardChecksumException;
12
use LVR\CreditCard\Exceptions\CreditCardCharactersException;
13
14
abstract class Card
15
{
16
    /**
17
     * Regular expression for card number recognition.
18
     *
19
     * @var string
20
     */
21
    public static $pattern;
22
23
    /**
24
     * Credit card type: "debit", "credit".
25
     *
26
     * @var string
27
     */
28
    protected $type;
29
30
    /**
31
     * Credit card name.
32
     *
33
     * @var string
34
     */
35
    protected $name;
36
37
    /**
38
     * Brand name.
39
     *
40
     * @var string
41
     */
42
    protected $brand;
43
44
    /**
45
     * Card number length's.
46
     *
47
     * @var array
48
     */
49
    protected $number_length;
50
51
    /**
52
     * CVC code length's.
53
     *
54
     * @var array
55
     */
56
    protected $cvc_length;
57
58
    /**
59
     * Test cvc code checksum against Luhn algorithm.
60
     *
61
     * @var bool
62
     */
63
    protected $checksum_test;
64
65
    /**
66
     * @var string
67
     */
68
    private $card_number;
69
70
    /**
71
     * Card constructor.
72
     *
73
     * @param string $card_number
74
     *
75
     * @throws \LVR\CreditCard\Exceptions\CreditCardException
76
     */
77 115
    public function __construct(string $card_number = '')
78
    {
79 115
        $this->checkImplementation();
80
81 110
        if ($card_number) {
82 44
            $this->setCardNumber($card_number);
83
        }
84 83
    }
85
86
    /**
87
     * @param string $card_number
88
     *
89
     * @return $this
90
     * @throws \LVR\CreditCard\Exceptions\CreditCardPatternException
91
     */
92 83
    public function setCardNumber(string $card_number)
93
    {
94 83
        $this->card_number = preg_replace('/\s+/', '', $card_number);
95
96 83
        $this->isValidCardNumber();
97
98 41
        if (! $this->validPattern()) {
99 11
            throw new CreditCardPatternException(
100 11
                sprintf('Wrong "%s" card pattern', $this->card_number)
101
            );
102
        }
103
104 30
        return $this;
105
    }
106
107
    /**
108
     * @return bool
109
     * @throws \LVR\CreditCard\Exceptions\CreditCardChecksumException
110
     * @throws \LVR\CreditCard\Exceptions\CreditCardCharactersException
111
     * @throws \LVR\CreditCard\Exceptions\CreditCardException
112
     * @throws \LVR\CreditCard\Exceptions\CreditCardLengthException
113
     */
114 96
    public function isValidCardNumber()
115
    {
116 96
        if (! $this->card_number) {
117 13
            throw new CreditCardException('Card number is not set');
118
        }
119
120 83
        if (! is_numeric(preg_replace('/\s+/', '', $this->card_number))) {
121 14
            throw new CreditCardCharactersException(
122 14
                sprintf('Card number "%s" contains invalid characters', $this->card_number)
123
            );
124
        }
125
126 69
        if (! $this->validLength()) {
127 25
            throw new CreditCardLengthException(
128 25
                sprintf('Incorrect "%s" card length', $this->card_number)
129
            );
130
        }
131
132 54
        if (! $this->validChecksum()) {
133 13
            throw new CreditCardChecksumException(
134 13
                sprintf('Invalid card number: "%s". Checksum is wrong', $this->card_number)
135
            );
136
        }
137
138 41
        return true;
139
    }
140
141
    /**
142
     * @return string
143
     */
144 13
    public function type()
145
    {
146 13
        return $this->type;
147
    }
148
149
    /**
150
     * @return string
151
     */
152 1
    public function name()
153
    {
154 1
        return $this->name;
155
    }
156
157
    /**
158
     * @return string
159
     */
160 1
    public function brand()
161
    {
162 1
        return $this->brand;
163
    }
164
165
    /**
166
     * @param $cvc
167
     *
168
     * @return bool
169
     */
170 1
    public function isValidCvc($cvc)
171
    {
172 1
        return is_numeric($cvc)
173 1
            && self::isValidCvcLength($cvc, $this->cvc_length);
174
    }
175
176
    /**
177
     * Check CVS length against possible lengths.
178
     *
179
     * @param string|int $cvc
180
     *
181
     * @param array $available_lengths
182
     *
183
     * @return bool
184
     */
185 2
    public static function isValidCvcLength($cvc, array $available_lengths = [3, 4])
186
    {
187
        return
188 2
            is_numeric($cvc)
189 2
            && in_array(strlen($cvc), $available_lengths, true);
190
    }
191
192
    /**
193
     * @throws \LVR\CreditCard\Exceptions\CreditCardException
194
     */
195 115
    protected function checkImplementation()
196
    {
197 115
        if (! $this->type || ! is_string($this->type) || ! in_array($this->type, ['debit', 'credit'])) {
198 1
            throw new CreditCardTypeException('Credit card type is missing');
199
        }
200
201 115
        if (! $this->name || ! is_string($this->name)) {
202 2
            throw new CreditCardNameException('Credit card name is missing or is not a string');
203
        }
204
205 114
        if (! static::$pattern || ! is_string(static::$pattern)) {
206 2
            throw new CreditCardPatternException(
207 2
                'Credit card number recognition pattern is missing or is not a string'
208
            );
209
        }
210
211 113
        if (empty($this->number_length) || ! is_array($this->number_length)) {
212 2
            throw new CreditCardLengthException(
213 2
                'Credit card number length is missing or is not an array'
214
            );
215
        }
216
217 112
        if (empty($this->cvc_length) || ! is_array($this->cvc_length)) {
218 2
            throw new CreditCardCvcException(
219 2
                'Credit card cvc code length is missing or is not an array'
220
            );
221
        }
222
223 111
        if ($this->checksum_test === null || ! is_bool($this->checksum_test)) {
224 2
            throw new CreditCardChecksumException(
225 2
                'Credit card checksum test is missing or is not a boolean'
226
            );
227
        }
228 110
    }
229
230
    /**
231
     * @return bool
232
     */
233 41
    protected function validPattern()
234
    {
235 41
        return (bool) preg_match(static::$pattern, $this->card_number);
236
    }
237
238
    /**
239
     * @return bool
240
     */
241 69
    protected function validLength()
242
    {
243 69
        return in_array(strlen($this->card_number), $this->number_length, true);
244
    }
245
246
    /**
247
     * @return bool
248
     */
249 54
    protected function validChecksum()
250
    {
251 54
        return ! $this->checksum_test || $this->checksumTest();
252
    }
253
254
    /**
255
     * @return bool
256
     */
257 51
    protected function checksumTest()
258
    {
259 51
        $checksum = 0;
260 51
        $len = strlen($this->card_number);
261 51
        for ($i = 2 - ($len % 2); $i <= $len; $i += 2) {
262 51
            $checksum += $this->card_number[$i - 1];
263
        }
264
        // Analyze odd digits in even length strings or even digits in odd length strings.
265 51
        for ($i = $len % 2 + 1; $i < $len; $i += 2) {
266 51
            $digit = $this->card_number[$i - 1] * 2;
267 51
            if ($digit < 10) {
268 51
                $checksum += $digit;
269
            } else {
270 45
                $checksum += $digit - 9;
271
            }
272
        }
273
274 51
        return ($checksum % 10) === 0;
275
    }
276
}
277