UNPKG

ajt-validator

Version:

Validation library for JavaScript and TypeScript

174 lines (173 loc) 7.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CreditCardValidator = exports.CardType = void 0; const base_1 = require("../base"); var CardType; (function (CardType) { CardType["VISA"] = "visa"; CardType["MASTERCARD"] = "mastercard"; CardType["AMEX"] = "amex"; CardType["DISCOVER"] = "discover"; CardType["DINERS"] = "diners"; CardType["JCB"] = "jcb"; CardType["UNKNOWN"] = "unknown"; })(CardType || (exports.CardType = CardType = {})); class CreditCardValidator extends base_1.BaseValidator { constructor(options = {}) { super(); // Card pattern definitions this.cardPatterns = { [CardType.VISA]: /^4[0-9]{12}(?:[0-9]{3})?$/, [CardType.MASTERCARD]: /^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/, [CardType.AMEX]: /^3[47][0-9]{13}$/, [CardType.DISCOVER]: /^6(?:011|5[0-9]{2})[0-9]{12}$/, [CardType.DINERS]: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, [CardType.JCB]: /^(?:2131|1800|35\d{3})\d{11}$/ }; // CVV length by card type this.cvvLengths = { [CardType.AMEX]: 4, default: 3 }; // Expiry date pattern (MM/YY or MM/YYYY) this.expiryPattern = /^(0[1-9]|1[0-2])\/([0-9]{2}|[0-9]{4})$/; this.options = Object.assign({ numberRequired: true, expiryRequired: true, cvvRequired: true, nameRequired: false, allowedCardTypes: Object.values(CardType).filter(type => type !== CardType.UNKNOWN), validateLuhn: true }, options); } validate(card) { var _a, _b, _c, _d; if (!card) { return this.createError('CREDIT_CARD_REQUIRED', 'Credit card data is required'); } // Card number validation if (this.options.numberRequired && !card.number) { return this.createError('CARD_NUMBER_REQUIRED', 'Card number is required'); } // Process the card number (remove spaces and dashes) const processedNumber = (_a = card.number) === null || _a === void 0 ? void 0 : _a.replace(/[\s-]/g, ''); // Default to unknown card type let cardType = CardType.UNKNOWN; if (processedNumber) { // Determine card type cardType = this.detectCardType(processedNumber); // Check if card type is allowed if (cardType !== CardType.UNKNOWN && this.options.allowedCardTypes && !this.options.allowedCardTypes.includes(cardType)) { return this.createError('CARD_TYPE_NOT_ALLOWED', `Card type ${cardType} is not accepted`); } // If card type is unknown but number is provided, it's an invalid format if (cardType === CardType.UNKNOWN) { return this.createError('INVALID_CARD_NUMBER_FORMAT', 'Card number format is not recognized'); } // Validate Luhn algorithm if enabled if (this.options.validateLuhn && !this.validateLuhnChecksum(processedNumber)) { return this.createError('INVALID_CARD_NUMBER_CHECKSUM', 'Card number failed checksum validation'); } } // Expiry date validation if (this.options.expiryRequired && !card.expiry) { return this.createError('EXPIRY_REQUIRED', 'Expiration date is required'); } if (card.expiry) { if (!this.expiryPattern.test(card.expiry)) { return this.createError('INVALID_EXPIRY_FORMAT', 'Expiration date must be in MM/YY or MM/YYYY format'); } if (!this.validateExpiryDate(card.expiry)) { return this.createError('EXPIRED_CARD', 'Credit card has expired'); } } // CVV validation if (this.options.cvvRequired && !card.cvv) { return this.createError('CVV_REQUIRED', 'CVV is required'); } if (card.cvv) { const requiredCvvLength = cardType === CardType.AMEX ? this.cvvLengths[CardType.AMEX] : this.cvvLengths.default; if (!/^\d+$/.test(card.cvv) || card.cvv.length !== requiredCvvLength) { return this.createError('INVALID_CVV', `CVV must be ${requiredCvvLength} digits for ${cardType} cards`); } } // Cardholder name validation if (this.options.nameRequired && !card.name) { return this.createError('CARDHOLDER_NAME_REQUIRED', 'Cardholder name is required'); } // Create result object that explicitly includes cardType const result = { number: this.maskCardNumber(processedNumber || ''), expiry: (_b = card.expiry) === null || _b === void 0 ? void 0 : _b.trim(), cvv: (_c = card.cvv) === null || _c === void 0 ? void 0 : _c.trim(), name: (_d = card.name) === null || _d === void 0 ? void 0 : _d.trim(), cardType: cardType }; return this.createSuccess(result); } /** * Detects the credit card type based on its number pattern */ detectCardType(cardNumber) { for (const [type, pattern] of Object.entries(this.cardPatterns)) { if (pattern.test(cardNumber)) { return type; } } return CardType.UNKNOWN; } /** * Validates a credit card number using the Luhn algorithm * The Luhn algorithm creates a checksum to help validate credit card numbers */ validateLuhnChecksum(cardNumber) { if (!cardNumber || !/^\d+$/.test(cardNumber)) { return false; } let sum = 0; let alternate = false; // Process from right to left for (let i = cardNumber.length - 1; i >= 0; i--) { let digit = parseInt(cardNumber.charAt(i)); // Double every second digit if (alternate) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; alternate = !alternate; } return sum % 10 === 0; } /** * Validates that the expiry date is not in the past */ validateExpiryDate(expiry) { const [month, yearStr] = expiry.split('/'); const year = yearStr.length === 2 ? '20' + yearStr : yearStr; const expiryDate = new Date(parseInt(year), parseInt(month)); const currentDate = new Date(); // Set to the last day of the month for comparison expiryDate.setDate(0); // Compare with current date return expiryDate >= currentDate; } /** * Masks the credit card number for security purposes * Returns format like: **** **** **** 1234 */ maskCardNumber(cardNumber) { if (!cardNumber || cardNumber.length < 4) { return cardNumber; } const lastFourDigits = cardNumber.slice(-4); const maskedPart = cardNumber.slice(0, -4).replace(/./g, '*'); // Format with spaces for readability let formatted = ''; const combined = maskedPart + lastFourDigits; for (let i = 0; i < combined.length; i += 4) { formatted += combined.substr(i, 4) + ' '; } return formatted.trim(); } } exports.CreditCardValidator = CreditCardValidator;