From 151c8d1c8c0cf92985a5b32f11e8f86f636f6f44 Mon Sep 17 00:00:00 2001 From: Patrick Cronin Date: Mon, 11 May 2020 10:06:48 -0400 Subject: [PATCH] Add Transaction Reporting --- .../Validation/Rules/TransactionReport.php | 40 +++ src/ReportTransaction.php | 62 ++++ tests/MaxMind/Test/ReportTransactionData.php | 21 ++ tests/MaxMind/Test/ReportTransactionTest.php | 320 ++++++++++++++++++ .../data/reporttransaction/full-request.json | 9 + .../reporttransaction/minimal-request.json | 4 + 6 files changed, 456 insertions(+) create mode 100644 src/MinFraud/Validation/Rules/TransactionReport.php create mode 100644 src/ReportTransaction.php create mode 100644 tests/MaxMind/Test/ReportTransactionData.php create mode 100644 tests/MaxMind/Test/ReportTransactionTest.php create mode 100644 tests/data/reporttransaction/full-request.json create mode 100644 tests/data/reporttransaction/minimal-request.json diff --git a/src/MinFraud/Validation/Rules/TransactionReport.php b/src/MinFraud/Validation/Rules/TransactionReport.php new file mode 100644 index 00000000..13e66a5f --- /dev/null +++ b/src/MinFraud/Validation/Rules/TransactionReport.php @@ -0,0 +1,40 @@ +validatable = v::keySet( + v::key('chargeback_code', v::stringType(), false), + v::key('ip_address', v::ip(), true), + v::key('maxmind_id', v::stringType()->length(8, 8), false), + v::key( + 'minfraud_id', + v::regex('/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/'), + false + ), + v::key('notes', v::stringType(), false), + v::key( + 'tag', + v::in( + [ + 'not_fraud', + 'suspected_fraud', + 'spam_or_abuse', + 'chargeback', + ] + ), + true + ), + v::key('transaction_id', v::stringType(), false) + ); + } +} diff --git a/src/ReportTransaction.php b/src/ReportTransaction.php new file mode 100644 index 00000000..c638ff1a --- /dev/null +++ b/src/ReportTransaction.php @@ -0,0 +1,62 @@ +cleanAndValidate('TransactionReport', $values); + + if (!isset($values['ip_address'])) { + throw new InvalidInputException('Key ip_address must be present in request'); + } + if (!isset($values['tag'])) { + throw new InvalidInputException('Key tag must be present in request'); + } + + $url = self::$basePath . 'transactions/report'; + $this->client->post('ReportTransaction', $url, $values); + } +} diff --git a/tests/MaxMind/Test/ReportTransactionData.php b/tests/MaxMind/Test/ReportTransactionData.php new file mode 100644 index 00000000..30e0f342 --- /dev/null +++ b/tests/MaxMind/Test/ReportTransactionData.php @@ -0,0 +1,21 @@ +assertEmpty( + $this->createReportTransactionRequest( + Data::minimalRequest(), + 1 + )->reportTransaction(Data::minimalRequest()), + 'response for minimal request' + ); + } + + public function testFullRequest() + { + $req = Data::fullRequest(); + $this->assertEmpty( + $this->createReportTransactionRequest( + $req + )->reportTransaction($req), + 'response for full request' + ); + } + + public function testRequestsWithNulls() + { + $req = array_merge( + Data::minimalRequest(), + [ + 'chargeback_code' => null, + 'maxmind_id' => null, + 'minfraud_id' => null, + 'notes' => null, + 'transaction_id' => null, + ] + ); + $this->assertEmpty( + $this->createReportTransactionRequest( + Data::minimalRequest(), + 1 + )->reportTransaction($req), + 'response from request including nulls' + ); + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage Must have keys + * + * @dataProvider requestsMissingRequiredFields + * + * @param array $req + */ + public function testMissingRequiredFields($req) + { + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage must be present in request + * + * @dataProvider requestsMissingRequiredFields + * + * @param array $req + */ + public function testMissingRequiredFieldsWithoutValidation($req) + { + $this->createReportTransactionRequest( + $req, + 0, + ['validateInput' => false] + )->reportTransaction($req); + } + + public function requestsMissingRequiredFields() + { + return [ + 'Missing ip_address' => [ + ['tag' => 'not_fraud'], + ], + 'Missing tag' => [ + ['ip_address' => '1.2.3.4'], + ], + 'Null ip_address' => [ + array_merge(Data::minimalRequest(), ['ip_address' => null]), + ], + 'Null tag' => [ + array_merge(Data::minimalRequest(), ['tag' => null]), + ], + ]; + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage Must have keys + */ + public function testUnknownKey() + { + $req = array_merge( + Data::minimalRequest(), + ['unknown' => 'some_value'] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage chargeback_code must be a string + * + * @dataProvider notStringTypes + * + * @param mixed $chargebackCode + */ + public function testInvalidChargebackCodes($chargebackCode) + { + $req = array_merge( + Data::minimalRequest(), + ['chargeback_code' => $chargebackCode] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage notes must be a string + * + * @dataProvider notStringTypes + * + * @param mixed $notes + */ + public function testInvalidNotes($notes) + { + $req = array_merge(Data::minimalRequest(), ['notes' => $notes]); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage transaction_id must be a string + * + * @dataProvider notStringTypes + * + * @param mixed $transactionId + */ + public function testInvalidTransactionIds($transactionId) + { + $req = array_merge( + Data::minimalRequest(), + ['transaction_id' => $transactionId] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + public function notStringTypes() + { + return [ + [1], + ]; + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage ip_address must be an IP address + * + * @dataProvider notStringTypes + * + * @param mixed $ip + */ + public function testInvalidIpAddresses($ip) + { + $req = array_merge( + Data::minimalRequest(), + ['ip_address' => $ip] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + public function invalidIpAddresses() + { + return [ + ['1.2.3.'], + ['299.1.1.1'], + ['::AF123'], + ]; + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage maxmind_id must have a length between 8 and 8 + * + * @dataProvider invalidMaxmindIds + * + * @param mixed $maxmindId + */ + public function testInvalidMaxmindIds($maxmindId) + { + $req = array_merge( + Data::minimalRequest(), + ['maxmind_id' => $maxmindId] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + public function invalidMaxmindIds() + { + return [ + ['1234567'], + ['123456789'], + [''], + ]; + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage minfraud_id must validate against + * + * @dataProvider invalidMinfraudIds + * + * @param mixed $minfraudId + */ + public function testInvalidMinfraudIds($minfraudId) + { + $req = array_merge( + Data::minimalRequest(), + ['minfraud_id' => $minfraudId] + ); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + public function invalidMinfraudIds() + { + return [ + ['1234567812341234123412345678901'], + ['1234-5678-1234-1234-1234-1234-5678-9012'], + ['12345678-123412341234-12345678901'], + ['12345678-1234-1234-1234-1234567890123'], + ['12345678-1234-1234-1234-12345678901g'], + [''], + ]; + } + + /** + * @expectedException \MaxMind\Exception\InvalidInputException + * @expectedExceptionMessage tag must be in + * + * @dataProvider invalidTags + * + * @param mixed $tag + */ + public function testInvalidTags($tag) + { + $req = array_merge(Data::minimalRequest(), ['tag' => $tag]); + $this->createReportTransactionRequest( + $req, + 0 + )->reportTransaction($req); + } + + public function invalidTags() + { + return [ + ['risky_business'], + [''], + ]; + } + + private function createReportTransactionRequest( + $requestContent, + $callsToRequest = 1, + $options = [], + $statusCode = 204, + $contentType = 'application/json', + $responseBody = null + ) { + return $this->createRequest( + '\MaxMind\ReportTransaction', + 'transactions/report', + $requestContent, + $statusCode, + $contentType, + $responseBody, + $options, + $callsToRequest + ); + } +} diff --git a/tests/data/reporttransaction/full-request.json b/tests/data/reporttransaction/full-request.json new file mode 100644 index 00000000..9508ca84 --- /dev/null +++ b/tests/data/reporttransaction/full-request.json @@ -0,0 +1,9 @@ +{ + "ip_address": "81.2.69.160", + "tag": "chargeback", + "chargeback_code": "UA01 Fraud - Card Present Transaction", + "minfraud_id": "c8ce89f9-734d-4411-a174-91560f5ec07a", + "maxmind_id": "a1b2c3d4", + "notes": "Fraudster was wearing a clown outfit.", + "transaction_id": "txn3134133" +} diff --git a/tests/data/reporttransaction/minimal-request.json b/tests/data/reporttransaction/minimal-request.json new file mode 100644 index 00000000..c47643e1 --- /dev/null +++ b/tests/data/reporttransaction/minimal-request.json @@ -0,0 +1,4 @@ +{ + "ip_address": "1.2.3.4", + "tag": "not_fraud" +} \ No newline at end of file