Skip to content

Commit

Permalink
Add risk score reasons
Browse files Browse the repository at this point in the history
  • Loading branch information
marselester committed Aug 27, 2024
1 parent 6cb4a1a commit 674457a
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
CHANGELOG
=========

3.5.1-beta.1
------------------

* Added support for the new risk reasons outputs in minFraud Factors. The risk
reasons output codes and reasons are currently in beta and are subject to
change. We recommend that you use these beta outputs with caution and avoid
relying on them for critical applications.

3.5.0 (2024-07-08)
------------------
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/maxmind/minfraud/response/FactorsResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
public final class FactorsResponse extends InsightsResponse {

private final List<RiskScoreReason> riskScoreReasons;
private final Subscores subscores;


Expand All @@ -26,6 +27,11 @@ public final class FactorsResponse extends InsightsResponse {
* @param queriesRemaining The number of queries remaining.
* @param riskScore The risk score.
* @param shippingAddress The {@code ShippingAddress} model object.
* @param riskScoreReasons A list containing objects that describe
* risk score reasons for a given transaction that change
* the risk score significantly. Risk score reasons are usually only returned
* for medium to high risk transactions. If there were no significant changes
* to the risk score due to these reasons, then this list will be empty.
* @param subscores The {@code Subscores} model object.
* @param warnings A list containing warning objects.
*/
Expand All @@ -43,12 +49,14 @@ public FactorsResponse(
@JsonProperty("risk_score") Double riskScore,
@JsonProperty("shipping_address") ShippingAddress shippingAddress,
@JsonProperty("shipping_phone") Phone shippingPhone,
@JsonProperty("risk_score_reasons") List<RiskScoreReason> riskScoreReasons,
@JsonProperty("subscores") Subscores subscores,
@JsonProperty("warnings") List<Warning> warnings
) {
super(billingAddress, billingPhone, creditCard, device, disposition, email,
fundsRemaining, id, ipAddress, queriesRemaining, riskScore,
shippingAddress, shippingPhone, warnings);
this.riskScoreReasons = riskScoreReasons;
this.subscores = subscores;
}

Expand Down Expand Up @@ -77,6 +85,15 @@ public FactorsResponse(
ipAddress, queriesRemaining, riskScore, shippingAddress, null, subscores, warnings);
}

/**
* @return A list containing objects that describe risk score reasons
* for a given transaction that change the risk score significantly.
*/
@JsonProperty("risk_score_reasons")
public List<RiskScoreReason> getRiskScoreReasons() {
return riskScoreReasons;
}

/**
* @return The {@code Subscores} model object containing the risk factor scores.
*/
Expand Down
84 changes: 84 additions & 0 deletions src/main/java/com/maxmind/minfraud/response/Reason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.maxmind.minfraud.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.maxmind.minfraud.AbstractModel;

/**
* This class represents a risk score reason for the multiplier.
*/
public final class Reason extends AbstractModel {
private final String code;
private final String reason;

/**
* Constructor for {@code Reason}.
*
* @param code The code.
* @param reason The reason.
*/
public Reason(
@JsonProperty("code") String code,
@JsonProperty("reason") String reason,
) {
this.code = code;
this.reason = reason;
}

/**
* This field provides a machine-readable code identifying the reason.
* Although more codes may be added in the future, the current codes are:
*
* <ul>
* <li>BROWSER_LANGUAGE - Riskiness of the browser user-agent and language associated with the request.</li>
* <li>BUSINESS_ACTIVITY - Riskiness of business activity associated with the request.</li>
* <li>COUNTRY - Riskiness of the country associated with the request.</li>
* <li>CUSTOMER_ID - Riskiness of a customer's activity.</li>
* <li>EMAIL_DOMAIN - Riskiness of email domain.</li>
* <li>EMAIL_DOMAIN_NEW - Riskiness of newly-sighted email domain.</li>
* <li>EMAIL_ADDRESS_NEW - Riskiness of newly-sighted email address.</li>
* <li>EMAIL_LOCAL_PART - Riskiness of the local part of the email address.</li>
* <li>EMAIL_VELOCITY - Velocity on email - many requests on same email over short period of time.</li>
* <li>ISSUER_ID_NUMBER_COUNTRY_MISMATCH - Riskiness of the country mismatch between IP, billing, shipping and IIN country.</li>
* <li>ISSUER_ID_NUMBER_ON_SHOP_ID - Risk of Issuer ID Number for the shop ID.</li>
* <li>ISSUER_ID_NUMBER_LAST_DIGITS_ACTIVITY - Riskiness of many recent requests and previous high-risk requests on the IIN and last digits of the credit card.</li>
* <li>ISSUER_ID_NUMBER_SHOP_ID_VELOCITY - Risk of recent Issuer ID Number activity for the shop ID.</li>
* <li>INTRACOUNTRY_DISTANCE - Risk of distance between IP, billing, and shipping location.</li>
* <li>ANONYMOUS_IP - Risk due to IP being an Anonymous IP.</li>
* <li>IP_BILLING_POSTAL_VELOCITY - Velocity of distinct billing postal code on IP address.</li>
* <li>IP_EMAIL_VELOCITY - Velocity of distinct email address on IP address.</li>
* <li>IP_HIGH_RISK_DEVICE - High-risk device sighted on IP address.</li>
* <li>IP_ISSUER_ID_NUMBER_VELOCITY - Velocity of distinct IIN on IP address.</li>
* <li>IP_ACTIVITY - Riskiness of IP based on minFraud network activity.</li>
* <li>LANGUAGE - Riskiness of browser language.</li>
* <li>MAX_RECENT_EMAIL - Riskiness of email address based on past minFraud risk scores on email.</li>
* <li>MAX_RECENT_PHONE - Riskiness of phone number based on past minFraud risk scores on phone.</li>
* <li>MAX_RECENT_SHIP - Riskiness of email address based on past minFraud risk scores on ship address.</li>
* <li>MULTIPLE_CUSTOMER_ID_ON_EMAIL - Riskiness of email address having many customer IDs.</li>
* <li>ORDER_AMOUNT - Riskiness of the order amount.</li>
* <li>ORG_DISTANCE_RISK - Risk of ISP and distance between billing address and IP location.</li>
* <li>PHONE - Riskiness of the phone number or related numbers.</li>
* <li>CART - Riskiness of shopping cart contents.</li>
* <li>TIME_OF_DAY - Risk due to local time of day.</li>
* <li>TRANSACTION_REPORT_EMAIL - Risk due to transaction reports on the email address.</li>
* <li>TRANSACTION_REPORT_IP - Risk due to transaction reports on the IP address.</li>
* <li>TRANSACTION_REPORT_PHONE - Risk due to transaction reports on the phone number.</li>
* <li>TRANSACTION_REPORT_SHIP - Risk due to transaction reports on the shipping address.</li>
* <li>EMAIL_ACTIVITY - Riskiness of the email address based on minFraud network activity.</li>
* <li>PHONE_ACTIVITY - Riskiness of the phone number based on minFraud network activity.</li>
* <li>SHIP_ACTIVITY - Riskiness of ship address based on minFraud network activity.</li>
* </ul>
*
* @return The code.
*/
public String getCode() {
return this.code;
}

/**
* @return The human-readable explanation of the reason.
* The description may change at any time and should not be matched against.
*/
public String getReason() {
return this.reason;
}
}
51 changes: 51 additions & 0 deletions src/main/java/com/maxmind/minfraud/response/RiskScoreReason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.maxmind.minfraud.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.maxmind.minfraud.AbstractModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* This class represents a risk score multiplier and reasons for that multiplier.
*/
public final class RiskScoreReason extends AbstractModel {
private final Double multiplier;
private final List<Reason> reasons;

/**
* Constructor for {@code RiskScoreReason}.
*
* @param multiplier The multiplier.
* @param reasons The reasons.
*/
public RiskScoreReason(
@JsonProperty("multiplier") Double multiplier,
@JsonProperty("reasons") List<Reason> reasons,
) {
this.multiplier = multiplier;
this.reasons =
Collections.unmodifiableList(reasons == null ? new ArrayList<>() : reasons);
}

/**
* @return The factor by which the risk score is increased (if the value is greater than 1)
* or decreased (if the value is less than 1) for given risk reason(s).
* Multipliers greater than 1.5 and less than 0.66 are considered significant
* and lead to risk reason(s) being present.
*/
@JsonProperty("multiplier")
public Double getMultiplier() {
return multiplier;
}

/**
* @return An unmodifiable list containing objects that describe
* one of the reasons for the multiplier.
* This will be an empty list if there are no reasons.
*/
@JsonProperty("reasons")
public List<Reason> getReasons() {
return reasons;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public void testFactors() throws Exception {
.put("queries_remaining", 123)
.put("id", id)
.put("risk_score", 0.01)
.startArrayField("risk_score_reasons")
.startObject()
.put("multiplier", 45)
.startArrayField("reasons")
.startObject()
.put("code", "ANONYMOUS_IP")
.put("reason", "Risk due to IP being an Anonymous IP")
.end()
.end()
.end()
.end()
.end()
.finish()
);
Expand Down Expand Up @@ -147,5 +158,20 @@ public void testFactors() throws Exception {
factors.getRiskScore(),
"correct risk score"
);
assertEquals(
Double.valueOf(45),
factors.getRiskScoreReasons().get(0).getMultiplier(),
"risk multiplier"
);
assertEquals(
"ANONYMOUS_IP",
factors.getRiskScoreReasons().get(0).getReasons().get(0).getCode(),
"risk reason code"
);
assertEquals(
"Risk due to IP being an Anonymous IP",
factors.getRiskScoreReasons().get(0).getReasons().get(0).getReason(),
"risk reason"
);
}
}
30 changes: 30 additions & 0 deletions src/test/java/com/maxmind/minfraud/response/Reason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.maxmind.minfraud.response;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.jr.ob.JSON;
import org.junit.jupiter.api.Test;

public class ReasonTest extends AbstractOutputTest {

@Test
public void testReason() throws Exception {
String code = "ANONYMOUS_IP";
String msg = "Risk due to IP being an Anonymous IP";

Reason reason = this.deserialize(
Reason.class,
JSON.std
.composeString()
.startObject()
.put("code", code)
.put("reason", msg)
.end()
.finish()
);

assertEquals(code, reason.getCode(), "code");
assertEquals(msg, reason.getReason(), "reason");
}

}
52 changes: 52 additions & 0 deletions src/test/java/com/maxmind/minfraud/response/RiskScoreReason.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.maxmind.minfraud.response;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.jr.ob.JSON;
import org.junit.jupiter.api.Test;

public class RiskScoreReasonTest extends AbstractOutputTest {

@Test
public void testRiskScoreReason() throws Exception {
RiskScoreReason reason = this.deserialize(
RiskScoreReason.class,
JSON.std
.composeString()
.startObject()
.put("multiplier", 45)
.startArrayField("reasons")
.startObject()
.put("code", "ANONYMOUS_IP")
.put("reason", "Risk due to IP being an Anonymous IP")
.end()
.end()
.end()
.finish()
);

assertEquals(Double.valueOf(45), reason.getMultiplier(), "multiplier");
assertEquals(
"ANONYMOUS_IP",
reason.getReasons().get(0).getCode(),
"risk reason code"
);
assertEquals(
"Risk due to IP being an Anonymous IP",
reason.getReasons().get(0).getReason(),
"risk reason"
);
}

@Test
public void testEmptyObject() throws Exception {
RiskScoreReason reason = this.deserialize(
RiskScoreReason.class,
"{}"
);

assertNotNull(reason.getReasons());
}
}
38 changes: 38 additions & 0 deletions src/test/resources/test-data/factors-response.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,43 @@
"input_pointer": "/account/username_md5",
"warning": "Encountered value at \/account\/username_md5 that does meet the required constraints"
}
],
"risk_score_reasons": [
{
"multiplier": 45,
"reasons": [
{
"code": "ANONYMOUS_IP",
"reason": "Risk due to IP being an Anonymous IP"
}
]
},
{
"multiplier": 1.8,
"reasons": [
{
"code": "TIME_OF_DAY",
"reason": "Risk due to local time of day"
}
]
},
{
"multiplier": 1.6,
"reasons": [
{
"reason": "Riskiness of newly-sighted email domain",
"code": "EMAIL_DOMAIN_NEW"
}
]
},
{
"multiplier": 0.34,
"reasons": [
{
"code": "EMAIL_ADDRESS_NEW",
"reason": "Riskiness of newly-sighted email address"
}
]
}
]
}

0 comments on commit 674457a

Please sign in to comment.