Skip to content

Commit 0bd59ad

Browse files
authored
New feature: bank card validations (#157)
* Add bank card validations * Fix linting
1 parent f8608f4 commit 0bd59ad

File tree

3 files changed

+386
-1
lines changed

3 files changed

+386
-1
lines changed

tests/test_card.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# -*- coding: utf-8 -*-
2+
import pytest
3+
4+
from validators import (
5+
amex,
6+
card_number,
7+
diners,
8+
discover,
9+
jcb,
10+
mastercard,
11+
unionpay,
12+
ValidationFailure,
13+
visa
14+
)
15+
16+
visa_cards = [
17+
'4242424242424242',
18+
'4000002760003184'
19+
]
20+
mastercard_cards = [
21+
'5555555555554444',
22+
'2223003122003222'
23+
]
24+
amex_cards = [
25+
'378282246310005',
26+
'371449635398431'
27+
]
28+
unionpay_cards = [
29+
'6200000000000005'
30+
]
31+
diners_cards = [
32+
'3056930009020004',
33+
'36227206271667'
34+
]
35+
jcb_cards = [
36+
'3566002020360505'
37+
]
38+
discover_cards = [
39+
'6011111111111117',
40+
'6011000990139424'
41+
]
42+
43+
44+
@pytest.mark.parametrize(
45+
"value",
46+
visa_cards
47+
+ mastercard_cards
48+
+ amex_cards
49+
+ unionpay_cards
50+
+ diners_cards
51+
+ jcb_cards
52+
+ discover_cards,
53+
)
54+
def test_returns_true_on_valid_card_number(value):
55+
assert card_number(value)
56+
57+
58+
@pytest.mark.parametrize('value', [
59+
'4242424242424240',
60+
'4000002760003180',
61+
'400000276000318X'
62+
])
63+
def test_returns_failed_on_valid_card_number(value):
64+
assert isinstance(card_number(value), ValidationFailure)
65+
66+
67+
@pytest.mark.parametrize('value', visa_cards)
68+
def test_returns_true_on_valid_visa(value):
69+
assert visa(value)
70+
71+
72+
@pytest.mark.parametrize(
73+
"value",
74+
mastercard_cards
75+
+ amex_cards
76+
+ unionpay_cards
77+
+ diners_cards
78+
+ jcb_cards
79+
+ discover_cards,
80+
)
81+
def test_returns_failed_on_valid_visa(value):
82+
assert isinstance(visa(value), ValidationFailure)
83+
84+
85+
@pytest.mark.parametrize('value', mastercard_cards)
86+
def test_returns_true_on_valid_mastercard(value):
87+
assert mastercard(value)
88+
89+
90+
@pytest.mark.parametrize(
91+
"value",
92+
visa_cards
93+
+ amex_cards
94+
+ unionpay_cards
95+
+ diners_cards
96+
+ jcb_cards
97+
+ discover_cards,
98+
)
99+
def test_returns_failed_on_valid_mastercard(value):
100+
assert isinstance(mastercard(value), ValidationFailure)
101+
102+
103+
@pytest.mark.parametrize('value', amex_cards)
104+
def test_returns_true_on_valid_amex(value):
105+
assert amex(value)
106+
107+
108+
@pytest.mark.parametrize(
109+
"value",
110+
visa_cards
111+
+ mastercard_cards
112+
+ unionpay_cards
113+
+ diners_cards
114+
+ jcb_cards
115+
+ discover_cards,
116+
)
117+
def test_returns_failed_on_valid_amex(value):
118+
assert isinstance(amex(value), ValidationFailure)
119+
120+
121+
@pytest.mark.parametrize('value', unionpay_cards)
122+
def test_returns_true_on_valid_unionpay(value):
123+
assert unionpay(value)
124+
125+
126+
@pytest.mark.parametrize(
127+
"value",
128+
visa_cards
129+
+ mastercard_cards
130+
+ amex_cards
131+
+ diners_cards
132+
+ jcb_cards
133+
+ discover_cards,
134+
)
135+
def test_returns_failed_on_valid_unionpay(value):
136+
assert isinstance(unionpay(value), ValidationFailure)
137+
138+
139+
@pytest.mark.parametrize('value', diners_cards)
140+
def test_returns_true_on_valid_diners(value):
141+
assert diners(value)
142+
143+
144+
@pytest.mark.parametrize(
145+
"value",
146+
visa_cards
147+
+ mastercard_cards
148+
+ amex_cards
149+
+ unionpay_cards
150+
+ jcb_cards
151+
+ discover_cards,
152+
)
153+
def test_returns_failed_on_valid_diners(value):
154+
assert isinstance(diners(value), ValidationFailure)
155+
156+
157+
@pytest.mark.parametrize('value', jcb_cards)
158+
def test_returns_true_on_valid_jcb(value):
159+
assert jcb(value)
160+
161+
162+
@pytest.mark.parametrize(
163+
"value",
164+
visa_cards
165+
+ mastercard_cards
166+
+ amex_cards
167+
+ unionpay_cards
168+
+ diners_cards
169+
+ discover_cards,
170+
)
171+
def test_returns_failed_on_valid_jcb(value):
172+
assert isinstance(jcb(value), ValidationFailure)
173+
174+
175+
@pytest.mark.parametrize('value', discover_cards)
176+
def test_returns_true_on_valid_discover(value):
177+
assert discover(value)
178+
179+
180+
@pytest.mark.parametrize(
181+
"value",
182+
visa_cards
183+
+ mastercard_cards
184+
+ amex_cards
185+
+ unionpay_cards
186+
+ diners_cards
187+
+ jcb_cards,
188+
)
189+
def test_returns_failed_on_valid_discover(value):
190+
assert isinstance(discover(value), ValidationFailure)

validators/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
from .between import between
2+
from .card import (
3+
amex,
4+
card_number,
5+
diners,
6+
discover,
7+
jcb,
8+
mastercard,
9+
unionpay,
10+
visa
11+
)
212
from .domain import domain
313
from .email import email
414
from .extremes import Max, Min
@@ -17,6 +27,8 @@
1727
__all__ = ('between', 'domain', 'email', 'Max', 'Min', 'md5', 'sha1', 'sha224',
1828
'sha256', 'sha512', 'fi_business_id', 'fi_ssn', 'iban', 'ipv4',
1929
'ipv4_cidr', 'ipv6', 'ipv6_cidr', 'length', 'mac_address', 'slug',
20-
'truthy', 'url', 'ValidationFailure', 'validator', 'uuid')
30+
'truthy', 'url', 'ValidationFailure', 'validator', 'uuid',
31+
'card_number', 'visa', 'mastercard', 'amex', 'unionpay', 'diners',
32+
'jcb', 'discover')
2133

