Skip to content

Commit

Permalink
Merge pull request #1415 from maxmind/nlogan/risk-reasons-beta
Browse files Browse the repository at this point in the history
Add risk reasons
  • Loading branch information
oschwald authored Sep 6, 2024
2 parents 89919a4 + 1f76fb1 commit 2964f71
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 10 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
CHANGELOG
=========

7.1.0-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.

7.0.0 (2024-07-08)
------------------

Expand Down
4 changes: 1 addition & 3 deletions e2e/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions e2e/ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions fixtures/reasons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"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"
}
]
}
]
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@maxmind/minfraud-api-node",
"version": "7.0.0",
"version": "7.1.0-beta.1",
"description": "Node.js API for MaxMind minFraud Score, Insights, and Factors web services",
"main": "dist/src/index.js",
"homepage": "https://github.com/maxmind/minfraud-api-node",
Expand Down
7 changes: 7 additions & 0 deletions src/response/models/factors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import * as webRecords from '../web-records';
import Insights from './insights';

export default class Factors extends Insights {
/**
* An array of risk score reason objects that describe a risk score
* multiplier and the reasons for that multiplier.
*/
public readonly riskScoreReasons?: records.RiskScoreReason[];

/**
* An object containing GeoIP2 and minFraud Insights information about the IP
* address.
Expand All @@ -13,6 +19,7 @@ export default class Factors extends Insights {
public constructor(response: webRecords.FactorsResponse) {
super(response);

this.riskScoreReasons = response.risk_score_reasons;
this.subscores = camelizeResponse(response.subscores) as records.Subscores;
}
}
100 changes: 100 additions & 0 deletions src/response/records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,106 @@ export interface Disposition {
readonly ruleLabel?: string;
}

/**
* The risk score reason for the multiplier.
*
* This class provides both a machine-readable code and a human-readable
* explanation of the reason for the risk score. See
* {@link https://dev.maxmind.com/minfraud/api-documentation/responses/#schema--response--risk-score-reason--multiplier-reason | the response API documentation}.
*
* Although more codes may be added in the future, the current codes are:
*
* * `BROWSER_LANGUAGE` - Riskiness of the browser user-agent and language
* associated with the request.
* * `BUSINESS_ACTIVITY` - Riskiness of business activity associated with the
* request.
* * `COUNTRY` - Riskiness of the country associated with the request.
* * `CUSTOMER_ID` - Riskiness of a customer's activity.
* * `EMAIL_DOMAIN` - Riskiness of email domain.
* * `EMAIL_DOMAIN_NEW` - Riskiness of newly-sighted email domain.
* * `EMAIL_ADDRESS_NEW` - Riskiness of newly-sighted email address.
* * `EMAIL_LOCAL_PART` - Riskiness of the local part of the email address.
* * `EMAIL_VELOCITY` - Velocity on email - many requests on same email over
* short period of time.
* * `ISSUER_ID_NUMBER_COUNTRY_MISMATCH` - Riskiness of the country mismatch
* between IP, billing, shipping and IIN country.
* * `ISSUER_ID_NUMBER_ON_SHOP_ID` - Risk of Issuer ID Number for the shop ID.
* * `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.
* * `ISSUER_ID_NUMBER_SHOP_ID_VELOCITY` - Risk of recent Issuer ID Number
* activity for the shop ID.
* * `INTRACOUNTRY_DISTANCE` - Risk of distance between IP, billing, and
* shipping location.
* * `ANONYMOUS_IP` - Risk due to IP being an Anonymous IP.
* * `IP_BILLING_POSTAL_VELOCITY` - Velocity of distinct billing postal code on
* IP address.
* * `IP_EMAIL_VELOCITY` - Velocity of distinct email address on IP address.
* * `IP_HIGH_RISK_DEVICE` - High-risk device sighted on IP address.
* * `IP_ISSUER_ID_NUMBER_VELOCITY` - Velocity of distinct IIN on IP address.
* * `IP_ACTIVITY` - Riskiness of IP based on minFraud network activity.
* * `LANGUAGE` - Riskiness of browser language.
* * `MAX_RECENT_EMAIL` - Riskiness of email address based on past minFraud
* risk scores on email.
* * `MAX_RECENT_PHONE` - Riskiness of phone number based on past minFraud risk
* scores on phone.
* * `MAX_RECENT_SHIP` - Riskiness of email address based on past minFraud risk
* scores on ship address.
* * `MULTIPLE_CUSTOMER_ID_ON_EMAIL` - Riskiness of email address having many
* customer IDs.
* * `ORDER_AMOUNT` - Riskiness of the order amount.
* * `ORG_DISTANCE_RISK` - Risk of ISP and distance between billing address and
* IP location.
* * `PHONE` - Riskiness of the phone number or related numbers.
* * `CART` - Riskiness of shopping cart contents.
* * `TIME_OF_DAY` - Risk due to local time of day.
* * `TRANSACTION_REPORT_EMAIL` - Risk due to transaction reports on the email
* address.
* * `TRANSACTION_REPORT_IP` - Risk due to transaction reports on the IP
* address.
* * `TRANSACTION_REPORT_PHONE` - Risk due to transaction reports on the phone
* number.
* * `TRANSACTION_REPORT_SHIP` - Risk due to transaction reports on the
* shipping address.
* * `EMAIL_ACTIVITY` - Riskiness of the email address based on minFraud
* network activity.
* * `PHONE_ACTIVITY` - Riskiness of the phone number based on minFraud network
* activity.
* * `SHIP_ACTIVITY` - Riskiness of ship address based on minFraud network
* activity.
*/
export interface Reason {
/**
* A machine-readable code identifying the reason.
*/
code: string;
/**
* A human-readable explanation of the reason. The description may change at
* any time and should not be matched against.
*/
reason: string;
}

/**
* The object describing the risk score multiplier and the reasons for that
* multiplier.
*/
export interface RiskScoreReason {
/**
* 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.
*/
multiplier: number;
/**
* An array containing Reason objects that describe one of the reasons for
* the multiplier.
*/
reasons: Reason[];
}

/**
* This object contains scores for many of the individual risk factors that
* are used to calculate the overall risk score.
Expand Down
11 changes: 11 additions & 0 deletions src/response/web-records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ export interface PhoneWebRecord {
readonly number_type?: string;
}

export interface ReasonWebRecord {
code: string;
reason: string;
}

export interface RiskScoreReasonWebRecord {
multiplier: number;
reasons: ReasonWebRecord[];
}

export interface SubscoresWebRecord {
readonly avs_result?: number;
readonly billing_address?: number;
Expand Down Expand Up @@ -172,5 +182,6 @@ export interface InsightsResponse extends ScoreResponse {
}

export interface FactorsResponse extends InsightsResponse {
readonly risk_score_reasons?: RiskScoreReasonWebRecord[];
readonly subscores: SubscoresWebRecord;
}
12 changes: 11 additions & 1 deletion src/webServiceClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cloneDeep = require('lodash.clonedeep');
import nock from 'nock';
import * as models from './response/models';
import * as insights from '../fixtures/insights.json';
import reasons from '../fixtures/reasons.json';
import * as score from '../fixtures/score.json';
import * as subscores from '../fixtures/subscores.json';
import {
Expand All @@ -26,6 +27,7 @@ const client = new Client(auth.user, auth.pass);
describe('WebServiceClient', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const factors = cloneDeep(insights) as any;
factors.response.full.risk_score_reasons = cloneDeep(reasons);
factors.response.full.subscores = cloneDeep(subscores);

describe('factors()', () => {
Expand All @@ -36,7 +38,7 @@ describe('WebServiceClient', () => {
});

it('handles "full" responses', async () => {
expect.assertions(167);
expect.assertions(172);

nockInstance
.post(fullPath('factors'), factors.request.basic)
Expand Down Expand Up @@ -251,6 +253,14 @@ describe('WebServiceClient', () => {
);
expect(got.warnings?.[0].inputPointer).toEqual('/shipping/city');

expect(got.riskScoreReasons).toHaveLength(4);
expect(got.riskScoreReasons?.[0].multiplier).toEqual(45);
expect(got.riskScoreReasons?.[0].reasons).toHaveLength(1);
expect(got.riskScoreReasons?.[0].reasons[0].code).toEqual('ANONYMOUS_IP');
expect(got.riskScoreReasons?.[0].reasons[0].reason).toEqual(
'Risk due to IP being an Anonymous IP'
);

expect(got?.subscores?.avsResult).toEqual(0.01);
expect(got?.subscores?.billingAddress).toEqual(0.02);
expect(got?.subscores?.billingAddressDistanceToIpLocation).toEqual(0.03);
Expand Down

0 comments on commit 2964f71

Please sign in to comment.