Skip to content

Commit 0ce8e4f

Browse files
authored
Merge pull request #91 from maxmind/nlogan/8-digit-iin-2-digit-lastdigits
Add support for 8 digit IINs and 2 digit last_digits
2 parents 305e687 + 7702fd7 commit 0ce8e4f

7 files changed

Lines changed: 104 additions & 11 deletions

File tree

HISTORY.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ History
2323
* ``payvision``
2424
* ``trustly``
2525
* ``windcave``
26+
* The ``/credit_card/last_4_digits`` input has been deprecated in favor of
27+
``/credit_card/last_digits`` and will be removed in a future release.
28+
``last_digits``/``last_4_digits`` also now supports two digit values in
29+
addition to the previous four digit values.
30+
* Eight digit ``/credit_card/issuer_id_number`` inputs are now supported in
31+
addition to the previously accepted six digit ``issuer_id_number``. In most
32+
cases, you should send the last four digits for ``last_digits``. If you send
33+
an ``issuer_id_number`` that contains an eight digit IIN, and if the credit
34+
card brand is not one of the following, you should send the last two digits
35+
for ``last_digits``:
36+
* ``Discover``
37+
* ``JCB``
38+
* ``Mastercard``
39+
* ``UnionPay``
40+
* ``Visa``
2641

2742
2.5.0 (2021-09-20)
2843
++++++++++++++++++

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,10 @@ Score, Insights and Factors Example
191191
>>> 'bank_phone_country_code': '1',
192192
>>> 'avs_result': 'Y',
193193
>>> 'bank_phone_number': '123-456-1234',
194-
>>> 'last_4_digits': '7643',
194+
>>> 'last_digits': '7643',
195195
>>> 'cvv_result': 'N',
196196
>>> 'bank_name': 'Bank of No Hope',
197-
>>> 'issuer_id_number': '411111'
197+
>>> 'issuer_id_number': '411111',
198198
>>> 'was_3d_secure_successful': True
199199
>>> },
200200
>>> 'payment': {

minfraud/request.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
"""
77

8+
import warnings
89
import hashlib
910
from typing import Any, Dict
1011
from voluptuous import MultipleInvalid
@@ -53,6 +54,9 @@ def prepare_transaction(
5354
if hash_email:
5455
maybe_hash_email(cleaned_request)
5556

57+
if cleaned_request.get("credit_card", None):
58+
clean_credit_card(cleaned_request)
59+
5660
return cleaned_request
5761

5862

@@ -65,6 +69,17 @@ def _copy_and_clean(data: Any) -> Any:
6569
return data
6670

6771

72+
def clean_credit_card(credit_card):
73+
"""Clean the credit_card input of a transaction request"""
74+
last4 = credit_card.pop("last_4_digits", None)
75+
if last4:
76+
warnings.warn(
77+
"last_4_digits has been deprecated in favor of last_digits",
78+
DeprecationWarning,
79+
)
80+
credit_card["last_digits"] = last4
81+
82+
6883
def maybe_hash_email(transaction):
6984
"""Hash email address in transaction, if present"""
7085
try:

minfraud/validation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
88
"""
99

10-
1110
import ipaddress
1211
import re
1312
import uuid
@@ -250,9 +249,9 @@ def _hostname(hostname: str) -> str:
250249

251250
_single_char = Match("^[A-Za-z0-9]$")
252251

253-
_iin = Match("^[0-9]{6}$")
252+
_iin = Match("^(?:[0-9]{6}|[0-9]{8})$")
254253

255-
_credit_card_last_4 = Match("^[0-9]{4}$")
254+
_credit_card_last_digits = Match("^(?:[0-9]{2}|[0-9]{4})$")
256255

257256

258257
def _credit_card_token(s: str) -> str:
@@ -311,7 +310,8 @@ def _uri(s: str) -> str:
311310
"bank_phone_number": str,
312311
"cvv_result": _single_char,
313312
"issuer_id_number": _iin,
314-
"last_4_digits": _credit_card_last_4,
313+
"last_digits": _credit_card_last_digits,
314+
"last_4_digits": _credit_card_last_digits,
315315
"token": _credit_card_token,
316316
"was_3d_secure_successful": bool,
317317
},

