Skip to content

Commit 47b24f7

Browse files
committed
Add risk score reasons
1 parent 088cf43 commit 47b24f7

File tree

7 files changed

+209
-4
lines changed

7 files changed

+209
-4
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v2.7.0-beta.1
4+
5+
* Added support for the new risk reasons outputs in minFraud Factors. The risk
6+
reasons output codes and reasons are currently in beta and are subject to
7+
change. We recommend that you use these beta outputs with caution and avoid
8+
relying on them for critical applications.
9+
310
## v2.6.0 (2024-07-08)
411

512
* Updated the validation for the Report Transactions API to make the

lib/minfraud/model/factors.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@
22

33
require 'minfraud/model/insights'
44
require 'minfraud/model/subscores'
5+
require 'minfraud/model/risk_score_reason'
56

67
module Minfraud
78
module Model
89
# Model representing the Factors response.
910
class Factors < Insights
11+
# This field contains RiskScoreReason objects that describe risk score reasons
12+
# for a given transaction that change the risk score significantly.
13+
# Risk score reasons are usually only returned for medium to high risk transactions.
14+
# If there were no significant changes to the risk score due to these reasons,
15+
# then this array will be empty.
16+
#
17+
# @return [Array<Minfraud::Model::RiskScoreReason>]
18+
attr_reader :risk_score_reasons
19+
1020
# An object containing scores for many of the individual risk factors
1121
# that are used to calculate the overall risk score.
1222
#
@@ -17,6 +27,13 @@ class Factors < Insights
1727
def initialize(record, locales)
1828
super
1929

30+
@risk_score_reasons = []
31+
if record&.key?('risk_score_reasons')
32+
record['risk_score_reasons'].each do |r|
33+
@risk_score_reasons << Minfraud::Model::RiskScoreReason.new(r)
34+
end
35+
end
36+
2037
@subscores = Minfraud::Model::Subscores.new(get('subscores'))
2138
end
2239
end

lib/minfraud/model/reason.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: true
2+
3+
require 'minfraud/model/abstract'
4+
5+
module Minfraud
6+
module Model
7+
# The risk score reason for the multiplier.
8+
#
9+
# This class provides both a machine-readable code and a human-readable
10+
# explanation of the reason for the risk score, see
11+
# https://dev.maxmind.com/minfraud/api-documentation/responses/schema--response--risk-score-reason--multiplier-reason.
12+
# Although more codes may be added in the future, the current codes are:
13+
#
14+
# * BROWSER_LANGUAGE - Riskiness of the browser user-agent and
15+
# language associated with the request.
16+
# * BUSINESS_ACTIVITY - Riskiness of business activity
17+
# associated with the request.
18+
# * COUNTRY - Riskiness of the country associated with the request.
19+
# * CUSTOMER_ID - Riskiness of a customer's activity.
20+
# * EMAIL_DOMAIN - Riskiness of email domain.
21+
# * EMAIL_DOMAIN_NEW - Riskiness of newly-sighted email domain.
22+
# * EMAIL_ADDRESS_NEW - Riskiness of newly-sighted email address.
23+
# * EMAIL_LOCAL_PART - Riskiness of the local part of the email address.
24+
# * EMAIL_VELOCITY - Velocity on email - many requests on same email
25+
# over short period of time.
26+
# * ISSUER_ID_NUMBER_COUNTRY_MISMATCH - Riskiness of the country mismatch
27+
# between IP, billing, shipping and IIN country.
28+
# * ISSUER_ID_NUMBER_ON_SHOP_ID - Risk of Issuer ID Number for the shop ID.
29+
# * ISSUER_ID_NUMBER_LAST_DIGITS_ACTIVITY - Riskiness of many recent requests
30+
# and previous high-risk requests on the IIN and last digits of the credit card.
31+
# * ISSUER_ID_NUMBER_SHOP_ID_VELOCITY - Risk of recent Issuer ID Number activity
32+
# for the shop ID.
33+
# * INTRACOUNTRY_DISTANCE - Risk of distance between IP, billing,
34+
# and shipping location.
35+
# * ANONYMOUS_IP - Risk due to IP being an Anonymous IP.
36+
# * IP_BILLING_POSTAL_VELOCITY - Velocity of distinct billing postal code
37+
# on IP address.
38+
# * IP_EMAIL_VELOCITY - Velocity of distinct email address on IP address.
39+
# * IP_HIGH_RISK_DEVICE - High-risk device sighted on IP address.
40+
# * IP_ISSUER_ID_NUMBER_VELOCITY - Velocity of distinct IIN on IP address.
41+
# * IP_ACTIVITY - Riskiness of IP based on minFraud network activity.
42+
# * LANGUAGE - Riskiness of browser language.
43+
# * MAX_RECENT_EMAIL - Riskiness of email address
44+
# based on past minFraud risk scores on email.
45+
# * MAX_RECENT_PHONE - Riskiness of phone number
46+
# based on past minFraud risk scores on phone.
47+
# * MAX_RECENT_SHIP - Riskiness of email address
48+
# based on past minFraud risk scores on ship address.
49+
# * MULTIPLE_CUSTOMER_ID_ON_EMAIL - Riskiness of email address
50+
# having many customer IDs.
51+
# * ORDER_AMOUNT - Riskiness of the order amount.
52+
# * ORG_DISTANCE_RISK - Risk of ISP and distance between
53+
# billing address and IP location.
54+
# * PHONE - Riskiness of the phone number or related numbers.
55+
# * CART - Riskiness of shopping cart contents.
56+
# * TIME_OF_DAY - Risk due to local time of day.
57+
# * TRANSACTION_REPORT_EMAIL - Risk due to transaction reports
58+
# on the email address.
59+
# * TRANSACTION_REPORT_IP - Risk due to transaction reports on the IP address.
60+
# * TRANSACTION_REPORT_PHONE - Risk due to transaction reports
61+
# on the phone number.
62+
# * TRANSACTION_REPORT_SHIP - Risk due to transaction reports
63+
# on the shipping address.
64+
# * EMAIL_ACTIVITY - Riskiness of the email address
65+
# based on minFraud network activity.
66+
# * PHONE_ACTIVITY - Riskiness of the phone number
67+
# based on minFraud network activity.
68+
# * SHIP_ACTIVITY - Riskiness of ship address based on minFraud network activity.
69+
class Reason < Abstract
70+
# This value is a machine-readable code identifying the reason.
71+
#
72+
# @return [String]
73+
attr_reader :code
74+
75+
# This property provides a human-readable explanation of the reason. The
76+
# description may change at any time and should not be matched against.
77+
#
78+
# @return [String]
79+
attr_reader :reason
80+
81+
# @!visibility private
82+
def initialize(record)
83+
super
84+
85+
@code = get('code')
86+
@reason = get('reason')
87+
end
88+
end
89+
end
90+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require 'minfraud/model/reason'
4+
5+
module Minfraud
6+
module Model
7+
# The risk score multiplier and the reasons for that multiplier.
8+
class RiskScoreReason < Abstract
9+
# The factor by which the risk score is increased (if the value is greater than 1)
10+
# or decreased (if the value is less than 1) for given risk reason(s).
11+
# Multipliers greater than 1.5 and less than 0.66 are considered significant
12+
# and lead to risk reason(s) being present.
13+
#
14+
# @return [Float]
15+
attr_reader :multiplier
16+
17+
# This field contains Risk objects that describe one of the reasons for the multiplier.
18+
#
19+
# @return [Array<Minfraud::Model::Risk>]
20+
attr_reader :reasons
21+
22+
# @!visibility private
23+
def initialize(record)
24+
super
25+
26+
@multiplier = get('multiplier')
27+
28+
@reasons = []
29+
if record&.key?('reasons')
30+
record['reasons'].each do |r|
31+
@reasons << Minfraud::Model::Reason.new(r)
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end

