Skip to content
64 changes: 44 additions & 20 deletions lib/Experian/IDAuth.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package Experian::IDAuth;
use strict;
use warnings;

our $VERSION = '1.8';
our $VERSION = '2.0.0';

use Locale::Country;
use Path::Tiny;
Expand Down Expand Up @@ -358,7 +358,7 @@ sub _get_result_proveid {

return unless $credit_reference and $kyc_summary;

my $decision = {};
my $decision = { matches => []};

# check if client has died or fraud
my $cr_deceased = $credit_reference->findvalue('DeceasedMatch') || 0;
Expand All @@ -376,13 +376,11 @@ sub _get_result_proveid {
or $cr_deceased == 1 )
{
$decision->{deceased} = 1;
return $decision;
}

$report_summary{Fraud} ||= 0;
if ( $report_summary{Fraud} == 1 ) {
$decision->{fraud} = 1;
return $decision;
}

# check if client is age verified
Expand All @@ -392,33 +390,34 @@ sub _get_result_proveid {
if ( $kyc_dob or $cr_total ) {
$decision->{age_verified} = 1;
}
else {
return $decision;
}

# check if client is in any suspicious list
# we don't care about: NoOfCCJ, COAMatch
# we don't care about: COAMatch
#
# Add NoOfCCJ separately since we don't fail that one.

$decision->{CCJ} = 1 if $credit_reference->findvalue('NoOfCCJ');

my @matches =
map { $_->[0] }
grep { $_->[1] > 0 }
map { [ $_, $credit_reference->findvalue($_) || 0 ] }
qw(BOEMatch PEPMatch OFACMatch CIFASMatch);

if (@matches) {
if ( grep { /^(BOEMatch|PEPMatch|OFACMatch|CIFASMatch)$/ } @matches ) {
my @hard_fails = grep { my $f = $_;
grep { "${f}Match" eq $_ } @matches }
qw(BOE PEP OFAC CIFAS);
$decision->{$_} = 1 for @hard_fails;
$decision->{deny} = 1 if @hard_fails;

# BOEMatch PEPMatch OFAC and CIFAS are hard failures and need manual verification
delete $decision->{age_verified};
$decision->{deny} = 1;
}
$decision->{matches} = \@matches;
return $decision;
}

# if client is in Directors list, we should not fully authenticate him
if ( $report_summary{Directors} ) {
$decision->{matches} = ['Directors'];
return $decision;
$decision->{director} = 1;
$decision->{matches} = [ @{$decision->{matches}}, 'Directors' ];
}

# check if client can be fully authenticated
Expand Down Expand Up @@ -587,7 +586,7 @@ Experian::IDAuth - Experian's ID Authenticate service

=head1 VERSION

Version 1.8
Version 2.0.0

=head1 DESCRIPTION

Expand Down Expand Up @@ -641,15 +640,20 @@ Then use this module.
die;
}

if ($prove_id_result->{fully_authenticated}) {
# client successfully authenticated
}
if ($prove_id_result->{age_verified}) {
# client's age is verified
}
if ($prove_id_result->{deceased} || $prove_id_result->{fraud}) {
# client flagged as deceased or fraud
}
if ($prove_id_result->{deny}) {
# client on any of PEP, OFAC, or BOE list
# you can check $prove_id_result->{PEP} etc if you want more detail
}
if ($prove_id_result->{fully_authenticated}) {
# client successfully authenticated,
# DOES NOT MEAN NO CONCERNS
}

# CheckID is a more simpler version and can be used if ProveID_KYC fails
my $check_id = My::Experian->new(
Expand All @@ -665,6 +669,26 @@ Then use this module.
# client successfully authenticated
}

=head1 CHANGES FROM 1.X

The 2.x series of this module provides some significant changes from 1.x.

With 1.x, fully_authenticated could only suggest that there were no concerns
and that the client was fully authenticated. Now, we set this in addition to
any failure fields. For this reason it is important to handle failures
first and then check this response attribute. Note that the response attribute
also now shows the full list (not the first) set of possible concerns.

Therefore:

=over

=item One cannot assume that fully_authenticated means "success" and

=item Reasons for failure are no longer exclusive.

=back

=head1 AUTHOR

binary.com, C<perl at binary.com>
Expand Down
196 changes: 95 additions & 101 deletions t/decision.t
Original file line number Diff line number Diff line change
Expand Up @@ -1485,34 +1485,44 @@ my $fraud =<<EOD;
</Search>
EOD

eq_or_diff(
examine($fully1),
{
age_verified => 1,
fully_authenticated => 1,
},
"Fully1 fully authenticated"
);
eq_or_diff(
examine($fully2),
{
age_verified => 1,
fully_authenticated => 1,
},
"Fully2 fully authenticated"
);
eq_or_diff(examine($not_authenticated), {}, "Not authenticated as expected");
eq_or_diff(
examine($not_deceased),
{
age_verified => 1,
fully_authenticated => 1,
},
"Deceased with low certainty level was fully authenticated"
);
eq_or_diff(examine($deceased), {deceased => 1}, "Found deceased in report summary");
eq_or_diff(examine($cr_deceased), {deceased => 1}, "Found deceased in credit reference");
eq_or_diff(examine($fraud), {fraud => 1}, "Found fraud in report summary");
my $result = examine($fully1);
is($result->{age_verified}, 1, "Fully 1, age verified");
is($result->{fully_authenticated}, 1, 'Fully 1, Fully authenticated');
ok(not(exists $result->{deny}), 'Fully 1, not denied');

$result = examine($fully2);
is($result->{age_verified}, 1, "Fully 2, age verified");
is($result->{fully_authenticated}, 1, 'Fully 2, Fully authenticated');
ok(not(exists $result->{deny}), 'Fully 2, not denied');

$result = examine($not_authenticated);
ok(not(exists $result->{deny}), 'not authenticated, not denied');
ok(not(exists $result->{age_verified}), 'not authenticated, not age verified');
ok(not(exists $result->{fully_authenticated}), 'not authenticated');

$result = examine($not_deceased),
is($result->{age_verified}, 1, "Not deceased, age verified");
is($result->{fully_authenticated}, 1, 'Not deceased, Fully authenticated');
ok(not(exists $result->{deceased}), 'Not deceased, not deceased');
ok(not(exists $result->{deny}), 'Not deceased, not denied');

$result = examine($deceased);
is($result->{age_verified}, 1, "deceased, age verified");
is($result->{fully_authenticated}, 1, 'deceased, Fully authenticated');
is($result->{deceased}, 1, 'deceased, deceased');
ok(not(exists $result->{deny}), 'deceased, not denied');

$result = examine($cr_deceased);
is($result->{age_verified}, 1, "cr deceased, age verified");
is($result->{fully_authenticated}, 1, 'cr deceased, Fully authenticated');
is($result->{deceased}, 1, 'cr deceased, deceased');
ok(not(exists $result->{deny}), 'cr deceased, not denied');

$result = examine($fraud);
is($result->{age_verified}, 1, "fraud, age verified");
is($result->{fully_authenticated}, 1, 'fraud, Fully authenticated');
is($result->{fraud}, 1, 'fraud, fraud');
ok(not(exists $result->{deny}), 'fraud, not denied');

# this one has 2 in KYCSummary, so should be fully authenticated
my $age_only_1 =<<EOD;
Expand Down Expand Up @@ -2364,88 +2374,72 @@ my $age_only_10 =<<EOD;
</Search>
EOD

eq_or_diff(
examine($age_only_1),
{
age_verified => 1,
fully_authenticated => 1,
},
"Two in KYC Summary so fully authenticated"
);
$result = examine($age_only_1),
is($result->{age_verified}, 1, "Age only 1, age verified");
is($result->{fully_authenticated}, 1, 'Age only 1, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 1, not deceased');
ok(not(exists $result->{deny}), 'Age only 1, not denied');

eq_or_diff(
examine($age_only_2),
{
deny => 1,
matches => ['PEPMatch'],
},
" if PEP is set, then fail verification"
);
$result = examine($age_only_2),
is($result->{age_verified}, 1, "Age only 2, age verified");
is($result->{fully_authenticated}, 1, 'Age only 2, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 2, not deceased');
is($result->{deny}, 1, 'Age only 2, denied');
is($result->{PEP}, 1, 'Age only 2, PEP flagged');

eq_or_diff(
examine($age_only_3),
{
deny => 1,
matches => ['BOEMatch'],
},
" if BOEMatch is set, then fail verification"
);
$result = examine($age_only_3),
is($result->{age_verified}, 1, "Age only 3, age verified");
is($result->{fully_authenticated}, 1, 'Age only 3, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 3, not deceased');
is($result->{deny}, 1, 'Age only 3, denied');
is($result->{BOE}, 1, 'Age only 3, BOE flagged');

eq_or_diff(
examine($age_only_4),
{
deny => 1,
matches => ['OFACMatch'],
},
" if OFACMatch is set, then fail verification"
);
$result = examine($age_only_4),
is($result->{age_verified}, 1, "Age only 4, age verified");
is($result->{fully_authenticated}, 1, 'Age only 4, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 4, not deceased');
is($result->{deny}, 1, 'Age only 4, denied');
is($result->{OFAC}, 1, 'Age only 4, OFAC flagged');

eq_or_diff(
examine($age_only_5),
{
age_verified => 1,
fully_authenticated => 1,
},
" ignore COAMatch if set, fully authenticate if possible"
);
#Change of address handling
$result = examine($age_only_5),
is($result->{age_verified}, 1, "Age only 5, age verified");
is($result->{fully_authenticated}, 1, 'Age only 5, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 5, not deceased');
ok(not(exists $result->{deny}), 'Age only 5, not denied');

eq_or_diff(
examine($age_only_6),
{
deny => 1,
matches => ['CIFASMatch'],
},
" if CIFASMatch is set, then fail verification"
);
$result = examine($age_only_6),
is($result->{age_verified}, 1, "Age only 6, age verified");
is($result->{fully_authenticated}, 1, 'Age only 6, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 6, not deceased');
is($result->{deny}, 1, 'Age only 6, denied');
is($result->{CIFAS}, 1, 'Age only 6, CIFAS flagged');

eq_or_diff(
examine($age_only_7),
{
age_verified => 1,
fully_authenticated => 1,
},
" ignore CCJs if set, fully authenticate if possible"
);
$result = examine($age_only_7),
is($result->{age_verified}, 1, "Age only 7, age verified");
is($result->{fully_authenticated}, 1, 'Age only 7, Fully authenticated');
is($result->{CCJ}, 1, 'Age only 7, Has court judgements');
ok(not(exists $result->{deceased}), 'Age only 7, not deceased');
ok(not(exists $result->{deny}), 'Age only 7, not denied');

eq_or_diff(
examine($age_only_8),
{
age_verified => 1,
matches => ['Directors'],
},
" if Director, then age_verified only"
);
$result = examine($age_only_8),
is($result->{age_verified}, 1, "Age only 8, age verified");
is($result->{fully_authenticated}, 1, 'Age only 8, Fully authenticated');
is($result->{director}, 1, 'Age only 8, Is Director');
ok(not(exists $result->{deceased}), 'Age only 8, not deceased');
ok(not(exists $result->{deny}), 'Age only 8, not denied');

eq_or_diff(examine($age_only_9), {age_verified => 1,}, "If KYC Summary is 1, then age_verified");
$result = examine($age_only_9),
is($result->{age_verified}, 1, "Age only 9, age verified");
ok(not(exists $result->{fully_authenticated}), 'Age only 9, not authenticated');
ok(not(exists $result->{deceased}), 'Age only 9, not deceased');
ok(not(exists $result->{deny}), 'Age only 9, not denied');

eq_or_diff(
examine($age_only_10),
{
age_verified => 1,
fully_authenticated => 1,
},
"If Total number of verifications in Credit reference is 2, then fully_authenticated"
);
$result = examine($age_only_10),
is($result->{age_verified}, 1, "Age only 10, age verified");
is($result->{fully_authenticated}, 1, 'Age only 10, Fully authenticated');
ok(not(exists $result->{deceased}), 'Age only 10, not deceased');
ok(not(exists $result->{deny}), 'Age only 10, not denied');

Test::NoWarnings::had_no_warnings();
done_testing;
Expand Down
6 changes: 4 additions & 2 deletions t/xml_report.t
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ use Data::Dumper;
use lib 'lib';
use_ok( 'Experian::IDAuth' );

# clean up
system "rm -rf /tmp/proveid/";
my $tmp = $ENV{TEMP} || '/tmp'; # portability between windows and linux

unlink $_ for <"$tmp/proveid/*">;
rmdir "$tmp/proveid/*";

my $module = Test::MockModule->new('SOAP::Lite');
my $xml;
Expand Down