From df002ce524a34e0c1acbb32f8052def0c5bca678 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 9 Oct 2026 20:59:36 +0000 Subject: [PATCH] Add email verification inputs Add wasVerificationSuccessful and verificationTime to the Email request object. They send /email/was_verification_successful and /email/verification_time. These describe the most recent verification of the email address. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++ README.md | 2 + .../com/maxmind/minfraud/request/Email.java | 42 +++++++++++++++++++ .../maxmind/minfraud/request/EmailTest.java | 28 +++++++++++++ .../minfraud/request/RequestTestHelper.java | 4 ++ .../test-data/full-request-email-md5.json | 4 +- .../resources/test-data/full-request.json | 4 +- 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c340c2..12cb4991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ CHANGELOG `Billing.PhoneVerificationMethod` enum has the values `DELIVERED_CODE`, `NETWORK`, and `OTHER`. Do not set `phoneWasVerificationSuccessful` if no verification was attempted. +* Added `wasVerificationSuccessful` and `verificationTime` to the `Email` + request object. These describe the most recent verification of the email + address. Do not set `wasVerificationSuccessful` if no verification was + attempted. 4.4.0 (2026-07-21) ------------------ diff --git a/README.md b/README.md index e3d38b34..d2bee6b2 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,8 @@ Transaction request = new Transaction.Builder( new Email.Builder() .address("fraud@ster.com") .domain("ster.com") + .verificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) + .wasVerificationSuccessful(true) .build() ).event( new Event.Builder() diff --git a/src/main/java/com/maxmind/minfraud/request/Email.java b/src/main/java/com/maxmind/minfraud/request/Email.java index c6159f42..13c4831f 100644 --- a/src/main/java/com/maxmind/minfraud/request/Email.java +++ b/src/main/java/com/maxmind/minfraud/request/Email.java @@ -8,6 +8,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.Normalizer; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -21,6 +22,8 @@ public final class Email extends AbstractModel { private final String address; private final boolean hashAddress; private final String domain; + private final Boolean wasVerificationSuccessful; + private final ZonedDateTime verificationTime; private static final Map typoDomains; private static final Map typoTlds; private static final Map equivalentDomains; @@ -298,6 +301,8 @@ private Email(Email.Builder builder) { address = builder.address; hashAddress = builder.hashAddress; domain = builder.domain; + wasVerificationSuccessful = builder.wasVerificationSuccessful; + verificationTime = builder.verificationTime; } /** @@ -308,6 +313,8 @@ public static final class Builder { private String address; private boolean hashAddress; private String domain; + private Boolean wasVerificationSuccessful; + private ZonedDateTime verificationTime; /** * The constructor for the builder. @@ -383,6 +390,25 @@ public Email.Builder domain(String domain) { return this; } + /** + * @param wasSuccessful Whether the most recent verification of the email address + * succeeded. Do not set this if no verification was attempted. + * @return The builder object. + */ + public Email.Builder wasVerificationSuccessful(Boolean wasSuccessful) { + wasVerificationSuccessful = wasSuccessful; + return this; + } + + /** + * @param time The date and time of the most recent verification of the email address. + * @return The builder object. + */ + public Email.Builder verificationTime(ZonedDateTime time) { + verificationTime = time; + return this; + } + /** * @return An instance of {@code Email} created from the fields set on this builder. */ @@ -571,4 +597,20 @@ private static boolean isValidDomainLabel(String label) { public String domain() { return domain; } + + /** + * @return Whether the most recent verification of the email address succeeded. + */ + @JsonProperty("was_verification_successful") + public Boolean wasVerificationSuccessful() { + return wasVerificationSuccessful; + } + + /** + * @return The date and time of the most recent verification of the email address. + */ + @JsonProperty("verification_time") + public ZonedDateTime verificationTime() { + return verificationTime; + } } diff --git a/src/test/java/com/maxmind/minfraud/request/EmailTest.java b/src/test/java/com/maxmind/minfraud/request/EmailTest.java index 820ae1b0..8d1ab101 100644 --- a/src/test/java/com/maxmind/minfraud/request/EmailTest.java +++ b/src/test/java/com/maxmind/minfraud/request/EmailTest.java @@ -1,6 +1,7 @@ package com.maxmind.minfraud.request; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -10,14 +11,41 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.skyscreamer.jsonassert.JSONAssert; public class EmailTest { + @Test + public void testWasVerificationSuccessful() { + var email = new Builder().wasVerificationSuccessful(false).build(); + assertFalse(email.wasVerificationSuccessful()); + } + + @Test + public void testVerificationTime() { + var time = ZonedDateTime.now(); + var email = new Builder().verificationTime(time).build(); + assertEquals(time, email.verificationTime()); + } + + @Test + public void testVerificationSerialization() throws Exception { + var email = new Builder() + .wasVerificationSuccessful(false) + .verificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) + .build(); + + var expectedJSON = "{was_verification_successful:false," + + "verification_time:'2026-10-01T14:30:00Z'}"; + JSONAssert.assertEquals(expectedJSON, email.toJson(), true); + } + @Test public void testAddress() { var email = new Builder().address("test@test.org").build(); diff --git a/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java b/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java index 38574fb7..2afdeeec 100644 --- a/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java +++ b/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java @@ -34,6 +34,8 @@ public static Transaction fullTransaction() throws Exception { return makeTransaction(new Email.Builder() .address("test@maxmind.com") .domain("maxmind.com") + .wasVerificationSuccessful(true) + .verificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) .build()); } @@ -42,6 +44,8 @@ public static Transaction fullTransactionEmailMd5() throws Exception { .address("test@maxmind.com") .hashAddress() .domain("maxmind.com") + .wasVerificationSuccessful(true) + .verificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) .build()); } diff --git a/src/test/resources/test-data/full-request-email-md5.json b/src/test/resources/test-data/full-request-email-md5.json index b13cf6db..d66c1bd3 100644 --- a/src/test/resources/test-data/full-request-email-md5.json +++ b/src/test/resources/test-data/full-request-email-md5.json @@ -12,7 +12,9 @@ }, "email": { "address": "977577b140bfb7c516e4746204fbdb01", - "domain": "maxmind.com" + "domain": "maxmind.com", + "was_verification_successful": true, + "verification_time": "2026-10-01T14:30:00Z" }, "billing": { "first_name": "First", diff --git a/src/test/resources/test-data/full-request.json b/src/test/resources/test-data/full-request.json index b46549c9..fdcd48a7 100644 --- a/src/test/resources/test-data/full-request.json +++ b/src/test/resources/test-data/full-request.json @@ -12,7 +12,9 @@ }, "email": { "address": "test@maxmind.com", - "domain": "maxmind.com" + "domain": "maxmind.com", + "was_verification_successful": true, + "verification_time": "2026-10-01T14:30:00Z" }, "billing": { "first_name": "First",