@@ -122,10 +122,124 @@ export const isValidEmailAddressFormat = (input: string) => {
122122export 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
130244export const isValidCreditCardExpirationDate = ( input : string ) => {
131245 // simple expiration date format check
0 commit comments