PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / banking / creditcard.php
vikappointments / site / helpers / libraries / banking Last commit date
brands 6 days ago creditcard.php 6 days ago index.html 6 days ago
creditcard.php
717 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * This class is used to handle and dispatch the proper credit card brand.
16 * It is helpful to use this class for seamless payment gateways.
17 *
18 * Usage:
19 * $card_number = '4242424242424242'; // VISA
20 * $cc = CreditCard::getInstance($card_number);
21 * echo $cc->formatCardNumber();
22 *
23 * @since 1.6
24 */
25 abstract class CreditCard
26 {
27 /**
28 * The number of the credit card.
29 *
30 * @var string
31 */
32 private $card_number = '';
33
34 /**
35 * The CVC, CVV or CV2 of the credit card.
36 *
37 * @var string
38 */
39 private $cvc = '0';
40
41 /**
42 * The expiring month of the credit card (1-12).
43 *
44 * @var integer
45 */
46 private $exp_month = 0;
47
48 /**
49 * The expiring year of the credit card.
50 *
51 * @var integer
52 */
53 private $exp_year = 0;
54
55 /**
56 * The cardholder name.
57 *
58 * @var string
59 */
60 private $card_holder = '';
61
62 /**
63 * Class constructor.
64 *
65 * @param string $card_number The card number.
66 * @param string $cvc The CVC, CVV or CV2.
67 * @param integer $month The expiring month.
68 * @param integer $year The expiring year.
69 * @param string $card_holder The cardholder name.
70 *
71 * @uses setCardNumber()
72 * @uses setCvc()
73 * @uses setExpiryDate()
74 * @uses setCardholderName()
75 */
76 public function __construct($card_number, $cvc = '0', $month = 0, $year = 0, $card_holder = '')
77 {
78 $this->setCardNumber($card_number)
79 ->setCvc($cvc)
80 ->setExpiryDate($month, $year)
81 ->setCardholderName($card_holder);
82 }
83
84 /**
85 * Sets the credit card number.
86 * Any character that it is not a digit will be ignored.
87 *
88 * @param string $card_number The card number.
89 *
90 * @return self This object to support chaining.
91 */
92 public function setCardNumber($card_number)
93 {
94 $this->card_number = preg_replace('/\D/', '', $card_number);
95
96 return $this;
97 }
98
99 /**
100 * Gets the credit card number.
101 *
102 * @return string The card number.
103 */
104 public function getCardNumber()
105 {
106 return $this->card_number;
107 }
108
109 /**
110 * Sets the credit card CVC, CVV or CV2.
111 * Any character that it is not a digit will be ignored.
112 *
113 * @param string $cvc The CVC, CVV or CV2.
114 *
115 * @return self This object to support chaining.
116 */
117 public function setCvc($cvc)
118 {
119 $this->cvc = preg_replace('/\D/', '', $cvc);
120
121 return $this;
122 }
123
124 /**
125 * Gets the credit card CVC, CVV or CV2.
126 *
127 * @return string The card CVC, CVV or CV2.
128 */
129 public function getCvc()
130 {
131 return $this->cvc;
132 }
133
134 /**
135 * Sets the credit card expiring month.
136 *
137 * @param integer $month The expiring month (1-12).
138 *
139 * @return self This object to support chaining.
140 */
141 public function setExpiryMonth($month)
142 {
143 $month = intval($month);
144
145 if ($month >= 1 && $month <= 12)
146 {
147 $this->exp_month = $month;
148 }
149
150 return $this;
151 }
152
153 /**
154 * Gets the expiring month.
155 *
156 * @return integer The expiring month.
157 */
158 public function getExpiryMonth()
159 {
160 return $this->exp_month;
161 }
162
163 /**
164 * Sets the credit card expiring year.
165 *
166 * @param integer $year The expiring year with 2 or 4 digits (e.g. 16 or 2016).
167 *
168 * @return self This object to support chaining.
169 */
170 public function setExpiryYear($year)
171 {
172 $year = intval($year);
173
174 if ($year < 1000)
175 {
176 $now = getdate();
177 $year = intval(substr($now['year'], 0, 2) . $year);
178 }
179
180 $this->exp_year = $year;
181
182 return $this;
183 }
184
185 /**
186 * Gets the expiring year.
187 *
188 * @return integer The expiring year.
189 */
190 public function getExpiryYear()
191 {
192 return $this->exp_year;
193 }
194
195 /**
196 * Sets the credit card expiring date: month and year.
197 *
198 * @param integer $month The expiring month (1-12).
199 * @param integer $year The expiring year with 2 or 4 digits (e.g. 16 or 2016).
200 *
201 * @return self This object to support chaining.
202 *
203 * @uses setExpiryMonth()
204 * @uses setExpiryYear()
205 */
206 public function setExpiryDate($month, $year)
207 {
208 return $this->setExpiryMonth($month)->setExpiryYear($year);
209 }
210
211 /**
212 * Gets the expiring date with the given format.
213 *
214 * @param string $format The date format.
215 *
216 * @return string The expiring date.
217 */
218 public function getExpiryDate($format = 'm/y')
219 {
220 $date = getdate(mktime(0, 0, 0, $this->exp_month, 1, $this->exp_year));
221
222 $last_mday = 31;
223 if (in_array($date['mon'], array(4, 6, 9, 11)))
224 {
225 $last_mday = 30;
226 }
227 else if ($date['mon'] == 2)
228 {
229 $last_mday = 28;
230 }
231
232 return date($format, mktime(23, 59, 59, $date['mon'], $last_mday, $date['year']));
233 }
234
235 /**
236 * Sets the cardholder name.
237 *
238 * @param string $card_holder The cardholder name.
239 *
240 * @return self This object to support chaining.
241 */
242 public function setCardholderName($card_holder)
243 {
244 $this->card_holder = trim($card_holder);
245
246 return $this;
247 }
248
249 /**
250 * Gets the cardholder name.
251 *
252 * @return string The cardholder name.
253 */
254 public function getCardholderName()
255 {
256 return $this->card_holder;
257 }
258
259 /**
260 * Checks if the credit card number is valid.
261 * The card number validation depends on the brand of the credit card.
262 *
263 * @return boolean True if the card number is valid.
264 */
265 abstract public function isCardNumberValid();
266
267 /**
268 * Checks if the CVC is valid.
269 * The CVC is valid if it is a number in the range [001 - 9999]
270 *
271 * @return boolean True if the CVC is valid, otherwise false.
272 */
273 public function isCardCvcValid()
274 {
275 return ($len = strlen($this->cvc)) >= 3 && $len <= 4;
276 }
277
278 /**
279 * Checks if the credit card is expired.
280 *
281 * @return boolean True if the expiring date is in the past, otherwise false.
282 *
283 * @uses getExpiryDate()
284 */
285 public function isExpired()
286 {
287 $exp = strtotime($this->getExpiryDate('Y-m-d'));
288
289 return $exp < time();
290 }
291
292 /**
293 * Checks if the cardholder name is valid.
294 * The cardholder name is valid only if it has more than 2 characters
295 * and at least one of them is a white space (e.g John Smith).
296 * It is needed to have at least 3 chars and 1 space to receive a First name
297 * and a last name.
298 *
299 * It is not possible to have a white space at the beginning or at the end
300 * because the cardholder name is trimmed by the setter.
301 *
302 * @return boolean True if the cardholder name is valid, otherwise false.
303 */
304 public function isCardholderValid()
305 {
306 return strlen($this->card_holder) > 2 && strpos($this->card_holder, ' ') !== false;
307 }
308
309 /**
310 * Checks approximately if the credit card can be charged.
311 * A credit card can be charged only if all the conditions below are satisfied:
312 * - credit card number is valid
313 * - cvc is valid
314 * - expiring date is not in the past
315 * - cardholder name is valid
316 *
317 * @return boolean True if the credit card is chargable, otherwise false.
318 *
319 * @uses isCardNumberValid()
320 * @uses isCardCvcValid()
321 * @uses isExpired()
322 * @uses isCardholderValid()
323 */
324 public function isChargeable()
325 {
326 return ($this->isCardNumberValid()
327 && $this->isCardCvcValid()
328 && !$this->isExpired()
329 && $this->isCardholderValid());
330 }
331
332 /**
333 * Gets the credit card number digits count.
334 *
335 * @return integer The digits count of the credit card number.
336 */
337 abstract public function getCardNumberDigits();
338
339 /**
340 * Formats the credit card number to be more human-readable.
341 *
342 * @return string The formatted card number.
343 */
344 abstract public function formatCardNumber();
345
346 /**
347 * Gets a masked version of the credit card for privacy.
348 *
349 * @return array A list containing 2 different masked versions of card number.
350 */
351 abstract public function getMaskedCardNumber();
352
353 /**
354 * Gets the alias of the credit card brand.
355 * The alias should be equal to the filename of the brand class.
356 *
357 * @return string The alias of the credit card brand.
358 */
359 abstract public function getBrandAlias();
360
361 /**
362 * Gets the name of the credit card brand.
363 *
364 * @return string The name of the credit card brand.
365 */
366 abstract public function getBrandName();
367
368 /**
369 * Instantiates a new credit card brand class depending on the specified card number.
370 * Returns NULL in case the method is not able to recognize the brand.
371 *
372 * @param string $card_number The card number.
373 * @param string $cvc The CVC, CVV or CV2.
374 * @param integer $month The expiring month.
375 * @param integer $year The expiring year.
376 * @param string $card_holder The cardholder name.
377 *
378 * @return mixed The proper CC brand handler, or NULL.
379 *
380 * @uses load()
381 * @uses isVisa()
382 * @uses isMsterCard()
383 * @uses isAmericanExpress()
384 * @uses isDinersClub()
385 * @uses isDiscover()
386 * @uses isJcb()
387 */
388 public static function getBrand($card_number, $cvc = '0', $month = 0, $year = 0, $card_holder = '')
389 {
390 if (CreditCard::isVisa($card_number))
391 {
392 if (CreditCard::load(CreditCard::VISA))
393 {
394 return new CCVisa($card_number, $cvc, $month, $year, $card_holder);
395 }
396 }
397 else if (CreditCard::isMasterCard($card_number))
398 {
399 if (CreditCard::load(CreditCard::MASTER_CARD))
400 {
401 return new CCMasterCard($card_number, $cvc, $month, $year, $card_holder);
402 }
403 }
404 else if (CreditCard::isAmericanExpress($card_number))
405 {
406 if (CreditCard::load(CreditCard::AMERICAN_EXPRESS))
407 {
408 return new CCAmericanExpress($card_number, $cvc, $month, $year, $card_holder);
409 }
410 }
411 else if (CreditCard::isDinersClub($card_number))
412 {
413 if (CreditCard::load(CreditCard::DINERS_CLUB))
414 {
415 return new CCDinersClub($card_number, $cvc, $month, $year, $card_holder);
416 }
417 }
418 else if (CreditCard::isDiscover($card_number))
419 {
420 if (CreditCard::load(CreditCard::DISCOVER))
421 {
422 return new CCDiscover($card_number, $cvc, $month, $year, $card_holder);
423 }
424 }
425 else if (CreditCard::isJcb($card_number))
426 {
427 if (CreditCard::load(CreditCard::JCB))
428 {
429 return new CCJcb($card_number, $cvc, $month, $year, $card_holder);
430 }
431 }
432
433 return null;
434 }
435
436 /**
437 * Checks if the card number matches a Visa.
438 *
439 * How to identify VISA:
440 * A credit card is a VISA when the card number starts with 4.
441 *
442 * e.g. 4242 0000 000 0000
443 *
444 * @param string $card_number The card number.
445 *
446 * @return boolean True if the card number is a Visa, otherwise false.
447 *
448 * @uses matchBrandRanges()
449 */
450 public static function isVisa($card_number)
451 {
452 $card_number = preg_replace('/\D/', '', $card_number);
453
454 // starts with 4
455 $ranges = array(
456 array(4),
457 );
458
459 return self::matchBrandRanges($card_number, $ranges);
460 }
461
462 /**
463 * Checks if the card number matches a MasterCard.
464 *
465 * How to identify MasterCard:
466 * A credit card is a MasterCard when the first 2 digits are in the range [51 - 55]
467 * or the first four digits are in the range [2221 - 2720].
468 *
469 * e.g. 5353 0000 0000 0000
470 * e.g. 2323 0000 0000 0000
471 *
472 * @param string $card_number The card number.
473 *
474 * @return boolean True if the card number is a MasterCard, otherwise false.
475 *
476 * @uses matchBrandRanges()
477 */
478 public static function isMasterCard($card_number)
479 {
480 $card_number = preg_replace('/\D/', '', $card_number);
481
482 // from 51 to 55 (included) or 2221 to 2720 (included)
483 $ranges = array(
484 array(51, 55),
485 array(2221, 2720),
486 );
487
488 return self::matchBrandRanges($card_number, $ranges);
489 }
490
491 /**
492 * Checks if the card number matches an American Express.
493 *
494 * How to identify American Express:
495 * A credit card is an American Express when the first 2 digits are 34 or 37.
496 *
497 * e.g. 3434 000000 00000
498 * e.g. 3737 000000 00000
499 *
500 * @param string $card_number The card number.
501 *
502 * @return boolean True if the card number is an American Express, otherwise false.
503 *
504 * @uses matchBrandRanges()
505 */
506 public static function isAmericanExpress($card_number)
507 {
508 $card_number = preg_replace('/\D/', '', $card_number);
509
510 // 34 or 37
511 $ranges = array(
512 array(34),
513 array(37),
514 );
515
516 return self::matchBrandRanges($card_number, $ranges);
517 }
518
519 /**
520 * Check if the card number matches a Diners Club.
521 * @usedby CreditCard::getBrand()
522 *
523 * How to identify Diners Club:
524 * A credit card is a Diners Club when the first 3 digits are in the range [300 - 305] or 309
525 * or the first 2 digits are 36 or in the range [38 - 39].
526 *
527 * e.g. 3636 0000 0000 0000
528 * e.g. 3838 0000 0000 0000
529 * e.g. 3010 0000 0000 0000
530 *
531 * @param string $card_number The card number.
532 *
533 * @return boolean True if the card number is a Diners Club, otherwise false.
534 *
535 * @uses matchBrandRanges() Verify matches of this brand.
536 */
537 public static function isDinersClub($card_number)
538 {
539 $card_number = preg_replace('/\D/', '', $card_number);
540
541 // from 300 to 305 (included) or 309 or 36 or from 38 to 39 (included)
542 $ranges = array(
543 array(300, 305),
544 array(309),
545 array(36),
546 array(38, 39)
547 );
548
549 return self::matchBrandRanges($card_number, $ranges);
550 }
551
552 /**
553 * Check if the card number matches a Discover.
554 * @usedby CreditCard::getBrand()
555 *
556 * How to identify Discover:
557 * A credit card is a Discover when the first 4 digits are 6011
558 * or the first 2 digits are 65
559 * or the first 6 digits are in the range [622126 - 622925]
560 * or the first 3 digits are in the range [644 - 649].
561 *
562 * e.g. 6565 0000 0000 0000
563 * e.g. 6011 0000 0000 0000
564 * e.g. 6221 2900 0000 0000
565 *
566 * @param string $card_number The card number.
567 *
568 * @return boolean True if the card number is a Discover, otherwise false.
569 *
570 * @uses matchBrandRanges() Verify matches of this brand.
571 */
572 public static function isDiscover($card_number)
573 {
574 $card_number = preg_replace('/\D/', '', $card_number);
575
576 // 6011 or 65 or from 622126 to 622925 (included) or from 644 to 649 (included)
577 $ranges = array(
578 array(6011),
579 array(65),
580 array(622126, 622925),
581 array(644, 649)
582 );
583
584 return self::matchBrandRanges($card_number, $ranges);
585 }
586
587 /**
588 * Checks if the card number matches a JCB.
589 *
590 * How to identify JCB:
591 * A credit card is a JCB when the first 4 digits are in the range [3528 - 3589].
592 *
593 * e.g. 3535 0000 0000 0000
594 *
595 * @param string $card_number The card number.
596 *
597 * @return boolean True if the card number is a JCB, otherwise false.
598 *
599 * @uses matchBrandRanges()
600 */
601 public static function isJcb($card_number)
602 {
603 $card_number = preg_replace('/\D/', '', $card_number);
604
605 // from 3528 to 3589 (included)
606 $ranges = array(
607 array(3528, 3589),
608 );
609
610 return self::matchBrandRanges($card_number, $ranges);
611 }
612
613 /**
614 * Returns a list containing the alias of all the accepted brands.
615 *
616 * @return array A list with all the supported brand's aliases.
617 */
618 public static function getAllBrands()
619 {
620 return array(
621 CreditCard::VISA,
622 CreditCard::MASTER_CARD,
623 CreditCard::AMERICAN_EXPRESS,
624 CreditCard::DINERS_CLUB,
625 CreditCard::DISCOVER,
626 CreditCard::JCB,
627 );
628 }
629
630 /**
631 * Loads the file which declares the class handler of the specified brand.
632 *
633 * @param string $brand The alias of the brand.
634 *
635 * @return boolean True if the brand file is loaded correctly, otherwise false.
636 */
637 protected static function load($brand)
638 {
639 return VAPLoader::import('libraries.banking.brands.'.$brand);
640 }
641
642 /**
643 * Matches the card number with the provided brand card ranges.
644 *
645 * @param string $card_number The credit card number.
646 * @param array $ranges The list containing all the accepted brand ranges.
647 *
648 * @return boolean True if the credit card matches at least one range, otherwise false.
649 */
650 protected static function matchBrandRanges($card_number, $ranges = array())
651 {
652 foreach ($ranges as $r)
653 {
654 if (count($r) == 1)
655 {
656 if (intval(substr($card_number, 0, strlen($r[0]))) == $r[0])
657 {
658 return true;
659 }
660 }
661 else if (count($r) == 2)
662 {
663 $val = substr($card_number, 0, strlen($r[0]));
664
665 if ($r[0] <= $val && $val <= $r[1])
666 {
667 return true;
668 }
669 }
670 }
671
672 return false;
673 }
674
675 /**
676 * The VISA alias brand identifier.
677 *
678 * @var string
679 */
680 const VISA = 'visa';
681
682 /**
683 * The MASTER CARD alias brand identifier.
684 *
685 * @var string
686 */
687 const MASTER_CARD = 'mastercard';
688
689 /**
690 * The AMERICAN EXPRESS alias brand identifier.
691 *
692 * @var string
693 */
694 const AMERICAN_EXPRESS = 'amex';
695
696 /**
697 * The DINERS CLUB alias brand identifier.
698 *
699 * @var string
700 */
701 const DINERS_CLUB = 'diners';
702
703 /**
704 * The DISCOVER alias brand identifier.
705 *
706 * @var string
707 */
708 const DISCOVER = 'discover';
709
710 /**
711 * The JCB alias brand identifier.
712 *
713 * @var string
714 */
715 const JCB = 'jcb';
716 }
717