Skip to content

Commit 77fe354

Browse files
committed
Add risk score reasons
1 parent 6cb4a1a commit 77fe354

File tree

8 files changed

+352
-1
lines changed

8 files changed

+352
-1
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
CHANGELOG
22
=========
33

4+
3.6.0-beta.1
5+
------------------
6+
7+
* Added support for the new risk reasons outputs in minFraud Factors. The risk
8+
reasons output codes and reasons are currently in beta and are subject to
9+
change. We recommend that you use these beta outputs with caution and avoid
10+
relying on them for critical applications.
411

512
3.5.0 (2024-07-08)
613
------------------

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
public final class FactorsResponse extends InsightsResponse {
1111

12+
private final List<RiskScoreReason> riskScoreReasons;
1213
private final Subscores subscores;
1314

1415

@@ -26,6 +27,11 @@ public final class FactorsResponse extends InsightsResponse {
2627
* @param queriesRemaining The number of queries remaining.
2728
* @param riskScore The risk score.
2829
* @param shippingAddress The {@code ShippingAddress} model object.
30+
* @param riskScoreReasons A list containing {@code RiskScoreReason} model objects that
31+
* describe risk score reasons for a given transaction that change the risk score
32+
* significantly. Risk score reasons are usually only returned for medium to high risk
33+
* transactions. If there were no significant changes to the risk score due to these
34+
* reasons, then this list will be empty.
2935
* @param subscores The {@code Subscores} model object.
3036
* @param warnings A list containing warning objects.
3137
*/
@@ -43,12 +49,14 @@ public FactorsResponse(
4349
@JsonProperty("risk_score") Double riskScore,
4450
@JsonProperty("shipping_address") ShippingAddress shippingAddress,
4551
@JsonProperty("shipping_phone") Phone shippingPhone,
52+
@JsonProperty("risk_score_reasons") List<RiskScoreReason> riskScoreReasons,
4653
@JsonProperty("subscores") Subscores subscores,
4754
@JsonProperty("warnings") List<Warning> warnings
4855
) {
4956
super(billingAddress, billingPhone, creditCard, device, disposition, email,
5057
fundsRemaining, id, ipAddress, queriesRemaining, riskScore,
5158
shippingAddress, shippingPhone, warnings);
59+
this.riskScoreReasons = riskScoreReasons;
5260
this.subscores = subscores;
5361
}
5462

@@ -74,7 +82,46 @@ public FactorsResponse(
7482
List<Warning> warnings
7583
) {
7684
this(billingAddress, null, creditCard, device, disposition, email, fundsRemaining, id,
77-
ipAddress, queriesRemaining, riskScore, shippingAddress, null, subscores, warnings);
85+
ipAddress, queriesRemaining, riskScore, shippingAddress, null, null, subscores,
86+
warnings);
87+
}
88+
89+
/**
90+
* Constructor for backwards compatibility. This will be removed in the next
91+
* major release.
92+
*
93+
* @deprecated use other constructor.
94+
*/
95+
@Deprecated
96+
public FactorsResponse(
97+
BillingAddress billingAddress,
98+
Phone billingPhone,
99+
CreditCard creditCard,
100+
Device device,
101+
Disposition disposition,
102+
Email email,
103+
Double fundsRemaining,
104+
UUID id,
105+
IpAddress ipAddress,
106+
Integer queriesRemaining,
107+
Double riskScore,
108+
ShippingAddress shippingAddress,
109+
Phone shippingPhone,
110+
Subscores subscores,
111+
List<Warning> warnings
112+
) {
113+
this(billingAddress, billingPhone, creditCard, device, disposition, email, fundsRemaining,
114+
id, ipAddress, queriesRemaining, riskScore, shippingAddress, shippingPhone, null,
115+
subscores, warnings);
116+
}
117+
118+
/**
119+
* @return A list containing objects that describe risk score reasons
120+
* for a given transaction that change the risk score significantly.
121+
*/
122+
@JsonProperty("risk_score_reasons")
123+
public List<RiskScoreReason> getRiskScoreReasons() {
124+
return riskScoreReasons;
78125
}
79126

80127
/**
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 a risk score reason for the multiplier.
8+
*/
9+
public final class Reason extends AbstractModel {
10+
private final String code;
11+
private final String reason;
12+
13+
/**
14+
* Constructor for {@code Reason}.
15+
*
16+
* @param code The code.
17+
* @param reason The reason.
18+
*/
19+
public Reason(
20+
@JsonProperty("code") String code,
21+
@JsonProperty("reason") String reason
22+
) {
23+
this.code = code;
24+
this.reason = reason;
25+
}
26+
27+
/**
28+
* This field provides a machine-readable code identifying the reason.
29+
* Although more <a href="https://dev.maxmind.com/minfraud/api-documentation/responses/#schema--response--risk-score-reason--multiplier-reason">codes</a>
30+
* may be added in the future, the current codes are:
31+
*
32+
* <ul>
33+
* <li>BROWSER_LANGUAGE - Riskiness of the browser user-agent
34+
* and language associated with the request.</li>
35+
* <li>BUSINESS_ACTIVITY - Riskiness of business activity associated with the request.</li>
36+
* <li>COUNTRY - Riskiness of the country associated with the request.</li>
37+
* <li>CUSTOMER_ID - Riskiness of a customer's activity.</li>
38+
* <li>EMAIL_DOMAIN - Riskiness of email domain.</li>
39+
* <li>EMAIL_DOMAIN_NEW - Riskiness of newly-sighted email domain.</li>
40+
* <li>EMAIL_ADDRESS_NEW - Riskiness of newly-sighted email address.</li>
41+
* <li>EMAIL_LOCAL_PART - Riskiness of the local part of the email address.</li>
42+
* <li>EMAIL_VELOCITY - Velocity on email - many requests on same email
43+
* over short period of time.</li>
44+
* <li>ISSUER_ID_NUMBER_COUNTRY_MISMATCH - Riskiness of the country mismatch between IP,
45+
* billing, shipping and IIN country.</li>
46+
* <li>ISSUER_ID_NUMBER_ON_SHOP_ID - Risk of Issuer ID Number for the shop ID.</li>
47+
* <li>ISSUER_ID_NUMBER_LAST_DIGITS_ACTIVITY - Riskiness of many recent requests
48+
* and previous high-risk requests on the IIN and last digits of the credit card.</li>
49+
* <li>ISSUER_ID_NUMBER_SHOP_ID_VELOCITY - Risk of recent Issuer ID Number activity
50+
* for the shop ID.</li>
51+
* <li>INTRACOUNTRY_DISTANCE - Risk of distance between IP, billing,
52+
* and shipping location.</li>
53+
* <li>ANONYMOUS_IP - Risk due to IP being an Anonymous IP.</li>
54+
* <li>IP_BILLING_POSTAL_VELOCITY - Velocity of distinct billing postal code
55+
* on IP address.</li>
56+
* <li>IP_EMAIL_VELOCITY - Velocity of distinct email address on IP address.</li>
57+
* <li>IP_HIGH_RISK_DEVICE - High-risk device sighted on IP address.</li>
58+
* <li>IP_ISSUER_ID_NUMBER_VELOCITY - Velocity of distinct IIN on IP address.</li>
59+
* <li>IP_ACTIVITY - Riskiness of IP based on minFraud network activity.</li>
60+
* <li>LANGUAGE - Riskiness of browser language.</li>
61+
* <li>MAX_RECENT_EMAIL - Riskiness of email address based on
62+
* past minFraud risk scores on email.</li>
63+
* <li>MAX_RECENT_PHONE - Riskiness of phone number based on
64+
* past minFraud risk scores on phone.</li>
65+
* <li>MAX_RECENT_SHIP - Riskiness of email address based on
66+
* past minFraud risk scores on ship address.</li>
67+
* <li>MULTIPLE_CUSTOMER_ID_ON_EMAIL - Riskiness of email address
68+
* having many customer IDs.</li>
69+
* <li>ORDER_AMOUNT - Riskiness of the order amount.</li>
70+
* <li>ORG_DISTANCE_RISK - Risk of ISP and distance between billing address
71+
* and IP location.</li>
72+
* <li>PHONE - Riskiness of the phone number or related numbers.</li>
73+
* <li>CART - Riskiness of shopping cart contents.</li>
74+
* <li>TIME_OF_DAY - Risk due to local time of day.</li>
75+
* <li>TRANSACTION_REPORT_EMAIL - Risk due to transaction reports on the email address.</li>
76+
* <li>TRANSACTION_REPORT_IP - Risk due to transaction reports on the IP address.</li>
77+
* <li>TRANSACTION_REPORT_PHONE - Risk due to transaction reports on the phone number.</li>
78+
* <li>TRANSACTION_REPORT_SHIP - Risk due to transaction reports on the shipping address.</li>
79+
* <li>EMAIL_ACTIVITY - Riskiness of the email address based on minFraud network activity.</li>
80+
* <li>PHONE_ACTIVITY - Riskiness of the phone number based on minFraud network activity.</li>
81+
* <li>SHIP_ACTIVITY - Riskiness of ship address based on minFraud network activity.</li>
82+
* </ul>
83+
*
84+
* @return The code.
85+
*/
86+
@JsonProperty("code")
87+
public String getCode() {
88+
return this.code;
89+
}
90+
91+
/**
92+
* @return The human-readable explanation of the reason. The description may change at any time
93+
* and should not be matched against.
94+
*/
95+
@JsonProperty("reason")
96+
public String getReason() {
97+
return this.reason;
98+
}
99+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.maxmind.minfraud.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.maxmind.minfraud.AbstractModel;
5+
import java.util.ArrayList;
6+
import java.util.Collections;
7+
import java.util.List;
8+
9+
/**
10+
* This class represents a risk score multiplier and reasons for that multiplier.
11+
*/
12+
public final class RiskScoreReason extends AbstractModel {
13+
private final Double multiplier;
14+
private final List<Reason> reasons;
15+
16+
/**
17+
* Constructor for {@code RiskScoreReason}.
18+
*
19+
* @param multiplier The multiplier.
20+
* @param reasons The reasons.
21+
*/
22+
public RiskScoreReason(
23+
@JsonProperty("multiplier") Double multiplier,
24+
@JsonProperty("reasons") List<Reason> reasons
25+
) {
26+
this.multiplier = multiplier;
27+
this.reasons =
28+
Collections.unmodifiableList(reasons == null ? new ArrayList<>() : reasons);
29+
}
30+
31+
/**
32+
* @return The factor by which the risk score is increased (if the value is greater than 1)
33+
* or decreased (if the value is less than 1) for given risk reason(s).
34+
* Multipliers greater than 1.5 and less than 0.66 are considered significant
35+
* and lead to risk reason(s) being present.
36+
*/
37+
@JsonProperty("multiplier")
38+
public Double getMultiplier() {
39+
return multiplier;
40+
}
41+
42+
/**
43+
* @return An unmodifiable list containing objects that describe one of the reasons for
44+
* the multiplier. This will be an empty list if there are no reasons.
45+
*/
46+
@JsonProperty("reasons")
47+
public List<Reason> getReasons() {
48+
return reasons;
49+
}
50+
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ public void testFactors() throws Exception {
5050
.put("queries_remaining", 123)
5151
.put("id", id)
5252
.put("risk_score", 0.01)
53+
.startArrayField("risk_score_reasons")
54+
.startObject()
55+
.put("multiplier", 45)
56+
.startArrayField("reasons")
57+
.startObject()
58+
.put("code", "ANONYMOUS_IP")
59+
.put("reason", "Risk due to IP being an Anonymous IP")
60+
.end()
61+
.end()
62+
.end()
63+
.end()
5364
.end()
5465
.finish()
5566
);
@@ -147,5 +158,22 @@ public void testFactors() throws Exception {
147158
factors.getRiskScore(),
148159
"correct risk score"
149160
);
161+
assertEquals(1, factors.getRiskScoreReasons().size());
162+
assertEquals(
163+
Double.valueOf(45),
164+
factors.getRiskScoreReasons().get(0).getMultiplier(),
165+
"risk multiplier"
166+
);
167+
assertEquals(1, factors.getRiskScoreReasons().get(0).getReasons().size());
168+
assertEquals(
169+
"ANONYMOUS_IP",
170+
factors.getRiskScoreReasons().get(0).getReasons().get(0).getCode(),
171+
"risk reason code"
172+
);
173+
assertEquals(
174+
"Risk due to IP being an Anonymous IP",
175+
factors.getRiskScoreReasons().get(0).getReasons().get(0).getReason(),
176+
"risk reason"
177+
);
150178
}
151179
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.maxmind.minfraud.response;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import com.fasterxml.jackson.jr.ob.JSON;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class ReasonTest extends AbstractOutputTest {
9+
10+
@Test
11+
public void testReason() throws Exception {
12+
String code = "ANONYMOUS_IP";
13+
String msg = "Risk due to IP being an Anonymous IP";
14+
15+
Reason reason = this.deserialize(
16+
Reason.class,
17+
JSON.std
18+
.composeString()
19+
.startObject()
20+
.put("code", code)
21+
.put("reason", msg)
22+
.end()
23+
.finish()
24+
);
25+
26+
assertEquals(code, reason.getCode(), "code");
27+
assertEquals(msg, reason.getReason(), "reason");
28+
}
29+
30+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.maxmind.minfraud.response;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import com.fasterxml.jackson.jr.ob.JSON;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class RiskScoreReasonTest extends AbstractOutputTest {
10+
11+
@Test
12+
public void testRiskScoreReason() throws Exception {
13+
RiskScoreReason reason = this.deserialize(
14+
RiskScoreReason.class,
15+
JSON.std
16+
.composeString()
17+
.startObject()
18+
.put("multiplier", 45)
19+
.startArrayField("reasons")
20+
.startObject()
21+
.put("code", "ANONYMOUS_IP")
22+
.put("reason", "Risk due to IP being an Anonymous IP")
23+
.end()
24+
.end()
25+
.end()
26+
.finish()
27+
);
28+
29+
assertEquals(Double.valueOf(45), reason.getMultiplier(), "multiplier");
30+
assertEquals(1, reason.getReasons().size());
31+
assertEquals(
32+
"ANONYMOUS_IP",
33+
reason.getReasons().get(0).getCode(),
34+
"risk reason code"
35+
);
36+
assertEquals(
37+
"Risk due to IP being an Anonymous IP",
38+
reason.getReasons().get(0).getReason(),
39+
"risk reason"
40+
);
41+
}
42+
43+
@Test
44+
public void testEmptyObject() throws Exception {
45+
RiskScoreReason reason = this.deserialize(
46+
RiskScoreReason.class,
47+
"{}"
48+
);
49+
50+
assertNotNull(reason.getReasons());
51+
}
52+
}

0 commit comments

Comments
 (0)