@@ -25,6 +25,7 @@ class PaymentCardBrand(str, Enum):
25
25
troy = 'Troy'
26
26
unionpay = 'UnionPay'
27
27
jcb = 'JCB'
28
+ diners_club = 'Diners Club'
28
29
other = 'other'
29
30
30
31
def __str__ (self ) -> str :
@@ -126,6 +127,77 @@ def validate_luhn_check_digit(cls, card_number: str) -> str:
126
127
raise PydanticCustomError ('payment_card_number_luhn' , 'Card number is not luhn valid' )
127
128
return card_number
128
129
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
+
129
201
@staticmethod
130
202
def validate_brand (card_number : str ) -> PaymentCardBrand :
131
203
"""Validate length based on
@@ -141,50 +213,7 @@ def validate_brand(card_number: str) -> PaymentCardBrand:
141
213
Raises:
142
214
PydanticCustomError: If the card number is not valid.
143
215
"""
144
- brand = PaymentCardBrand .other
145
-
146
- if card_number [0 ] == '4' :
147
- brand = PaymentCardBrand .visa
148
- required_length = [13 , 16 , 19 ]
149
- elif 51 <= int (card_number [:2 ]) <= 55 :
150
- brand = PaymentCardBrand .mastercard
151
- required_length = [16 ]
152
- elif card_number [:2 ] in {'34' , '37' }:
153
- brand = PaymentCardBrand .amex
154
- required_length = [15 ]
155
- elif 2200 <= int (card_number [:4 ]) <= 2204 :
156
- brand = PaymentCardBrand .mir
157
- required_length = list (range (16 , 20 ))
158
- elif card_number [:4 ] in {'5018' , '5020' , '5038' , '5893' , '6304' , '6759' , '6761' , '6762' , '6763' } or card_number [
159
- :6
160
- ] in (
161
- '676770' ,
162
- '676774' ,
163
- ):
164
- brand = PaymentCardBrand .maestro
165
- required_length = list (range (12 , 20 ))
166
- elif card_number .startswith ('65' ) or 644 <= int (card_number [:3 ]) <= 649 or card_number .startswith ('6011' ):
167
- brand = PaymentCardBrand .discover
168
- required_length = list (range (16 , 20 ))
169
- elif (
170
- 506099 <= int (card_number [:6 ]) <= 506198
171
- or 650002 <= int (card_number [:6 ]) <= 650027
172
- or 507865 <= int (card_number [:6 ]) <= 507964
173
- ):
174
- brand = PaymentCardBrand .verve
175
- required_length = [16 , 18 , 19 ]
176
- elif card_number [:4 ] in {'5019' , '4571' }:
177
- brand = PaymentCardBrand .dankort
178
- required_length = [16 ]
179
- elif card_number .startswith ('9792' ):
180
- brand = PaymentCardBrand .troy
181
- required_length = [16 ]
182
- elif card_number [:2 ] in {'62' , '81' }:
183
- brand = PaymentCardBrand .unionpay
184
- required_length = [16 , 19 ]
185
- elif 3528 <= int (card_number [:4 ]) <= 3589 :
186
- brand = PaymentCardBrand .jcb
187
- required_length = [16 , 19 ]
216
+ brand , required_length = PaymentCardNumber ._identify_brand (card_number )
188
217
189
218
valid = len (card_number ) in required_length if brand != PaymentCardBrand .other else True
190
219
0 commit comments