Skip to content

Commit e56ced1

Browse files
committed
Add support for IP risk reasons
1 parent db9768c commit e56ced1

7 files changed

Lines changed: 153 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ CHANGELOG
99
* `APS_PAYMENTS`
1010
* Added additional normalizing of the email address if `hashAddress` is
1111
enabled.
12+
* Added support for the IP address risk reasons in the minFraud Insights and
13+
Factors responses. This is available at `response.getIpAddress().getRiskReasons()`.
14+
It is a list of `IpRiskReason` objects.
1215

1316
1.16.0 (2020-10-14)
1417
-------------------

src/main/java/com/maxmind/minfraud/response/IpAddress.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.maxmind.geoip2.model.InsightsResponse;
55
import com.maxmind.geoip2.record.*;
66

7+
import java.util.ArrayList;
8+
import java.util.Collections;
79
import java.util.List;
810

911
/**
@@ -13,6 +15,7 @@ public final class IpAddress extends InsightsResponse implements IpAddressInterf
1315
private final GeoIp2Country country;
1416
private final GeoIp2Location location;
1517
private final Double risk;
18+
private final List<IpRiskReason> riskReasons;
1619

1720
public IpAddress(
1821
@JsonProperty("city") City city,
@@ -24,13 +27,36 @@ public IpAddress(
2427
@JsonProperty("registered_country") Country registeredCountry,
2528
@JsonProperty("represented_country") RepresentedCountry representedCountry,
2629
@JsonProperty("risk") Double risk,
30+
@JsonProperty("risk_reasons") List<IpRiskReason> riskReasons,
2731
@JsonProperty("subdivisions") List<Subdivision> subdivisions,
2832
@JsonProperty("traits") Traits traits
2933
) {
3034
super(city, continent, country, location, maxmind, postal, registeredCountry, representedCountry, subdivisions, traits);
3135
this.country = country == null ? new GeoIp2Country() : country;
3236
this.location = location == null ? new GeoIp2Location() : location;
3337
this.risk = risk;
38+
this.riskReasons = Collections.unmodifiableList(riskReasons == null ? new ArrayList<>() : riskReasons);
39+
}
40+
41+
/**
42+
* @deprecated This constructor only exists for backward compatibility
43+
* and will be removed in the next major release.
44+
*/
45+
public IpAddress(
46+
City city,
47+
Continent continent,
48+
GeoIp2Country country,
49+
GeoIp2Location location,
50+
MaxMind maxmind,
51+
Postal postal,
52+
Country registeredCountry,
53+
RepresentedCountry representedCountry,
54+
Double risk,
55+
List<Subdivision> subdivisions,
56+
Traits traits
57+
) {
58+
this(city, continent, country, location, maxmind, postal, registeredCountry, representedCountry,
59+
risk, null, subdivisions, traits);
3460
}
3561

