Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
History
-------

3.4.0
++++++++++++++++++

* Added the ``phone_verification_method``,
``phone_was_verification_successful``, and ``phone_verification_time``
inputs to the ``/billing`` request object. They describe the most recent
verification of the billing phone number.

3.3.0 (2026-07-21)
++++++++++++++++++

Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ Score, Insights and Factors Example
>>> 'postal': '06510',
>>> 'phone_country_code': '1',
>>> 'phone_number': '123-456-7890',
>>> 'phone_verification_method': 'delivered_code',
>>> 'phone_verification_time': '2026-10-01T14:30:00Z',
>>> 'phone_was_verification_successful': True,
>>> },
>>> 'shipping': {
>>> 'first_name': 'John',
Expand Down
20 changes: 14 additions & 6 deletions src/minfraud/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def _hostname(hostname: str) -> str:
raise ValueError


_rfc3339_datetime = Match(
r"(?a)\A\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})\Z",
)


_delivery_speed = In(["same_day", "overnight", "expedited", "standard"])

_address = {
Expand All @@ -99,6 +104,14 @@ def _hostname(hostname: str) -> str:

_shipping_address["delivery_speed"] = _delivery_speed

_phone_verification_method = In(["delivered_code", "network", "other"])

_billing_address = _address.copy()

_billing_address["phone_verification_method"] = _phone_verification_method
_billing_address["phone_verification_time"] = _rfc3339_datetime
_billing_address["phone_was_verification_successful"] = bool

_payment_method = In(
[
"bank_debit",
Expand Down Expand Up @@ -296,11 +309,6 @@ def _credit_card_token(s: str) -> str:
raise ValueError


_rfc3339_datetime = Match(
r"(?a)\A\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})\Z",
)


_event_party = In(["agent", "customer"])

_event_type = In(
Expand Down Expand Up @@ -339,7 +347,7 @@ def _uri(s: str) -> str:
"user_id": str,
"username_md5": _md5,
},
"billing": _address,
"billing": _billing_address,
"payment": {
"method": _payment_method,
"processor": _payment_processor,
Expand Down
5 changes: 4 additions & 1 deletion tests/data/full-transaction-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
"country": "US",
"postal": "06510",
"phone_number": "123-456-7890",
"phone_country_code": "1"
"phone_country_code": "1",
"phone_verification_method": "delivered_code",
"phone_verification_time": "2026-10-01T14:30:00Z",
"phone_was_verification_successful": true
},
"shipping": {
"first_name": "ShipFirst",
Expand Down
27 changes: 27 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,37 @@ def test_phone_country_code(self) -> None:
class TestBillingAddress(AddressBase):
type = "billing"

def test_phone_verification_method(self) -> None:
for good in ("delivered_code", "network", "other"):
self.check_transaction({"billing": {"phone_verification_method": good}})
for bad in ("sms", 1, ""):
self.check_invalid_transaction(
{"billing": {"phone_verification_method": bad}},
)

def test_phone_was_verification_successful(self) -> None:
self.check_bool("billing", "phone_was_verification_successful")

def test_phone_verification_time(self) -> None:
for good in ("2015-05-08T16:07:56+00:00", "2015-05-08T16:07:56Z"):
self.check_transaction({"billing": {"phone_verification_time": good}})
for bad in ("2015-05-08T16:07:56", "2015-05-08 16:07:56Z"):
self.check_invalid_transaction(
{"billing": {"phone_verification_time": bad}},
)


class TestShippingAddress(AddressBase):
type = "shipping"

def test_billing_only_phone_verification_inputs(self) -> None:
for key, value in (
("phone_verification_method", "network"),
("phone_verification_time", "2015-05-08T16:07:56Z"),
("phone_was_verification_successful", True),
):
self.check_invalid_transaction({"shipping": {key: value}})

def test_delivery_speed(self) -> None:
for speed in ("same_day", "overnight", "expedited", "standard"):
self.check_transaction({self.type: {"delivery_speed": speed}})
Expand Down
Loading