@@ -127,6 +127,77 @@ def validate_luhn_check_digit(cls, card_number: str) -> str:
127127 raise PydanticCustomError ('payment_card_number_luhn' , 'Card number is not luhn valid' )
128128 return card_number
129129
130+ @classmethod
131+ def _identify_brand (cls , card_number : str ) -> tuple [PaymentCardBrand , list [int ]]:
132+ """Identify the brand and required length for a card number.
133+
134+ Args:
135+ card_number: The card number to identify.
136+
137+ Returns:
138+ A tuple of (brand, required_length)
139+ """
140+ # VISA
141+ if card_number [0 ] == '4' :
142+ return PaymentCardBrand .visa , [13 , 16 , 19 ]
143+
144+ # Mastercard
145+ if (51 <= int (card_number [:2 ]) <= 55 ) or (2221 <= int (card_number [:4 ]) <= 2720 ):
146+ return PaymentCardBrand .mastercard , [16 ]
147+
148+ # American Express
149+ if card_number [:2 ] in {'34' , '37' }:
150+ return PaymentCardBrand .amex , [15 ]
151+
152+ # MIR
153+ if 2200 <= int (card_number [:4 ]) <= 2204 :
154+ return PaymentCardBrand .mir , list (range (16 , 20 ))
155+
156+ # Maestro
157+ if card_number [:4 ] in {'5018' , '5020' , '5038' , '5893' , '6304' , '6759' , '6761' , '6762' , '6763' } or card_number [
158+ :6
159+ ] in ('676770' , '676774' ):
160+ return PaymentCardBrand .maestro , list (range (12 , 20 ))
161+
162+ # Discover
163+ if card_number .startswith ('65' ) or 644 <= int (card_number [:3 ]) <= 649 or card_number .startswith ('6011' ):
164+ return PaymentCardBrand .discover , list (range (16 , 20 ))
165+
166+ # Verve
167+ if (
168+ 506099 <= int (card_number [:6 ]) <= 506198
169+ or 650002 <= int (card_number [:6 ]) <= 650027
170+ or 507865 <= int (card_number [:6 ]) <= 507964
171+ ):
172+ return PaymentCardBrand .verve , [16 , 18 , 19 ]
173+
174+ # Dankort
175+ if card_number [:4 ] in {'5019' , '4571' }:
176+ return PaymentCardBrand .dankort , [16 ]
177+
178+ # Troy
179+ if card_number .startswith ('9792' ):
180+ return PaymentCardBrand .troy , [16 ]
181+
182+ # UnionPay
183+ if card_number [:2 ] in {'62' , '81' }:
184+ return PaymentCardBrand .unionpay , [16 , 19 ]
185+
186+ # JCB
187+ if 3528 <= int (card_number [:4 ]) <= 3589 :
188+ return PaymentCardBrand .jcb , [16 , 19 ]
189+
190+ # Diners Club
191+ if card_number [:2 ] in {'30' , '36' , '38' , '39' }:
192+ return PaymentCardBrand .diners_club , list (range (14 , 20 ))
193+
194+ # More Diners Club
195+ if card_number .startswith ('55' ):
196+ return PaymentCardBrand .diners_club , [16 ]
197+
198+ # Other / Unknown
199+ return PaymentCardBrand .other , []
200+
130201 @staticmethod
131202 def validate_brand (card_number : str ) -> PaymentCardBrand :
132203 """Validate length based on
@@ -142,56 +213,7 @@ def validate_brand(card_number: str) -> PaymentCardBrand:
142213 Raises:
143214 PydanticCustomError: If the card number is not valid.
144215 """
145- brand = PaymentCardBrand .other
146-
147- if card_number [0 ] == '4' :
148- brand = PaymentCardBrand .visa
149- required_length = [13 , 16 , 19 ]
150- elif (51 <= int (card_number [:2 ]) <= 55 ) or (2221 <= int (card_number [:4 ]) <= 2720 ):
151- brand = PaymentCardBrand .mastercard
152- required_length = [16 ]
153- elif card_number [:2 ] in {'34' , '37' }:
154- brand = PaymentCardBrand .amex
155- required_length = [15 ]
156- elif 2200 <= int (card_number [:4 ]) <= 2204 :
157- brand = PaymentCardBrand .mir
158- required_length = list (range (16 , 20 ))
159- elif card_number [:4 ] in {'5018' , '5020' , '5038' , '5893' , '6304' , '6759' , '6761' , '6762' , '6763' } or card_number [
160- :6
161- ] in (
162- '676770' ,
163- '676774' ,
164- ):
165- brand = PaymentCardBrand .maestro
166- required_length = list (range (12 , 20 ))
167- elif card_number .startswith ('65' ) or 644 <= int (card_number [:3 ]) <= 649 or card_number .startswith ('6011' ):
168- brand = PaymentCardBrand .discover
169- required_length = list (range (16 , 20 ))
170- elif (
171- 506099 <= int (card_number [:6 ]) <= 506198
172- or 650002 <= int (card_number [:6 ]) <= 650027
173- or 507865 <= int (card_number [:6 ]) <= 507964
174- ):
175- brand = PaymentCardBrand .verve
176- required_length = [16 , 18 , 19 ]
177- elif card_number [:4 ] in {'5019' , '4571' }:
178- brand = PaymentCardBrand .dankort
179- required_length = [16 ]
180- elif card_number .startswith ('9792' ):
181- brand = PaymentCardBrand .troy
182- required_length = [16 ]
183- elif card_number [:2 ] in {'62' , '81' }:
184- brand = PaymentCardBrand .unionpay
185- required_length = [16 , 19 ]
186- elif 3528 <= int (card_number [:4 ]) <= 3589 :
187- brand = PaymentCardBrand .jcb
188- required_length = [16 , 19 ]
189- elif card_number [:2 ] in {'30' , '36' , '38' , '39' }:
190- brand = PaymentCardBrand .diners_club
191- required_length = list (range (14 , 20 ))
192- elif card_number .startswith ('55' ):
193- brand = PaymentCardBrand .diners_club
194- required_length = [16 ]
216+ brand , required_length = PaymentCardNumber ._identify_brand (card_number )
195217
196218 valid = len (card_number ) in required_length if brand != PaymentCardBrand .other else True
197219
0 commit comments