Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
attributes to `Minfraud::Components::Billing` and
`Minfraud::Components::Shipping`. They describe the most recent
verification of the billing or shipping phone number.
* Added the `verification_time` and `was_verification_successful`
attributes to `Minfraud::Components::Email`. They describe the most
recent verification of the email address.
* Boolean inputs are now always sent as JSON booleans, and `false` is no
longer dropped. Previously, every input set to `false` was omitted.
Now only `nil` inputs are omitted. In particular:
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ assessment = Minfraud::Assessments.new(
username_md5: '4f9726678c438914fa04bdb8c1a24088',
},
email: {
address: 'test@maxmind.com',
domain: 'maxmind.com',
address: 'test@maxmind.com',
domain: 'maxmind.com',
verification_time: '2026-10-01T14:30:00Z',
was_verification_successful: true,
},
billing: {
first_name: 'First',
Expand Down
28 changes: 25 additions & 3 deletions lib/minfraud/components/email.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,32 @@ class Email < Base
# @return [Boolean, nil]
attr_accessor :hash_address

# The date and time of the most recent attempt to verify the email
# address, if known. The string must be in the RFC 3339 date-time
# format, e.g., "2012-04-12T23:20:50.52Z". The time must not be before
# 1970-01-01T00:00:00Z or more than two days in the future. The web
# service ignores a time outside this range and returns a warning.
#
# @see https://datatracker.ietf.org/doc/html/rfc3339
#
# @return [String, nil]
attr_accessor :verification_time

# Whether the most recent verification that the end user controls the
# email address succeeded, for example by confirming a link sent to the
# address. Do not include this field if no verification was attempted.
#
# @return [Boolean, nil]
attr_accessor :was_verification_successful

# @param params [Hash] Hash of parameters. Each key/value should
# correspond to one of the available attributes.
def initialize(params = {})
@address = params[:address]
@domain = params[:domain]
@hash_address = params[:hash_address]
@address = params[:address]
@domain = params[:domain]
@hash_address = params[:hash_address]
@verification_time = params[:verification_time]
@was_verification_successful = params[:was_verification_successful]

validate
end
Expand Down Expand Up @@ -72,6 +92,8 @@ def validate

validate_email('email', @address)
validate_string('domain', 255, @domain)
validate_rfc3339('verification_time', @verification_time)
validate_boolean('was_verification_successful', @was_verification_successful)
end

def hash_email_address(address)
Expand Down
27 changes: 26 additions & 1 deletion spec/components/email_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,34 @@
require 'spec_helper'

describe Minfraud::Components::Email do
it_behaves_like 'a component with verification inputs', ''

describe '#to_json' do
it 'sends the verification inputs with a hashed address' do
email = described_class.new(
address: 'test@maxmind.com',
domain: 'maxmind.com',
hash_address: true,
verification_time: '2026-10-01T14:30:00Z',
was_verification_successful: true,
)
expect(email.to_json).to eq(
'address' => '977577b140bfb7c516e4746204fbdb01',
'domain' => 'maxmind.com',
'verification_time' => '2026-10-01T14:30:00Z',
'was_verification_successful' => true,
)
end
end

describe 'validation' do
before do
around do |example|
previous = Minfraud.enable_validation

Minfraud.configure { |c| c.enable_validation = 1 }
example.run
ensure
Minfraud.configure { |c| c.enable_validation = previous }
end

it 'raises an exception for an invalid email address' do
Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'minfraud'
require_relative 'support/addressable_examples'
require_relative 'support/verification_examples'
53 changes: 6 additions & 47 deletions spec/support/addressable_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,12 @@
end.to raise_exception(Minfraud::NotEnumValueError)
end

[true, false].each do |successful|
it "sends phone_was_verification_successful #{successful} as a JSON boolean" do
component = described_class.new(
phone_verification_method: 'network',
phone_verification_time: '2026-10-01T14:30:00Z',
phone_was_verification_successful: successful,
)
expect(component.to_json).to eq(
'phone_verification_method' => 'network',
'phone_verification_time' => '2026-10-01T14:30:00Z',
'phone_was_verification_successful' => successful,
)
end
it 'sends phone_verification_method as a string' do
expect(described_class.new(phone_verification_method: :network).to_json).to eq(
'phone_verification_method' => 'network',
)
end

context 'with validation' do
around do |example|
previous = Minfraud.enable_validation

Minfraud.configure { |c| c.enable_validation = 1 }
example.run
ensure
Minfraud.configure { |c| c.enable_validation = previous }
end

it 'raises an exception for an invalid phone_was_verification_successful' do
expect do
described_class.new(
phone_was_verification_successful: 'true',
)
end.to raise_exception(Minfraud::InvalidInputError)
end

it 'raises an exception for an invalid phone_verification_time' do
expect do
described_class.new(
phone_verification_time: '2026-10-01 14:30:00',
)
end.to raise_exception(Minfraud::InvalidInputError)
end

it 'does not raise an exception for valid phone verification values' do
described_class.new(
phone_verification_method: :delivered_code,
phone_verification_time: '2026-10-01T14:30:00Z',
phone_was_verification_successful: false,
)
end
end
it_behaves_like 'a component with verification inputs', 'phone_',
phone_verification_method: :delivered_code
end
65 changes: 65 additions & 0 deletions spec/support/verification_examples.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

# The prefix is the text before verification_time and
# was_verification_successful in the field names, such as 'phone_'.
# other_valid holds more valid inputs to send with the verification values.
RSpec.shared_examples 'a component with verification inputs' do |prefix, other_valid = {}|
let(:time_field) { :"#{prefix}verification_time" }
let(:successful_field) { :"#{prefix}was_verification_successful" }

[true, false].each do |successful|
it "sends #{prefix}was_verification_successful #{successful} as a JSON boolean" do
component = described_class.new(
time_field => '2026-10-01T14:30:00Z',
successful_field => successful,
)
expect(component.to_json).to eq(
time_field.to_s => '2026-10-01T14:30:00Z',
successful_field.to_s => successful,
)
end
end

context 'with verification validation' do
around do |example|
previous = Minfraud.enable_validation

Minfraud.configure { |c| c.enable_validation = 1 }
example.run
ensure
Minfraud.configure { |c| c.enable_validation = previous }
end

it "raises an exception for an invalid #{prefix}verification_time" do
expect do
described_class.new(time_field => '2026-10-01 14:30:00')
end.to raise_exception(Minfraud::InvalidInputError)
end

it "raises an exception for a boolean #{prefix}verification_time" do
expect do
described_class.new(time_field => true)
end.to raise_exception(Minfraud::InvalidInputError)
end

it "raises an exception for an invalid #{prefix}was_verification_successful" do
expect do
described_class.new(successful_field => 'true')
end.to raise_exception(Minfraud::InvalidInputError)
end

it "raises an exception for a non-boolean #{prefix}was_verification_successful" do
expect do
described_class.new(successful_field => 1)
end.to raise_exception(Minfraud::InvalidInputError)
end

it 'does not raise an exception for valid verification values' do
described_class.new(
**other_valid,
time_field => '2026-10-01T14:30:00Z',
successful_field => false,
)
end
end
end
Loading