2234
__version__ = '0.14.3'

validators/card.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import re
2+
3+
from .utils import validator
4+
5+
6+
@validator
7+
def card_number(value):
8+
"""
9+
Return whether or not given value is a valid card number.
10+
11+
This validator is based on Luhn algorithm.
12+
13+
.. luhn:
14+
https://github.com/mmcloughlin/luhn
15+
16+
Examples::
17+
18+
>>> card_number('4242424242424242')
19+
True
20+
21+
>>> card_number('4242424242424241')
22+
ValidationFailure(func=card_number, args={'value': '4242424242424241'})
23+
24+
.. versionadded:: 0.2
25+
26+
:param value: card number string to validate
27+
"""
28+
try:
29+
digits = list(map(int, value))
30+
odd_sum = sum(digits[-1::-2])
31+
even_sum = sum([sum(divmod(2 * d, 10)) for d in digits[-2::-2]])
32+
return (odd_sum + even_sum) % 10 == 0
33+
except ValueError:
34+
return False
35+
36+
37+
@validator
38+
def visa(value):
39+
"""
40+
Return whether or not given value is a valid Visa card number.
41+
42+
Examples::
43+
44+
>>> visa('4242424242424242')
45+
True
46+
47+
>>> visa('2223003122003222')
48+
ValidationFailure(func=visa, args={'value': '2223003122003222'})
49+
50+
.. versionadded:: 0.2
51+
52+
:param value: Visa card number string to validate
53+
"""
54+
pattern = re.compile(r'^4')
55+
return card_number(value) and len(value) == 16 and pattern.match(value)
56+
57+
58+
@validator
59+
def mastercard(value):
60+
"""
61+
Return whether or not given value is a valid Mastercard card number.
62+
63+
Examples::
64+
65+
>>> mastercard('5555555555554444')
66+
True
67+
68+
>>> mastercard('4242424242424242')
69+
ValidationFailure(func=mastercard, args={'value': '4242424242424242'})
70+
71+
.. versionadded:: 0.2
72+
73+
:param value: Mastercard card number string to validate
74+
"""
75+
pattern = re.compile(r'^(51|52|53|54|55|22|23|24|25|26|27)')
76+
return card_number(value) and len(value) == 16 and pattern.match(value)
77+
78+
79+
@validator
80+
def amex(value):
81+
"""
82+
Return whether or not given value is a valid American Express card number.
83+
84+
Examples::
85+
86+
>>> amex('378282246310005')
87+
True
88+
89+
>>> amex('4242424242424242')
90+
ValidationFailure(func=amex, args={'value': '4242424242424242'})
91+
92+
.. versionadded:: 0.2
93+
94+
:param value: American Express card number string to validate
95+
"""
96+
pattern = re.compile(r'^(34|37)')
97+
return card_number(value) and len(value) == 15 and pattern.match(value)
98+
99+
100+
@validator
101+
def unionpay(value):
102+
"""
103+
Return whether or not given value is a valid UnionPay card number.
104+
105+
Examples::
106+
107+
>>> unionpay('6200000000000005')
108+
True
109+
110+
>>> unionpay('4242424242424242')
111+
ValidationFailure(func=unionpay, args={'value': '4242424242424242'})
112+
113+
.. versionadded:: 0.2
114+
115+
:param value: UnionPay card number string to validate
116+
"""
117+
pattern = re.compile(r'^62')
118+
return card_number(value) and len(value) == 16 and pattern.match(value)
119+
120+
121+
@validator
122+
def diners(value):
123+
"""
124+
Return whether or not given value is a valid Diners Club card number.
125+
126+
Examples::
127+
128+
>>> diners('3056930009020004')
129+
True
130+
131+
>>> diners('4242424242424242')
132+
ValidationFailure(func=diners, args={'value': '4242424242424242'})
133+
134+
.. versionadded:: 0.2
135+
136+
:param value: Diners Club card number string to validate
137+
"""
138+
pattern = re.compile(r'^(30|36|38|39)')
139+
return (
140+
card_number(value) and len(value) in [14, 16] and pattern.match(value)
141+
)
142+
143+
144+
@validator
145+
def jcb(value):
146+
"""
147+
Return whether or not given value is a valid JCB card number.
148+
149+
Examples::
150+
151+
>>> jcb('3566002020360505')
152+
True
153+
154+
>>> jcb('4242424242424242')
155+
ValidationFailure(func=jcb, args={'value': '4242424242424242'})
156+
157+
.. versionadded:: 0.2
158+
159+
:param value: JCB card number string to validate
160+
"""
161+
pattern = re.compile(r'^35')
162+
return card_number(value) and len(value) == 16 and pattern.match(value)
163+
164+
165+
@validator
166+
def discover(value):
167+
"""
168+
Return whether or not given value is a valid Discover card number.
169+
170+
Examples::
171+
172+
>>> discover('6011111111111117')
173+
True
174+
175+
>>> discover('4242424242424242')
176+
ValidationFailure(func=discover, args={'value': '4242424242424242'})
177+
178+
.. versionadded:: 0.2
179+
180+
:param value: Discover card number string to validate
181+
"""
182+
pattern = re.compile(r'^(60|64|65)')
183+
return card_number(value) and len(value) == 16 and pattern.match(value)

0 commit comments

Comments
 (0)