diff --git a/CHANGELOG.md b/CHANGELOG.md index b65991ea..c1c340c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +4.5.0 +------------------ + +* Added `phoneVerificationMethod`, `phoneWasVerificationSuccessful`, and + `phoneVerificationTime` to the `Billing` request object. These describe + the most recent verification of the billing phone number. The + `Billing.PhoneVerificationMethod` enum has the values `DELIVERED_CODE`, + `NETWORK`, and `OTHER`. Do not set `phoneWasVerificationSuccessful` if no + verification was attempted. + 4.4.0 (2026-07-21) ------------------ diff --git a/README.md b/README.md index 9cbbd065..e3d38b34 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,9 @@ Transaction request = new Transaction.Builder( .lastName("Smith") .phoneCountryCode("1") .phoneNumber("321-321-3211") + .phoneVerificationMethod(Billing.PhoneVerificationMethod.DELIVERED_CODE) + .phoneVerificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) + .phoneWasVerificationSuccessful(true) .postal("06510") .region("CT") .build() diff --git a/src/main/java/com/maxmind/minfraud/request/Billing.java b/src/main/java/com/maxmind/minfraud/request/Billing.java index e0f36838..0dbe3f59 100644 --- a/src/main/java/com/maxmind/minfraud/request/Billing.java +++ b/src/main/java/com/maxmind/minfraud/request/Billing.java @@ -1,11 +1,21 @@ package com.maxmind.minfraud.request; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.ZonedDateTime; + /** * The billing information for the transaction. */ public final class Billing extends AbstractLocation { + private final PhoneVerificationMethod phoneVerificationMethod; + private final Boolean phoneWasVerificationSuccessful; + private final ZonedDateTime phoneVerificationTime; + private Billing(Billing.Builder builder) { super(builder); + phoneVerificationMethod = builder.phoneVerificationMethod; + phoneWasVerificationSuccessful = builder.phoneWasVerificationSuccessful; + phoneVerificationTime = builder.phoneVerificationTime; } /** @@ -13,6 +23,39 @@ private Billing(Billing.Builder builder) { * methods. */ public static final class Builder extends AbstractLocation.Builder { + PhoneVerificationMethod phoneVerificationMethod; + Boolean phoneWasVerificationSuccessful; + ZonedDateTime phoneVerificationTime; + + /** + * @param method The most recent method used to verify the billing phone number. + * @return The builder object. + */ + public Billing.Builder phoneVerificationMethod(PhoneVerificationMethod method) { + phoneVerificationMethod = method; + return this; + } + + /** + * @param wasSuccessful Whether the most recent verification of the billing phone number + * succeeded. Do not set this if no verification was attempted. + * @return The builder object. + */ + public Billing.Builder phoneWasVerificationSuccessful(Boolean wasSuccessful) { + phoneWasVerificationSuccessful = wasSuccessful; + return this; + } + + /** + * @param time The date and time of the most recent verification of the billing phone + * number. + * @return The builder object. + */ + public Billing.Builder phoneVerificationTime(ZonedDateTime time) { + phoneVerificationTime = time; + return this; + } + /** * @return An instance of {@code Billing} created from the fields set on this builder. */ @@ -21,4 +64,54 @@ public Billing build() { return new Billing(this); } } -} \ No newline at end of file + + /** + * @return The most recent method used to verify the billing phone number. + */ + @JsonProperty("phone_verification_method") + public PhoneVerificationMethod phoneVerificationMethod() { + return phoneVerificationMethod; + } + + /** + * @return Whether the most recent verification of the billing phone number succeeded. + */ + @JsonProperty("phone_was_verification_successful") + public Boolean phoneWasVerificationSuccessful() { + return phoneWasVerificationSuccessful; + } + + /** + * @return The date and time of the most recent verification of the billing phone number. + */ + @JsonProperty("phone_verification_time") + public ZonedDateTime phoneVerificationTime() { + return phoneVerificationTime; + } + + /** + * Enumerated phone verification methods. + */ + public enum PhoneVerificationMethod { + /** + * A code delivered to the phone, such as by SMS, voice call, or messaging app + */ + DELIVERED_CODE, + /** + * Verification through the mobile network operator, such as silent network + * authentication + */ + NETWORK, + /** + * Another verification method + */ + OTHER; + + /** + * @return a string representation of the object. + */ + public String toString() { + return this.name().toLowerCase(); + } + } +} diff --git a/src/test/java/com/maxmind/minfraud/request/BillingTest.java b/src/test/java/com/maxmind/minfraud/request/BillingTest.java index 0a2759a3..5cbe2fe9 100644 --- a/src/test/java/com/maxmind/minfraud/request/BillingTest.java +++ b/src/test/java/com/maxmind/minfraud/request/BillingTest.java @@ -1,11 +1,66 @@ package com.maxmind.minfraud.request; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + import com.maxmind.minfraud.request.Billing.Builder; +import com.maxmind.minfraud.request.Billing.PhoneVerificationMethod; +import java.time.ZonedDateTime; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; public class BillingTest extends AbstractLocationTest { Builder builder() { return new Builder(); } -} \ No newline at end of file + + @Test + public void testPhoneVerificationMethod() { + var loc = this.builder() + .phoneVerificationMethod(PhoneVerificationMethod.DELIVERED_CODE) + .build(); + assertEquals(PhoneVerificationMethod.DELIVERED_CODE, loc.phoneVerificationMethod()); + + loc = this.builder().phoneVerificationMethod(PhoneVerificationMethod.NETWORK).build(); + assertEquals(PhoneVerificationMethod.NETWORK, loc.phoneVerificationMethod()); + + loc = this.builder().phoneVerificationMethod(PhoneVerificationMethod.OTHER).build(); + assertEquals(PhoneVerificationMethod.OTHER, loc.phoneVerificationMethod()); + } + + @Test + public void testPhoneVerificationMethodToString() { + assertEquals("delivered_code", PhoneVerificationMethod.DELIVERED_CODE.toString()); + assertEquals("network", PhoneVerificationMethod.NETWORK.toString()); + assertEquals("other", PhoneVerificationMethod.OTHER.toString()); + } + + @Test + public void testPhoneWasVerificationSuccessful() { + var loc = this.builder().phoneWasVerificationSuccessful(false).build(); + assertFalse(loc.phoneWasVerificationSuccessful()); + } + + @Test + public void testPhoneVerificationTime() { + var time = ZonedDateTime.now(); + var loc = this.builder().phoneVerificationTime(time).build(); + assertEquals(time, loc.phoneVerificationTime()); + } + + @Test + public void testPhoneVerificationSerialization() throws Exception { + var loc = this.builder() + .phoneVerificationMethod(PhoneVerificationMethod.NETWORK) + .phoneWasVerificationSuccessful(false) + .phoneVerificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) + .build(); + + var expectedJSON = "{phone_verification_method:'network'," + + "phone_was_verification_successful:false," + + "phone_verification_time:'2026-10-01T14:30:00Z'}"; + JSONAssert.assertEquals(expectedJSON, loc.toJson(), true); + } +} diff --git a/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java b/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java index 606ca5bd..38574fb7 100644 --- a/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java +++ b/src/test/java/com/maxmind/minfraud/request/RequestTestHelper.java @@ -86,6 +86,9 @@ private static Transaction makeTransaction(Email e) throws Exception { .postal("06510") .phoneNumber("123-456-7890") .phoneCountryCode("1") + .phoneVerificationMethod(Billing.PhoneVerificationMethod.DELIVERED_CODE) + .phoneWasVerificationSuccessful(true) + .phoneVerificationTime(ZonedDateTime.parse("2026-10-01T14:30:00Z")) .build() ).shipping( new Shipping.Builder() 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 97150805..b13cf6db 100644 --- a/src/test/resources/test-data/full-request-email-md5.json +++ b/src/test/resources/test-data/full-request-email-md5.json @@ -25,7 +25,10 @@ "country": "US", "postal": "06510", "phone_number": "123-456-7890", - "phone_country_code": "1" + "phone_country_code": "1", + "phone_verification_method": "delivered_code", + "phone_was_verification_successful": true, + "phone_verification_time": "2026-10-01T14:30:00Z" }, "shipping": { "first_name": "ShipFirst", diff --git a/src/test/resources/test-data/full-request.json b/src/test/resources/test-data/full-request.json index 09658526..b46549c9 100644 --- a/src/test/resources/test-data/full-request.json +++ b/src/test/resources/test-data/full-request.json @@ -25,7 +25,10 @@ "country": "US", "postal": "06510", "phone_number": "123-456-7890", - "phone_country_code": "1" + "phone_country_code": "1", + "phone_verification_method": "delivered_code", + "phone_was_verification_successful": true, + "phone_verification_time": "2026-10-01T14:30:00Z" }, "shipping": { "first_name": "ShipFirst",