3662
public IpAddress() {
@@ -63,4 +89,14 @@ public GeoIp2Location getLocation() {
6389
public Double getRisk() {
6490
return risk;
6591
}
92+
93+
/**
94+
* @return An unmodifiable list contains risk reason objects identifying
95+
* the reasons why the IP address received the associated risk. This will
96+
* be an empty list if there are no reasons.
97+
*/
98+
@JsonProperty("risk_reasons")
99+
public final List<IpRiskReason> getRiskReasons() {
100+
return riskReasons;
101+
}
66102
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.maxmind.minfraud.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.maxmind.minfraud.AbstractModel;
5+
6+
/**
7+
* This class represents the reason for the IP risk.
8+
*/
9+
public final class IpRiskReason extends AbstractModel {
10+
private final String code;
11+
private final String reason;
12+
13+
public IpRiskReason(
14+
@JsonProperty("code") String code,
15+
@JsonProperty("reason") String reason
16+
) {
17+
this.code = code;
18+
this.reason = reason;
19+
}
20+
21+
/**
22+
* This provides a machine-readable code identifying the reason. Although
23+
* more codes may be added in the future, the current codes are:
24+
* <dl>
25+
* <dt>ANONYMOUS_IP</dt>
26+
* <dd>The IP address belongs to an anonymous network. See the
27+
* object at <c>.IPAddress.Traits</c> for more details.</dd>
28+
*
29+
* <dt>BILLING_POSTAL_VELOCITY</dt>
30+
* <dd>Many different billing postal codes have been seen on
31+
* this IP address.</dd>
32+
*
33+
* <dt>EMAIL_VELOCITY</dt>
34+
* <dd>Many different email addresses have been seen on this
35+
* IP address.</dd>
36+
*
37+
* <dt>HIGH_RISK_DEVICE</dt>
38+
* <dd>A high risk device was seen on this IP address.</dd>
39+
*
40+
* <dt>HIGH_RISK_EMAIL</dt>
41+
* <dd>A high risk email address was seen on this IP address in
42+
* your past transactions.</dd>
43+
*
44+
* <dt>ISSUER_ID_NUMBER_VELOCITY</dt>
45+
* <dd>Many different issuer ID numbers have been seen on this
46+
* IP address.</dd>
47+
*
48+
* <dt>MINFRAUD_NETWORK_ACTIVITY</dt>
49+
* <dd>Suspicious activity has been seen on this IP address
50+
* across minFraud customers.</dd>
51+
* </dl>
52+
*
53+
* @return The reason code.
54+
*/
55+
public String getCode() {
56+
return this.code;
57+
}
58+
59+
/**
60+
* @return This field provides a human-readable explanation of the reason.
61+
* The description may change at any time and should not be matched against.
62+
*/
63+
public String getReason() {
64+
return this.reason;
65+
}
66+
}

src/test/java/com/maxmind/minfraud/WebServiceClientTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.maxmind.minfraud.request.TransactionReport;
99
import com.maxmind.minfraud.response.FactorsResponse;
1010
import com.maxmind.minfraud.response.InsightsResponse;
11+
import com.maxmind.minfraud.response.IpRiskReason;
1112
import com.maxmind.minfraud.response.ScoreResponse;
1213
import junitparams.JUnitParamsRunner;
1314
import junitparams.Parameters;
@@ -17,6 +18,7 @@
1718
import org.skyscreamer.jsonassert.JSONAssert;
1819

1920
import java.net.InetAddress;
21+
import java.util.List;
2022

2123
import static com.github.tomakehurst.wiremock.client.WireMock.*;
2224
import static com.jcabi.matchers.RegexMatchers.matchesPattern;
@@ -89,6 +91,12 @@ public void testFullInsightsTransaction() throws Exception {
8991
assertEquals("81.2.69.0/24", response.getIpAddress().getTraits().getNetwork().toString());
9092

9193
assertTrue(response.getCreditCard().isVirtual());
94+
95+
List<IpRiskReason> reasons = response.getIpAddress().getRiskReasons();
96+
97+
assertEquals("two IP risk reasons", 2, reasons.size());
98+
assertEquals("second IP risk reason code", "MINFRAUD_NETWORK_ACTIVITY",
99+
reasons.get(1).getCode());
92100
}
93101
}
94102

src/test/java/com/maxmind/minfraud/response/IpAddressTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import com.fasterxml.jackson.jr.ob.JSON;
44
import org.junit.Test;
55

6-
import static org.junit.Assert.assertEquals;
7-
import static org.junit.Assert.assertTrue;
6+
import static org.junit.Assert.*;
87

98
public class IpAddressTest extends AbstractOutputTest {
109

@@ -18,6 +17,12 @@ public void testIpAddress() throws Exception {
1817
.composeString()
1918
.startObject()
2019
.put("risk", 99)
20+
.startArrayField("risk_reasons")
21+
.startObject()
22+
.put("code", "ANONYMOUS_IP")
23+
.put("reason", "some reason")
24+
.end()
25+
.end()
2126
.startObjectField("country")
2227
.put("is_high_risk", true)
2328
.end()
@@ -46,6 +51,19 @@ public void testIpAddress() throws Exception {
4651
assertTrue("isHostingProvider", address.getTraits().isHostingProvider());
4752
assertTrue("isPublicProxy", address.getTraits().isPublicProxy());
4853
assertTrue("isTorExitNode", address.getTraits().isTorExitNode());
54+
assertEquals("IP risk reason code", "ANONYMOUS_IP",
55+
address.getRiskReasons().get(0).getCode());
56+
assertEquals("IP risk reason", "some reason",
57+
address.getRiskReasons().get(0).getReason());
58+
}
59+
60+
@Test
61+
public void testEmptyObject() throws Exception {
62+
IpAddress address = this.deserialize(
63+
IpAddress.class,
64+
"{}"
65+
);
4966

67+
assertNotNull(address.getRiskReasons());
5068
}
5169
}

src/test/resources/test-data/factors-response.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
"queries_remaining": 1000,
66
"ip_address": {
77
"risk": 0.01,
8+
"risk_reasons": [
9+
{
10+
"code": "ANONYMOUS_IP",
11+
"reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details."
12+
},
13+
{
14+
"code": "MINFRAUD_NETWORK_ACTIVITY",
15+
"reason": "Suspicious activity has been seen on this IP address across minFraud customers."
16+
}
17+
],
818
"city": {
919
"confidence": 42,
1020
"geoname_id": 2643743,

src/test/resources/test-data/insights-response.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
"queries_remaining": 1000,
66
"ip_address": {
77
"risk": 0.01,
8+
"risk_reasons": [
9+
{
10+
"code": "ANONYMOUS_IP",
11+
"reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details."
12+
},
13+
{
14+
"code": "MINFRAUD_NETWORK_ACTIVITY",
15+
"reason": "Suspicious activity has been seen on this IP address across minFraud customers."
16+
}
17+
],
818
"city": {
919
"confidence": 42,
1020
"geoname_id": 2643743,

0 commit comments

Comments
 (0)