Skip to content

Commit

Permalink
Merge pull request #91 from maxmind/nlogan/8-digit-iin-2-digit-lastdi…
Browse files Browse the repository at this point in the history
…gits

Add support for 8 digit IINs and 2 digit last_digits
  • Loading branch information
oschwald authored Jan 20, 2022
2 parents 305e687 + 7702fd7 commit 0ce8e4f
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 11 deletions.
15 changes: 15 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ History
* ``payvision``
* ``trustly``
* ``windcave``
* The ``/credit_card/last_4_digits`` input has been deprecated in favor of
``/credit_card/last_digits`` and will be removed in a future release.
``last_digits``/``last_4_digits`` also now supports two digit values in
addition to the previous four digit values.
* Eight digit ``/credit_card/issuer_id_number`` inputs are now supported in
addition to the previously accepted six digit ``issuer_id_number``. In most
cases, you should send the last four digits for ``last_digits``. If you send
an ``issuer_id_number`` that contains an eight digit IIN, and if the credit
card brand is not one of the following, you should send the last two digits
for ``last_digits``:
* ``Discover``
* ``JCB``
* ``Mastercard``
* ``UnionPay``
* ``Visa``

2.5.0 (2021-09-20)
++++++++++++++++++
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ Score, Insights and Factors Example
>>> 'bank_phone_country_code': '1',
>>> 'avs_result': 'Y',
>>> 'bank_phone_number': '123-456-1234',
>>> 'last_4_digits': '7643',
>>> 'last_digits': '7643',
>>> 'cvv_result': 'N',
>>> 'bank_name': 'Bank of No Hope',
>>> 'issuer_id_number': '411111'
>>> 'issuer_id_number': '411111',
>>> 'was_3d_secure_successful': True
>>> },
>>> 'payment': {
Expand Down
15 changes: 15 additions & 0 deletions minfraud/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import warnings
import hashlib
from typing import Any, Dict
from voluptuous import MultipleInvalid
Expand Down Expand Up @@ -53,6 +54,9 @@ def prepare_transaction(
if hash_email:
maybe_hash_email(cleaned_request)

if cleaned_request.get("credit_card", None):
clean_credit_card(cleaned_request)

return cleaned_request


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


def clean_credit_card(credit_card):
"""Clean the credit_card input of a transaction request"""
last4 = credit_card.pop("last_4_digits", None)
if last4:
warnings.warn(
"last_4_digits has been deprecated in favor of last_digits",
DeprecationWarning,
)
credit_card["last_digits"] = last4


def maybe_hash_email(transaction):
"""Hash email address in transaction, if present"""
try:
Expand Down
8 changes: 4 additions & 4 deletions minfraud/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"""


import ipaddress
import re
import uuid
Expand Down Expand Up @@ -250,9 +249,9 @@ def _hostname(hostname: str) -> str:

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

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

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


def _credit_card_token(s: str) -> str:
Expand Down Expand Up @@ -311,7 +310,8 @@ def _uri(s: str) -> str:
"bank_phone_number": str,
"cvv_result": _single_char,
"issuer_id_number": _iin,
"last_4_digits": _credit_card_last_4,
"last_digits": _credit_card_last_digits,
"last_4_digits": _credit_card_last_digits,
"token": _credit_card_token,
"was_3d_secure_successful": bool,
},
Expand Down
2 changes: 1 addition & 1 deletion tests/data/full-transaction-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"credit_card": {
"issuer_id_number": "411111",
"last_4_digits": "7643",
"last_digits": "7643",
"bank_name": "Bank of No Hope",
"bank_phone_country_code": "1",
"bank_phone_number": "123-456-1234",
Expand Down
47 changes: 46 additions & 1 deletion tests/test_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from minfraud.request import maybe_hash_email
from minfraud.request import maybe_hash_email, clean_credit_card


class TestRequest(unittest.TestCase):
Expand Down Expand Up @@ -146,3 +146,48 @@ def test_maybe_hash_email(self):
maybe_hash_email(transaction)

self.assertEqual(test["expected"], transaction)

def test_clean_credit_card(self):
tests = [
{
"name": "deprecated last_4_digits is cleaned to last_digits",
"input": {
"issuer_id_number": "123456",
"last_4_digits": "1234",
},
"expected": {
"issuer_id_number": "123456",
"last_digits": "1234",
},
},
{
"name": "6 digit iin, 4 digit last_digits",
"input": {
"issuer_id_number": "123456",
"last_digits": "1234",
},
"expected": {
"issuer_id_number": "123456",
"last_digits": "1234",
},
},
{
"name": "8 digit iin, 2 digit last_digits",
"input": {
"issuer_id_number": "12345678",
"last_digits": "34",
},
"expected": {
"issuer_id_number": "12345678",
"last_digits": "34",
},
},
]

for test in tests:
with self.subTest(test["name"]):
transaction = test["input"]

clean_credit_card(transaction)

self.assertEqual(test["expected"], transaction)
24 changes: 21 additions & 3 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,34 @@ def test_delivery_speed(self):

class TestCreditCard(ValidationBase, unittest.TestCase):
def test_issuer_id_number(self):
for iin in ("123456", "532313"):
for iin in ("123456", "532313", "88888888"):
self.check_transaction({"credit_card": {"issuer_id_number": iin}})
for invalid in ("12345", "1234567", 123456, "12345a"):
self.check_invalid_transaction(
{"credit_card": {"issuer_id_number": invalid}}
)

def test_last_digits(self):
for last_digits in ("1234", "9323", "34"):
self.check_transaction({"credit_card": {"last_digits": last_digits}})
for invalid in ("12345", "123", 1234, "123a"):
self.check_invalid_transaction({"credit_card": {"last_digits": invalid}})
self.check_transaction(
{"credit_card": {"issuer_id_number": "88888888", "last_digits": "12"}}
)
self.check_transaction(
{"credit_card": {"issuer_id_number": "88888888", "last_digits": "1234"}}
)
self.check_transaction(
{"credit_card": {"issuer_id_number": "666666", "last_digits": "1234"}}
)
self.check_transaction(
{"credit_card": {"issuer_id_number": "666666", "last_digits": "34"}}
)

def test_last_4_digits(self):
for iin in ("1234", "9323"):
self.check_transaction({"credit_card": {"last_4_digits": iin}})
for last_digits in ("1234", "9323", "34"):
self.check_transaction({"credit_card": {"last_4_digits": last_digits}})
for invalid in ("12345", "123", 1234, "123a"):
self.check_invalid_transaction({"credit_card": {"last_4_digits": invalid}})

Expand Down

0 comments on commit 0ce8e4f

Please sign in to comment.