Skip to content

Commit

Permalink
♻️ refactor the validate_brand method & add new types (#56)
Browse files Browse the repository at this point in the history
* ♻️ refactor the `validate_brand` method & add new types

* use `.startswith()`

* bump coverage
  • Loading branch information
yezz123 authored Jan 8, 2024
1 parent 5ebc5bb commit 0705e6d
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 20 deletions.
66 changes: 47 additions & 19 deletions pydantic_extra_types/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ class PaymentCardBrand(str, Enum):
mastercard = 'Mastercard'
visa = 'Visa'
mir = 'Mir'
maestro = 'Maestro'
discover = 'Discover'
verve = 'Verve'
dankort = 'Dankort'
troy = 'Troy'
unionpay = 'UnionPay'
jcb = 'JCB'
other = 'other'

def __str__(self) -> str:
Expand Down Expand Up @@ -135,37 +142,58 @@ def validate_brand(card_number: str) -> PaymentCardBrand:
Raises:
PydanticCustomError: If the card number is not valid.
"""
brand = PaymentCardBrand.other

if card_number[0] == '4':
brand = PaymentCardBrand.visa
required_length = [13, 16, 19]
elif 51 <= int(card_number[:2]) <= 55:
brand = PaymentCardBrand.mastercard
required_length = [16]
elif card_number[:2] in {'34', '37'}:
brand = PaymentCardBrand.amex
required_length = [15]
elif 2200 <= int(card_number[:4]) <= 2204:
brand = PaymentCardBrand.mir
else:
brand = PaymentCardBrand.other

required_length: None | int | str = None
if brand in PaymentCardBrand.mastercard:
required_length = 16
valid = len(card_number) == required_length
elif brand == PaymentCardBrand.visa:
required_length = '13, 16 or 19'
valid = len(card_number) in {13, 16, 19}
elif brand == PaymentCardBrand.amex:
required_length = 15
valid = len(card_number) == required_length
elif brand == PaymentCardBrand.mir:
required_length = 'in range from 16 to 19'
valid = len(card_number) in range(16, 20)
else:
valid = True
required_length = list(range(16, 20))
elif card_number[:4] in {'5018', '5020', '5038', '5893', '6304', '6759', '6761', '6762', '6763'} or card_number[
:6
] in (
'676770',
'676774',
):
brand = PaymentCardBrand.maestro
required_length = list(range(12, 20))
elif card_number.startswith('65') or 644 <= int(card_number[:3]) <= 649 or card_number.startswith('6011'):
brand = PaymentCardBrand.discover
required_length = list(range(16, 20))
elif (
506099 <= int(card_number[:6]) <= 506198
or 650002 <= int(card_number[:6]) <= 650027
or 507865 <= int(card_number[:6]) <= 507964
):
brand = PaymentCardBrand.verve
required_length = [16, 18, 19]
elif card_number[:4] in {'5019', '4571'}:
brand = PaymentCardBrand.dankort
required_length = [16]
elif card_number.startswith('9792'):
brand = PaymentCardBrand.troy
required_length = [16]
elif card_number[:2] in {'62', '81'}:
brand = PaymentCardBrand.unionpay
required_length = [16, 19]
elif 3528 <= int(card_number[:4]) <= 3589:
brand = PaymentCardBrand.jcb
required_length = [16, 19]

valid = len(card_number) in required_length if brand != PaymentCardBrand.other else True

if not valid:
raise PydanticCustomError(
'payment_card_number_brand',
'Length for a {brand} card must be {required_length}',
f'Length for a {brand} card must be {" or ".join(map(str, required_length))}',
{'brand': brand, 'required_length': required_length},
)

return brand
31 changes: 30 additions & 1 deletion tests/test_types_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
VALID_MIR_17 = '22000000000000004'
VALID_MIR_18 = '220000000000000004'
VALID_MIR_19 = '2200000000000000004'
VALID_DISCOVER = '6011000000000004'
VALID_VERVE_16 = '5061000000000001'
VALID_VERVE_18 = '506100000000000001'
VALID_VERVE_19 = '5061000000000000001'
VALID_DANKORT = '5019000000000000'
VALID_UNIONPAY_16 = '6200000000000001'
VALID_UNIONPAY_19 = '8100000000000000001'
VALID_JCB_16 = '3528000000000001'
VALID_JCB_19 = '3528000000000000001'
VALID_MAESTRO = '6759649826438453'
VALID_TROY = '9792000000000001'
VALID_OTHER = '2000000000000000008'
LUHN_INVALID = '4000000000000000'
LEN_INVALID = '40000000000000006'
Expand Down Expand Up @@ -94,8 +105,19 @@ def test_validate_luhn_check_digit(card_number: str, valid: bool):
(VALID_MIR_17, PaymentCardBrand.mir, True),
(VALID_MIR_18, PaymentCardBrand.mir, True),
(VALID_MIR_19, PaymentCardBrand.mir, True),
(VALID_OTHER, PaymentCardBrand.other, True),
(VALID_DISCOVER, PaymentCardBrand.discover, True),
(VALID_VERVE_16, PaymentCardBrand.verve, True),
(VALID_VERVE_18, PaymentCardBrand.verve, True),
(VALID_VERVE_19, PaymentCardBrand.verve, True),
(VALID_DANKORT, PaymentCardBrand.dankort, True),
(VALID_UNIONPAY_16, PaymentCardBrand.unionpay, True),
(VALID_UNIONPAY_19, PaymentCardBrand.unionpay, True),
(VALID_JCB_16, PaymentCardBrand.jcb, True),
(VALID_JCB_19, PaymentCardBrand.jcb, True),
(LEN_INVALID, PaymentCardBrand.visa, False),
(VALID_MAESTRO, PaymentCardBrand.maestro, True),
(VALID_TROY, PaymentCardBrand.troy, True),
(VALID_OTHER, PaymentCardBrand.other, True),
],
)
def test_length_for_brand(card_number: str, brand: PaymentCardBrand, valid: bool):
Expand All @@ -115,7 +137,14 @@ def test_length_for_brand(card_number: str, brand: PaymentCardBrand, valid: bool
(VALID_MC, PaymentCardBrand.mastercard),
(VALID_VISA_16, PaymentCardBrand.visa),
(VALID_MIR_16, PaymentCardBrand.mir),
(VALID_DISCOVER, PaymentCardBrand.discover),
(VALID_VERVE_16, PaymentCardBrand.verve),
(VALID_DANKORT, PaymentCardBrand.dankort),
(VALID_UNIONPAY_16, PaymentCardBrand.unionpay),
(VALID_JCB_16, PaymentCardBrand.jcb),
(VALID_OTHER, PaymentCardBrand.other),
(VALID_MAESTRO, PaymentCardBrand.maestro),
(VALID_TROY, PaymentCardBrand.troy),
],
)
def test_get_brand(card_number: str, brand: PaymentCardBrand):
Expand Down

0 comments on commit 0705e6d

Please sign in to comment.