diff --git a/HISTORY.rst b/HISTORY.rst index 2470852..905d3e4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,6 +9,12 @@ History * Setuptools has been replaced with the uv build backend for building the package. * Added ``securepay`` to the ``/payment/processor`` validation. +* Added ``credit_application`` and ``fund_transfer`` to the ``/event/type`` + validation. +* Added the input ``/event/party``. This is the party submitting the + transaction. +* Added the input ``/payment/method``. This is the payment method associated + with the transaction. 3.1.0 (2025-05-23) ++++++++++++++++++ diff --git a/README.rst b/README.rst index 7dd93cc..92c58c6 100644 --- a/README.rst +++ b/README.rst @@ -156,6 +156,7 @@ Score, Insights and Factors Example >>> 'user_agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36' >>> }, >>> 'event': { + >>> 'party': 'customer', >>> 'shop_id': 's2123', >>> 'type': 'purchase', >>> 'transaction_id': 'txn3134133', @@ -209,6 +210,7 @@ Score, Insights and Factors Example >>> 'payment': { >>> 'decline_code': 'invalid number', >>> 'was_authorized': False, + >>> 'method': 'card', >>> 'processor': 'stripe' >>> }, >>> 'shopping_cart': [{ diff --git a/src/minfraud/validation.py b/src/minfraud/validation.py index 76ef668..6dc9be9 100644 --- a/src/minfraud/validation.py +++ b/src/minfraud/validation.py @@ -98,6 +98,21 @@ def _hostname(hostname: str) -> str: _shipping_address["delivery_speed"] = _delivery_speed +_payment_method = In( + [ + "bank_debit", + "bank_redirect", + "bank_transfer", + "buy_now_pay_later", + "card", + "crypto", + "digital_wallet", + "gift_card", + "real_time_payment", + "rewards", + ], +) + _payment_processor = In( [ "adyen", @@ -281,11 +296,15 @@ def _credit_card_token(s: str) -> str: ) +_event_party = In(["agent", "customer"]) + _event_type = In( [ "account_creation", "account_login", + "credit_application", "email_change", + "fund_transfer", "password_reset", "payout_change", "purchase", @@ -316,6 +335,7 @@ def _uri(s: str) -> str: }, "billing": _address, "payment": { + "method": _payment_method, "processor": _payment_processor, "was_authorized": bool, "decline_code": str, @@ -346,6 +366,7 @@ def _uri(s: str) -> str: "domain": _hostname, }, "event": { + "party": _event_party, "shop_id": str, "time": _rfc3339_datetime, "type": _event_type, diff --git a/tests/data/full-transaction-request.json b/tests/data/full-transaction-request.json index 73df585..c49b508 100644 --- a/tests/data/full-transaction-request.json +++ b/tests/data/full-transaction-request.json @@ -1,5 +1,6 @@ { "event": { + "party": "customer", "transaction_id": "txn3134133", "shop_id": "s2123", "time": "2014-04-12T23:20:50.052+00:00", @@ -41,6 +42,7 @@ "delivery_speed": "same_day" }, "payment": { + "method": "card", "processor": "stripe", "was_authorized": false, "decline_code": "invalid number" diff --git a/tests/test_validation.py b/tests/test_validation.py index 206d753..0d31a53 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -308,6 +308,12 @@ def test_domain(self) -> None: class TestEvent(ValidationBase, unittest.TestCase): + def test_party(self) -> None: + for good in ("agent", "customer"): + self.check_transaction({"event": {"party": good}}) + for bad in ("bad", 1, ""): + self.check_invalid_transaction({"event": {"party": bad}}) + def test_transaction(self) -> None: self.check_transaction_str_type("event", "transaction_id") @@ -324,7 +330,9 @@ def test_type(self) -> None: for good in ( "account_creation", "account_login", + "credit_application", "email_change", + "fund_transfer", "password_reset", "payout_change", "purchase", @@ -370,6 +378,23 @@ def test_referrer_uri(self) -> None: class TestPayment(ValidationBase, unittest.TestCase): + def test_method(self) -> None: + for good in ( + "bank_debit", + "bank_redirect", + "bank_transfer", + "buy_now_pay_later", + "card", + "crypto", + "digital_wallet", + "gift_card", + "real_time_payment", + "rewards", + ): + self.check_transaction({"payment": {"method": good}}) + for bad in ("bad", 1, ""): + self.check_invalid_transaction({"payment": {"method": bad}}) + def test_processor(self) -> None: for good in ("adyen", "stripe"): self.check_transaction({"payment": {"processor": good}})