From 4458d318d9461aa61e6ca1f3ba9891567db34dd3 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Tue, 25 Jun 2024 14:40:37 -0700 Subject: [PATCH] Add support for new phone outputs --- HISTORY.rst | 5 ++ minfraud/models.py | 92 +++++++++++++++++++++++++++++++ tests/data/factors-response.json | 12 ++++ tests/data/insights-response.json | 12 ++++ tests/test_models.py | 19 +++++++ 5 files changed, 140 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index f707936..02709fb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,11 @@ History ``ip_address`` parameter optional. Now the ``tag`` and at least one of the following parameters must be supplied: ``ip_address``, ``maxmind_id``, ``minfraud_id``, ``transaction_id``. +* Added ``billing_phone`` and ``shipping_phone`` properties to the minFraud + Insights and Factors response models. These contain objects with information + about the respective phone numbers. Please see `our developer + site `_ for + more information. 2.10.0 (2024-04-16) +++++++++++++++++++ diff --git a/minfraud/models.py b/minfraud/models.py index 18bbe2c..def0dd5 100644 --- a/minfraud/models.py +++ b/minfraud/models.py @@ -758,6 +758,58 @@ class ShippingAddress: } +@_inflate_to_namedtuple +class Phone: + """Information about the billing or shipping phone number. + + .. attribute:: country + + The two-character ISO 3166-1 country code for the country associated with + the phone number. + + :type: str | None + + .. attribute:: is_voip + + This property is ``True`` if the phone number is a Voice over Internet + Protocol (VoIP) number allocated by a regulator. The property is + ``False`` when the number is not VoIP. If the phone number was not + provided or we do not have data for it, the property will be ``None``. + + :type: bool | None + + .. attribute:: network_operator + + The name of the original network operator associated with the phone + number. This field does not reflect phone numbers that have been ported + from the original operator to another, nor does it identify mobile + virtual network operators. + + :type: str | None + + .. attribute:: number_type + + One of the following values: fixed or mobile. Additional values may be + added in the future. + + :type: str | None + + """ + + country: Optional[str] + is_voip: Optional[bool] + network_operator: Optional[str] + number_type: Optional[str] + + __slots__ = () + _fields = { + "country": None, + "is_voip": None, + "network_operator": None, + "number_type": None, + } + + @_inflate_to_namedtuple class ServiceWarning: """Warning from the web service. @@ -1110,11 +1162,27 @@ class Factors: :type: BillingAddress + .. attribute:: billing_phone + + A :class:`.Phone` object containing minFrauddata related to the billing + phone used in the transaction. + + :type: Phone + .. attribute:: shipping_address A :class:`.ShippingAddress` object containing minFraud data related to the shipping address used in the transaction. + :type: ShippingAddress + + .. attribute:: shipping_phone + + A :class:`.Phone` object containing minFrauddata related to the shipping + phone used in the transaction. + + :type: Phone + .. attribute:: subscores A :class:`.Subscores` object containing scores for many of the @@ -1123,6 +1191,7 @@ class Factors: """ billing_address: BillingAddress + billing_phone: Phone credit_card: CreditCard disposition: Disposition funds_remaining: float @@ -1133,12 +1202,14 @@ class Factors: queries_remaining: int risk_score: float shipping_address: ShippingAddress + shipping_phone: Phone subscores: Subscores warnings: Tuple[ServiceWarning, ...] __slots__ = () _fields = { "billing_address": BillingAddress, + "billing_phone": Phone, "credit_card": CreditCard, "disposition": Disposition, "funds_remaining": None, @@ -1149,6 +1220,7 @@ class Factors: "queries_remaining": None, "risk_score": None, "shipping_address": ShippingAddress, + "shipping_phone": Phone, "subscores": Subscores, "warnings": _create_warnings, } @@ -1241,13 +1313,30 @@ class Insights: :type: BillingAddress + .. attribute:: billing_phone + + A :class:`.Phone` object containing minFrauddata related to the billing + phone used in the transaction. + + :type: Phone + .. attribute:: shipping_address A :class:`.ShippingAddress` object containing minFraud data related to the shipping address used in the transaction. + + :type: ShippingAddress + + .. attribute:: shipping_phone + + A :class:`.Phone` object containing minFrauddata related to the shipping + phone used in the transaction. + + :type: Phone """ billing_address: BillingAddress + billing_phone: Phone credit_card: CreditCard device: Device disposition: Disposition @@ -1258,11 +1347,13 @@ class Insights: queries_remaining: int risk_score: float shipping_address: ShippingAddress + shipping_phone: Phone warnings: Tuple[ServiceWarning, ...] __slots__ = () _fields = { "billing_address": BillingAddress, + "billing_phone": Phone, "credit_card": CreditCard, "device": Device, "disposition": Disposition, @@ -1273,6 +1364,7 @@ class Insights: "queries_remaining": None, "risk_score": None, "shipping_address": ShippingAddress, + "shipping_phone": Phone, "warnings": _create_warnings, } diff --git a/tests/data/factors-response.json b/tests/data/factors-response.json index f90f54c..c328ef1 100644 --- a/tests/data/factors-response.json +++ b/tests/data/factors-response.json @@ -122,6 +122,12 @@ "distance_to_ip_location": 5465, "is_in_ip_country": false }, + "billing_phone": { + "country": "US", + "is_voip": true, + "network_operator": "Verizon/1", + "number_type": "fixed" + }, "credit_card": { "issuer": { "name": "Bank of No Hope", @@ -159,6 +165,12 @@ "latitude": 35.704729, "longitude": -97.568619 }, + "shipping_phone": { + "country": "CA", + "is_voip": true, + "network_operator": "Telus Mobility-SVR/2", + "number_type": "mobile" + }, "subscores": { "avs_result": 0.01, "billing_address": 0.02, diff --git a/tests/data/insights-response.json b/tests/data/insights-response.json index f9c8956..94c53d9 100644 --- a/tests/data/insights-response.json +++ b/tests/data/insights-response.json @@ -122,6 +122,12 @@ "distance_to_ip_location": 5465, "is_in_ip_country": false }, + "billing_phone": { + "country": "US", + "is_voip": true, + "network_operator": "Verizon/1", + "number_type": "fixed" + }, "credit_card": { "issuer": { "name": "Bank of No Hope", @@ -159,6 +165,12 @@ "latitude": 35.704729, "longitude": -97.568619 }, + "shipping_phone": { + "country": "CA", + "is_voip": true, + "network_operator": "Telus Mobility-SVR/2", + "number_type": "mobile" + }, "warnings": [ { "code": "INPUT_INVALID", diff --git a/tests/test_models.py b/tests/test_models.py index 8ed825b..a8472fa 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -246,6 +246,21 @@ def test_issuer(self): self.assertEqual(phone, issuer.phone_number) self.assertEqual(True, issuer.matches_provided_phone_number) + def test_phone(self): + phone = Phone( + { + "country": "US", + "is_voip": True, + "network_operator": "Verizon/1", + "number_type": "fixed", + } + ) + + self.assertEqual("US", phone.country) + self.assertEqual(True, phone.is_voip) + self.assertEqual("Verizon/1", phone.network_operator) + self.assertEqual("fixed", phone.number_type) + def test_warning(self): code = "INVALID_INPUT" msg = "Input invalid" @@ -327,7 +342,9 @@ def factors_response(self): "device": {"id": "b643d445-18b2-4b9d-bad4-c9c4366e402a"}, "email": {"domain": {"first_seen": "2014-02-23"}, "is_free": True}, "shipping_address": {"is_in_ip_country": True}, + "shipping_phone": {"is_voip": True}, "billing_address": {"is_in_ip_country": True}, + "billing_phone": {"is_voip": True}, "funds_remaining": 10.01, "queries_remaining": 123, "risk_score": 0.01, @@ -367,8 +384,10 @@ def check_insights_data(self, insights, uuid): self.assertEqual("reject", insights.disposition.action) self.assertEqual(True, insights.email.is_free) self.assertEqual("2014-02-23", insights.email.domain.first_seen) + self.assertEqual(True, insights.shipping_phone.is_voip) self.assertEqual(True, insights.shipping_address.is_in_ip_country) self.assertEqual(True, insights.billing_address.is_in_ip_country) + self.assertEqual(True, insights.billing_phone.is_voip) self.assertEqual(uuid, insights.id) self.assertEqual(10.01, insights.funds_remaining) self.assertEqual(123, insights.queries_remaining)