Skip to content

Commit 40179dd

Browse files
committed
implemented robust credit card validation with Luhn algorithm check
1 parent 21aeab4 commit 40179dd

File tree

1 file changed

+118
-4
lines changed

1 file changed

+118
-4
lines changed

lib/utils.ts

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,124 @@ export const isValidEmailAddressFormat = (input: string) => {
122122
export const isValidCardNumber = (input: string) => {
123123
// Remove all non-digit characters
124124
const cleanedInput = input.replace(/[^0-9]/g, "");
125-
// test for credit card number between 13 and 19 characters
126-
const regex = /^\d{13,19}$/;
127-
return regex.test(cleanedInput);
128-
}
125+
126+
// Check if the cleaned input has valid length (13-19 digits)
127+
if (!/^\d{13,19}$/.test(cleanedInput)) {
128+
return false;
129+
}
130+
131+
// Implement Luhn algorithm for credit card validation
132+
return luhnCheck(cleanedInput);
133+
};
134+
135+
/**
136+
* Luhn algorithm implementation for credit card validation
137+
* @param cardNumber - The credit card number as a string
138+
* @returns boolean - true if the card number is valid according to Luhn algorithm
139+
*/
140+
const luhnCheck = (cardNumber: string): boolean => {
141+
let sum = 0;
142+
let isEven = false;
143+
144+
// Process digits from right to left
145+
for (let i = cardNumber.length - 1; i >= 0; i--) {
146+
let digit = parseInt(cardNumber[i], 10);
147+
148+
if (isEven) {
149+
digit *= 2;
150+
if (digit > 9) {
151+
digit -= 9;
152+
}
153+
}
154+
155+
sum += digit;
156+
isEven = !isEven;
157+
}
158+
159+
return sum % 10 === 0;
160+
};
161+
162+
/**
163+
* Enhanced credit card validation with card type detection
164+
* @param input - The credit card number as a string
165+
* @returns object with validation result and card type
166+
*/
167+
export const validateCreditCard = (input: string) => {
168+
const cleanedInput = input.replace(/[^0-9]/g, "");
169+
170+
// Basic length and format check
171+
if (!/^\d{13,19}$/.test(cleanedInput)) {
172+
return {
173+
isValid: false,
174+
cardType: 'unknown',
175+
error: 'Invalid card number format'
176+
};
177+
}
178+
179+
// Luhn algorithm check
180+
if (!luhnCheck(cleanedInput)) {
181+
return {
182+
isValid: false,
183+
cardType: 'unknown',
184+
error: 'Invalid card number (Luhn check failed)'
185+
};
186+
}
187+
188+
// Detect card type based on BIN (Bank Identification Number)
189+
const cardType = detectCardType(cleanedInput);
190+
191+
return {
192+
isValid: true,
193+
cardType,
194+
error: null
195+
};
196+
};
197+
198+
/**
199+
* Detect credit card type based on BIN patterns
200+
* @param cardNumber - The credit card number as a string
201+
* @returns string - The detected card type
202+
*/
203+
const detectCardType = (cardNumber: string): string => {
204+
const firstDigit = cardNumber[0];
205+
const firstTwoDigits = cardNumber.substring(0, 2);
206+
const firstFourDigits = cardNumber.substring(0, 4);
207+
const firstThreeDigits = cardNumber.substring(0, 3);
208+
209+
// Visa: starts with 4
210+
if (firstDigit === '4') {
211+
return 'visa';
212+
}
213+
214+
// Mastercard: starts with 5 or 2
215+
if (firstDigit === '5' || (firstTwoDigits >= '22' && firstTwoDigits <= '27')) {
216+
return 'mastercard';
217+
}
218+
219+
// American Express: starts with 34 or 37
220+
if (firstTwoDigits === '34' || firstTwoDigits === '37') {
221+
return 'amex';
222+
}
223+
224+
// Discover: starts with 6011, 65, or 644-649
225+
if (firstFourDigits === '6011' || firstTwoDigits === '65' ||
226+
(firstThreeDigits >= '644' && firstThreeDigits <= '649')) {
227+
return 'discover';
228+
}
229+
230+
// Diners Club: starts with 300-305, 36, or 38
231+
if ((firstThreeDigits >= '300' && firstThreeDigits <= '305') ||
232+
firstTwoDigits === '36' || firstTwoDigits === '38') {
233+
return 'diners';
234+
}
235+
236+
// JCB: starts with 35
237+
if (firstTwoDigits === '35') {
238+
return 'jcb';
239+
}
240+
241+
return 'unknown';
242+
};
129243

130244
export const isValidCreditCardExpirationDate = (input: string) => {
131245
// simple expiration date format check

0 commit comments

Comments
 (0)