Completed
Pull Request — master (#10)
by
unknown
01:23
created

Card::name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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