minfraud.gemspec

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ Gem::Specification.new do |spec|
2222
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
2323
spec.require_paths = ['lib']
2424

25-
spec.add_runtime_dependency 'connection_pool', '~> 2.2'
26-
spec.add_runtime_dependency 'http', '>= 4.3', '< 6.0'
27-
spec.add_runtime_dependency 'maxmind-geoip2', '~> 1.2'
28-
spec.add_runtime_dependency 'simpleidn', '~> 0.1', '>= 0.1.1'
25+
spec.add_dependency 'connection_pool', '~> 2.2'
26+
spec.add_dependency 'http', '>= 4.3', '< 6.0'
27+
spec.add_dependency 'maxmind-geoip2', '~> 1.2'
28+
spec.add_dependency 'simpleidn', '~> 0.1', '>= 0.1.1'
2929

3030
spec.add_development_dependency 'bundler', '~> 2.2'
3131
spec.add_development_dependency 'rake', '~> 13.0'

spec/fixtures/files/factors-response1.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,5 +206,43 @@
206206
"input_pointer": "/account/username_md5",
207207
"warning": "Encountered value at /account/username_md5 that does meet the required constraints"
208208
}
209+
],
210+
"risk_score_reasons": [
211+
{
212+
"multiplier": 45.0,
213+
"reasons": [
214+
{
215+
"code": "ANONYMOUS_IP",
216+
"reason": "Risk due to IP being an Anonymous IP"
217+
}
218+
]
219+
},
220+
{
221+
"multiplier": 1.8,
222+
"reasons": [
223+
{
224+
"code": "TIME_OF_DAY",
225+
"reason": "Risk due to local time of day"
226+
}
227+
]
228+
},
229+
{
230+
"multiplier": 1.6,
231+
"reasons": [
232+
{
233+
"reason": "Riskiness of newly-sighted email domain",
234+
"code": "EMAIL_DOMAIN_NEW"
235+
}
236+
]
237+
},
238+
{
239+
"multiplier": 0.34,
240+
"reasons": [
241+
{
242+
"code": "EMAIL_ADDRESS_NEW",
243+
"reason": "Riskiness of newly-sighted email address"
244+
}
245+
]
246+
}
209247
]
210248
}

spec/model/factors_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@
3131
expect(m.subscores.shipping_address).to eq 0.2
3232
expect(m.subscores.shipping_address_distance_to_ip_location).to eq 0.16
3333
expect(m.subscores.time_of_day).to eq 0.17
34+
35+
expect(m.risk_score_reasons.length).to eq 4
36+
expect(m.risk_score_reasons[0].multiplier).to eq 45.0
37+
expect(m.risk_score_reasons[0].reasons.length).to eq 1
38+
expect(m.risk_score_reasons[0].reasons[0].code).to eq 'ANONYMOUS_IP'
39+
expect(m.risk_score_reasons[0].reasons[0].reason).to eq 'Risk due to IP being an Anonymous IP'
40+
end
41+
42+
it 'checks absence of risk score reasons' do
43+
buf = File.read('spec/fixtures/files/factors-response1.json')
44+
record = JSON.parse(buf)
45+
record.delete('risk_score_reasons')
46+
47+
m = Minfraud::Model::Factors.new(record, ['en'])
48+
49+
expect(m.risk_score_reasons).to eq []
3450
end
3551
end
3652
end

0 commit comments

Comments
 (0)