Skip to content

Commit 015d70a

Browse files
authored
Merge pull request #141 from maxmind/nlogan/no-ip-report
Make IP address optional when reporting transactions
2 parents 1bc1280 + b4a69b0 commit 015d70a

File tree

4 files changed

+66
-6
lines changed

4 files changed

+66
-6
lines changed

HISTORY.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
History
44
-------
55

6+
2.11.0
7+
+++++++++++++++++++
8+
9+
* Updated the validation for the Report Transactions API to make the
10+
``ip_address`` parameter optional. Now the ``tag`` and at least one of the
11+
following parameters must be supplied: ``ip_address``, ``maxmind_id``,
12+
``minfraud_id``, ``transaction_id``.
13+
614
2.10.0 (2024-04-16)
715
+++++++++++++++++++
816

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ The method takes a dictionary representing the report to be sent to the web
9898
service. The structure of this dictionary should be in `the format specified
9999
in the REST API documentation
100100
<https://dev.maxmind.com/minfraud/report-a-transaction?lang=en>`__. The
101-
``ip_address`` and ``tag`` fields are required. All other fields are optional.
101+
required fields are ``tag`` and one or more of the following: ``ip_address``,
102+
``maxmind_id``, ``minfraud_id``, ``transaction_id``.
102103

103104
Request Validation (for all request methods)
104105
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

minfraud/validation.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import Optional
1616

1717
from email_validator import validate_email # type: ignore
18-
from voluptuous import All, Any, In, Match, Range, Required, Schema
18+
from voluptuous import All, Any, In, Match, MultipleInvalid, Range, Required, Schema
1919
from voluptuous.error import UrlInvalid
2020

2121
# Pylint doesn't like the private function type naming for the callable
@@ -379,14 +379,48 @@ def _uuid(s: str) -> str:
379379
raise ValueError
380380

381381

382-
validate_report = Schema(
382+
NIL_UUID = str(uuid.UUID(int=0))
383+
384+
385+
def _non_empty_uuid(s: str) -> str:
386+
if _uuid(s) == NIL_UUID:
387+
raise ValueError
388+
return s
389+
390+
391+
def _transaction_id(s: Optional[str]) -> str:
392+
if isinstance(s, str) and len(s) > 0:
393+
return s
394+
raise ValueError
395+
396+
397+
_validate_report_schema = Schema(
383398
{
384399
"chargeback_code": str,
385-
Required("ip_address"): _ip_address,
400+
"ip_address": _ip_address,
386401
"maxmind_id": _maxmind_id,
387-
"minfraud_id": _uuid,
402+
"minfraud_id": _non_empty_uuid,
388403
"notes": str,
389404
Required("tag"): _tag,
390-
"transaction_id": str,
405+
"transaction_id": _transaction_id,
391406
},
392407
)
408+
409+
410+
def _validate_at_least_one_identifier_field(report):
411+
optional_fields = ["ip_address", "maxmind_id", "minfraud_id", "transaction_id"]
412+
if not any(field in report for field in optional_fields):
413+
# We return MultipleInvalid instead of ValueError to be consistent with what
414+
# voluptuous returns.
415+
raise MultipleInvalid(
416+
"The report must contain at least one of the following fields: "
417+
"'ip_address', 'maxmind_id', 'minfraud_id', 'transaction_id'."
418+
)
419+
return True
420+
421+
422+
def validate_report(report):
423+
"""Validate minFraud Transaction Report fields."""
424+
_validate_report_schema(report)
425+
_validate_at_least_one_identifier_field(report)
426+
return True

tests/test_validation.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,17 @@ def setup_report(self, report):
5151

5252
def check_invalid_report(self, report):
5353
self.setup_report(report)
54+
self.check_invalid_report_no_setup(report)
55+
56+
def check_invalid_report_no_setup(self, report):
5457
with self.assertRaises(MultipleInvalid, msg=f"{report} is invalid"):
5558
validate_report(report)
5659

5760
def check_report(self, report):
5861
self.setup_report(report)
62+
self.check_report_no_setup(report)
63+
64+
def check_report_no_setup(self, report):
5965
try:
6066
validate_report(report)
6167
except MultipleInvalid as e:
@@ -414,6 +420,7 @@ def test_minfraud_id(self):
414420
"12345678-123412341234-12345678901",
415421
"12345678-1234-1234-1234-1234567890123",
416422
"12345678-1234-1234-1234-12345678901g",
423+
"00000000-0000-0000-0000-000000000000",
417424
"",
418425
):
419426
self.check_invalid_report({"minfraud_id": bad})
@@ -431,3 +438,13 @@ def test_tag(self):
431438
self.check_report({"tag": good})
432439
for bad in ("risky_business", "", None):
433440
self.check_invalid_report({"tag": bad})
441+
442+
def test_report_valid_identifier(self):
443+
self.check_invalid_report_no_setup({"tag": "chargeback"})
444+
445+
self.check_report_no_setup({"tag": "chargeback", "ip_address": "1.1.1.1"})
446+
self.check_report_no_setup(
447+
{"tag": "chargeback", "minfraud_id": "58fa38d8-4b87-458b-a22b-f00eda1aa20d"}
448+
)
449+
self.check_report_no_setup({"tag": "chargeback", "maxmind_id": "12345678"})
450+
self.check_report_no_setup({"tag": "chargeback", "transaction_id": "abc123"})

0 commit comments

Comments
 (0)