tests/data/full-transaction-request.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
},
4848
"credit_card": {
4949
"issuer_id_number": "411111",
50-
"last_4_digits": "7643",
50+
"last_digits": "7643",
5151
"bank_name": "Bank of No Hope",
5252
"bank_phone_country_code": "1",
5353
"bank_phone_number": "123-456-1234",

tests/test_request.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import unittest
22

3-
from minfraud.request import maybe_hash_email
3+
from minfraud.request import maybe_hash_email, clean_credit_card
44

55

66
class TestRequest(unittest.TestCase):
@@ -146,3 +146,48 @@ def test_maybe_hash_email(self):
146146
maybe_hash_email(transaction)
147147

148148
self.assertEqual(test["expected"], transaction)
149+
150+
def test_clean_credit_card(self):
151+
tests = [
152+
{
153+
"name": "deprecated last_4_digits is cleaned to last_digits",
154+
"input": {
155+
"issuer_id_number": "123456",
156+
"last_4_digits": "1234",
157+
},
158+
"expected": {
159+
"issuer_id_number": "123456",
160+
"last_digits": "1234",
161+
},
162+
},
163+
{
164+
"name": "6 digit iin, 4 digit last_digits",
165+
"input": {
166+
"issuer_id_number": "123456",
167+
"last_digits": "1234",
168+
},
169+
"expected": {
170+
"issuer_id_number": "123456",
171+
"last_digits": "1234",
172+
},
173+
},
174+
{
175+
"name": "8 digit iin, 2 digit last_digits",
176+
"input": {
177+
"issuer_id_number": "12345678",
178+
"last_digits": "34",
179+
},
180+
"expected": {
181+
"issuer_id_number": "12345678",
182+
"last_digits": "34",
183+
},
184+
},
185+
]
186+
187+
for test in tests:
188+
with self.subTest(test["name"]):
189+
transaction = test["input"]
190+
191+
clean_credit_card(transaction)
192+
193+
self.assertEqual(test["expected"], transaction)

tests/test_validation.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,34 @@ def test_delivery_speed(self):
143143

144144
class TestCreditCard(ValidationBase, unittest.TestCase):
145145
def test_issuer_id_number(self):
146-
for iin in ("123456", "532313"):
146+
for iin in ("123456", "532313", "88888888"):
147147
self.check_transaction({"credit_card": {"issuer_id_number": iin}})
148148
for invalid in ("12345", "1234567", 123456, "12345a"):
149149
self.check_invalid_transaction(
150150
{"credit_card": {"issuer_id_number": invalid}}
151151
)
152152

153+
def test_last_digits(self):
154+
for last_digits in ("1234", "9323", "34"):
155+
self.check_transaction({"credit_card": {"last_digits": last_digits}})
156+
for invalid in ("12345", "123", 1234, "123a"):
157+
self.check_invalid_transaction({"credit_card": {"last_digits": invalid}})
158+
self.check_transaction(
159+
{"credit_card": {"issuer_id_number": "88888888", "last_digits": "12"}}
160+
)
161+
self.check_transaction(
162+
{"credit_card": {"issuer_id_number": "88888888", "last_digits": "1234"}}
163+
)
164+
self.check_transaction(
165+
{"credit_card": {"issuer_id_number": "666666", "last_digits": "1234"}}
166+
)
167+
self.check_transaction(
168+
{"credit_card": {"issuer_id_number": "666666", "last_digits": "34"}}
169+
)
170+
153171
def test_last_4_digits(self):
154-
for iin in ("1234", "9323"):
155-
self.check_transaction({"credit_card": {"last_4_digits": iin}})
172+
for last_digits in ("1234", "9323", "34"):
173+
self.check_transaction({"credit_card": {"last_4_digits": last_digits}})
156174
for invalid in ("12345", "123", 1234, "123a"):
157175
self.check_invalid_transaction({"credit_card": {"last_4_digits": invalid}})
158176

0 commit comments

Comments
 (0)