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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ CHANGELOG
`phoneWasVerificationSuccessful`, and `phoneVerificationTime` named
arguments. Omit `phone_was_verification_successful` if no verification was
attempted.
* Added the `verification_time` and `was_verification_successful` inputs to
the `/email` object. These describe the most recent verification of the
email address. Use them with `withEmail()` as array keys or as the
`verificationTime` and `wasVerificationSuccessful` named arguments. Omit
`was_verification_successful` if no verification was attempted.

3.7.0 (2026-07-21)
------------------
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ $request = $mf->withDevice(
usernameMd5: '4f9726678c438914fa04bdb8c1a24088'
)->withEmail(
address: 'test@maxmind.com',
domain: 'maxmind.com'
domain: 'maxmind.com',
verificationTime: '2026-10-01T14:30:00Z',
wasVerificationSuccessful: true
)->withBilling(
firstName: 'First',
lastName: 'Last',
Expand Down
45 changes: 37 additions & 8 deletions src/MinFraud.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,14 +487,26 @@ public function withAccount(
* @link https://dev.maxmind.com/minfraud/api-documentation/requests/?lang=en#schema--request--email
* minFraud email API docs
*
* @param array<string, mixed> $values An array of email data. The keys are the same as
* the JSON keys. You may use either this or the named
* arguments, but not both.
* @param string|null $address The email address used in the transaction.
* This field must be a valid email address.
* @param string|null $domain The domain of the email address used in the
* transaction. Do not include the `@` in this
* field.
* @param array<string, mixed> $values An array of email data. The keys are the same as
* the JSON keys. You may use either this or the named
* arguments, but not both.
* @param string|null $address The email address used in the transaction.
* This field must be a valid email address.
* @param string|null $domain The domain of the email address used in the
* transaction. Do not include the `@` in this
* field.
* @param string|null $verificationTime The date and time of the most recent
* attempt to verify the email address, if
* known, in RFC 3339 date-time format. The
* time must not be before
* 1970-01-01T00:00:00Z or more than two days
* in the future.
* @param bool|null $wasVerificationSuccessful Whether the most recent verification
* that the end user controls the email
* address succeeded, for example by
* confirming a link sent to the address.
* Omit this if no verification was
* attempted.
*
* @throws InvalidInputException if a value has the wrong type, or if
* input validation is enabled and a value is
Expand All @@ -507,6 +519,8 @@ public function withEmail(
array $values = [],
?string $address = null,
?string $domain = null,
?string $verificationTime = null,
?bool $wasVerificationSuccessful = null,
): self {
if (\count($values) !== 0) {
if (\func_num_args() !== 1) {
Expand All @@ -516,6 +530,12 @@ public function withEmail(
}
$address = $this->remove($values, 'address');
$domain = $this->remove($values, 'domain');
$verificationTime = $this->remove($values, 'verification_time');
$wasVerificationSuccessful = $this->remove(
$values,
'was_verification_successful',
['boolean'],
);

$this->verifyEmpty($values);
}
Expand All @@ -535,6 +555,15 @@ public function withEmail(
$values['domain'] = $domain;
}

if ($verificationTime !== null) {
$this->verifyRfc3339DateTime($verificationTime);
$values['verification_time'] = $verificationTime;
}

if ($wasVerificationSuccessful !== null) {
$values['was_verification_successful'] = $wasVerificationSuccessful;
}

$new = clone $this;
$new->content['email'] = $values;

Expand Down
115 changes: 112 additions & 3 deletions tests/MaxMind/Test/MinFraudTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public function testWrongTypeWithoutValidation(string $method, string $field): v
'phone_was_verification_successful',
'was_3d_secure_successful',
'was_authorized',
'was_verification_successful',
];
if (\in_array($field, $booleans, true)) {
$values[] = 'true';
Expand Down Expand Up @@ -141,7 +142,7 @@ public static function allFields(): array
'withDevice' => [
'accept_language', 'ip_address', 'session_age', 'session_id', 'tracking_token', 'user_agent',
],
'withEmail' => ['address', 'domain'],
'withEmail' => ['address', 'domain', 'verification_time', 'was_verification_successful'],
'withEvent' => ['party', 'shop_id', 'time', 'transaction_id', 'type'],
'withOrder' => [
'affiliate_id', 'amount', 'currency', 'discount_code', 'has_gift_message', 'is_gift',
Expand Down Expand Up @@ -342,7 +343,11 @@ public static function invalidValues(): array
'token' => '4111111111111111',
]],
'withDevice' => ['device', ['ip_address' => 'not an IP', 'session_age' => -1.5, 'session_id' => '']],
'withEmail' => ['email', ['address' => 'not an email', 'domain' => 'not a domain']],
'withEmail' => ['email', [
'address' => 'not an email',
'domain' => 'not a domain',
'verification_time' => 'not a time',
]],
'withEvent' => ['event', ['party' => 'other', 'time' => 'not a time', 'type' => 'other']],
'withOrder' => ['order', ['amount' => -1.5, 'currency' => 'usd', 'referrer_uri' => 'not a URL']],
'withPayment' => ['payment', ['method' => 'other', 'processor' => 'other']],
Expand Down Expand Up @@ -481,7 +486,9 @@ public function testFullInsightsRequestUsingNamedArgs(string $class, string $ser
)
->withEmail(
address: '977577b140bfb7c516e4746204fbdb01',
domain: 'maxmind.com'
domain: 'maxmind.com',
verificationTime: '2026-10-01T14:30:00Z',
wasVerificationSuccessful: true
)
->withEvent(
party: 'customer',
Expand Down Expand Up @@ -1208,6 +1215,108 @@ public function testBadShippingPhoneWasVerificationSuccessful(): void
)->withShipping(['phone_was_verification_successful' => 'true']);
}

/**
* @dataProvider goodTimes
*/
public function testGoodEmailVerificationTimes(string $time): void
{
$result = $this->createMinFraudRequestWithFullResponse(
'insights',
0
)->withEmail(['verification_time' => $time]);

$this->assertSame(
$time,
$result->jsonSerialize()['content']['email']['verification_time'],
);
}

/**
* @dataProvider badTimes
*/
public function testBadEmailVerificationTime(string $time): void
{
$this->expectException(InvalidInputException::class);
$this->expectExceptionMessage('valid RFC 3339');

$this->createMinFraudRequestWithFullResponse(
'insights',
0
)->withEmail(['verification_time' => $time]);
}

public function testEmailWasVerificationSuccessfulFalse(): void
{
$result = $this->createMinFraudRequestWithFullResponse(
'insights',
0
)->withEmail(wasVerificationSuccessful: false);

$this->assertFalse(
$result->jsonSerialize()['content']['email']['was_verification_successful'],
);
}

public function testBadEmailWasVerificationSuccessful(): void
{
$this->expectException(InvalidInputException::class);
$this->expectExceptionMessage(
'Expected was_verification_successful to be in [boolean] but was string',
);

$this->createMinFraudRequestWithFullResponse(
'insights',
0
)->withEmail(['was_verification_successful' => 'true']);
}

public function testEmailWasVerificationSuccessfulFalseArray(): void
{
$result = $this->createMinFraudRequestWithFullResponse(
'insights',
0
)->withEmail(['was_verification_successful' => false]);

$this->assertFalse(
$result->jsonSerialize()['content']['email']['was_verification_successful'],
);
}

public function testEmailWasVerificationSuccessfulFalseWith(): void
{
$result = $this->createMinFraudRequestWithFullResponse(
'insights',
0
)->with(['email' => ['was_verification_successful' => false]]);

$this->assertFalse(
$result->jsonSerialize()['content']['email']['was_verification_successful'],
);
}

public function testEmailVerificationWithHashing(): void
{
$result = $this->createMinFraudRequestWithFullResponse(
'insights',
0,
['hashEmail' => true],
)->withEmail([
'address' => 'test@gmail.com',
'verification_time' => '2026-10-01T14:30:00Z',
'was_verification_successful' => false,
]);

$this->assertSame(
[
'address' => '1aedb8d9dc4751e229a335e371db8058',
'verification_time' => '2026-10-01T14:30:00Z',
'was_verification_successful' => false,
'domain' => 'gmail.com',
],
$result->jsonSerialize()['content']['email'],
);
}

/**
* @dataProvider badIins
*/
Expand Down
4 changes: 3 additions & 1 deletion tests/data/minfraud/full-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
},
"email": {
"address": "977577b140bfb7c516e4746204fbdb01",
"domain": "maxmind.com"
"domain": "maxmind.com",
"verification_time": "2026-10-01T14:30:00Z",
"was_verification_successful": true
},
"billing": {
"first_name": "First",
Expand Down
Loading