From fc048701bd42ea576fe1ab5c888facc81b4ee20d Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Mon, 12 Oct 2026 15:01:06 +0000 Subject: [PATCH 1/2] Use Locale.ROOT for case conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enum toString methods and email address cleaning called toLowerCase with the default locale. In locales such as tr-TR and az-AZ, "I" becomes a dotless "ı". Enum values then serialized as, for example, "account_creatıon", and the web service rejected them. Email hashing also produced a different MD5 for addresses that contain "I". Use Locale.ROOT for these conversions. Add a test that runs them under tr-TR. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 +++ CLAUDE.md | 4 +- .../com/maxmind/minfraud/request/Email.java | 3 +- .../com/maxmind/minfraud/request/Event.java | 5 +- .../com/maxmind/minfraud/request/Payment.java | 5 +- .../maxmind/minfraud/request/Shipping.java | 3 +- .../minfraud/request/TransactionReport.java | 3 +- .../minfraud/response/EmailDomain.java | 3 +- .../minfraud/response/EmailDomainVisit.java | 3 +- .../java/com/maxmind/minfraud/LocaleTest.java | 63 +++++++++++++++++++ 10 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 src/test/java/com/maxmind/minfraud/LocaleTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b65991ea..ceb17650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +4.5.0 +------------------ + +* Fixed enum serialization and email address hashing in locales that + change case conversion, such as Turkish (`tr`) and Azerbaijani (`az`). + In those locales, some enum values serialized with a dotless `ı`, for + example `account_creatıon`, and the web service rejected them. The library + now uses `Locale.ROOT` for these case conversions. + 4.4.0 (2026-07-21) ------------------ diff --git a/CLAUDE.md b/CLAUDE.md index 043a2893..ea5612dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,14 +170,14 @@ public enum Status { @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } ``` **How it works:** - Enum constants use `UPPER_SNAKE_CASE` (e.g., `DNS_ERROR`, `ISP_EMAIL`) -- Override `toString()` to return `name().toLowerCase()` for JSON serialization +- Override `toString()` to return `name().toLowerCase(Locale.ROOT)` for JSON serialization - The `Mapper` class configures Jackson with: - `READ_ENUMS_USING_TO_STRING` - Deserializes using `toString()` - `READ_UNKNOWN_ENUM_VALUES_AS_NULL` - Unknown values become `null` instead of throwing exceptions diff --git a/src/main/java/com/maxmind/minfraud/request/Email.java b/src/main/java/com/maxmind/minfraud/request/Email.java index c6159f42..5840821a 100644 --- a/src/main/java/com/maxmind/minfraud/request/Email.java +++ b/src/main/java/com/maxmind/minfraud/request/Email.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -416,7 +417,7 @@ public String address() { } private String cleanAddress(String address) { - address = address.trim().toLowerCase(); + address = address.trim().toLowerCase(Locale.ROOT); int domainIndex = address.lastIndexOf('@'); if (domainIndex == -1 || domainIndex + 1 == address.length()) { diff --git a/src/main/java/com/maxmind/minfraud/request/Event.java b/src/main/java/com/maxmind/minfraud/request/Event.java index 1ece32fd..c838b239 100644 --- a/src/main/java/com/maxmind/minfraud/request/Event.java +++ b/src/main/java/com/maxmind/minfraud/request/Event.java @@ -6,6 +6,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; +import java.util.Locale; /** * This class contains general information related to the event being scored. @@ -207,7 +208,7 @@ public enum Type { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } @@ -228,7 +229,7 @@ public enum Party { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } } \ No newline at end of file diff --git a/src/main/java/com/maxmind/minfraud/request/Payment.java b/src/main/java/com/maxmind/minfraud/request/Payment.java index dcbd8dda..54631913 100644 --- a/src/main/java/com/maxmind/minfraud/request/Payment.java +++ b/src/main/java/com/maxmind/minfraud/request/Payment.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.minfraud.AbstractModel; +import java.util.Locale; /** * The payment information for the transaction. @@ -283,7 +284,7 @@ public enum Processor { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } @@ -336,7 +337,7 @@ public enum Method { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } } diff --git a/src/main/java/com/maxmind/minfraud/request/Shipping.java b/src/main/java/com/maxmind/minfraud/request/Shipping.java index 43ab51ad..e979f177 100644 --- a/src/main/java/com/maxmind/minfraud/request/Shipping.java +++ b/src/main/java/com/maxmind/minfraud/request/Shipping.java @@ -1,6 +1,7 @@ package com.maxmind.minfraud.request; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Locale; /** * The shipping information for the transaction. @@ -71,7 +72,7 @@ public enum DeliverySpeed { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } } diff --git a/src/main/java/com/maxmind/minfraud/request/TransactionReport.java b/src/main/java/com/maxmind/minfraud/request/TransactionReport.java index 83c66f7f..e18f5f58 100644 --- a/src/main/java/com/maxmind/minfraud/request/TransactionReport.java +++ b/src/main/java/com/maxmind/minfraud/request/TransactionReport.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.minfraud.AbstractModel; import java.net.InetAddress; +import java.util.Locale; import java.util.UUID; /** @@ -231,7 +232,7 @@ public enum Tag { * @return a string representation of the object. */ public String toString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } diff --git a/src/main/java/com/maxmind/minfraud/response/EmailDomain.java b/src/main/java/com/maxmind/minfraud/response/EmailDomain.java index 267c7661..efb869d0 100644 --- a/src/main/java/com/maxmind/minfraud/response/EmailDomain.java +++ b/src/main/java/com/maxmind/minfraud/response/EmailDomain.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.minfraud.JsonSerializable; import java.time.LocalDate; +import java.util.Locale; /** * This class contains minFraud response data related to the email domain. @@ -63,7 +64,7 @@ public enum Classification { */ @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/src/main/java/com/maxmind/minfraud/response/EmailDomainVisit.java b/src/main/java/com/maxmind/minfraud/response/EmailDomainVisit.java index c3e1428b..3f3b7175 100644 --- a/src/main/java/com/maxmind/minfraud/response/EmailDomainVisit.java +++ b/src/main/java/com/maxmind/minfraud/response/EmailDomainVisit.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.minfraud.JsonSerializable; import java.time.LocalDate; +import java.util.Locale; /** * This class contains information about an automated visit to the email domain. @@ -63,7 +64,7 @@ public enum Status { */ @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/src/test/java/com/maxmind/minfraud/LocaleTest.java b/src/test/java/com/maxmind/minfraud/LocaleTest.java new file mode 100644 index 00000000..3543d263 --- /dev/null +++ b/src/test/java/com/maxmind/minfraud/LocaleTest.java @@ -0,0 +1,63 @@ +package com.maxmind.minfraud; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.maxmind.minfraud.request.Email; +import com.maxmind.minfraud.request.Event; +import com.maxmind.minfraud.request.Payment; +import com.maxmind.minfraud.request.Shipping; +import com.maxmind.minfraud.request.TransactionReport; +import com.maxmind.minfraud.response.EmailDomain; +import com.maxmind.minfraud.response.EmailDomainVisit; +import java.util.List; +import java.util.Locale; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Checks that the default locale does not change serialized values. In the tr-TR locale, + * {@code "I".toLowerCase()} is a dotless "ı". + */ +public class LocaleTest { + private final Locale originalLocale = Locale.getDefault(); + + @BeforeEach + public void setTurkishLocale() { + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + } + + @AfterEach + public void restoreLocale() { + Locale.setDefault(originalLocale); + } + + @Test + public void testEnumToString() { + var values = List.[]>of( + Shipping.DeliverySpeed.values(), + Payment.Processor.values(), + Payment.Method.values(), + TransactionReport.Tag.values(), + Event.Type.values(), + Event.Party.values(), + EmailDomain.Classification.values(), + EmailDomainVisit.Status.values() + ); + for (var enumValues : values) { + for (var value : enumValues) { + assertEquals( + value.name().toLowerCase(Locale.ROOT), + value.toString(), + value.getClass().getName() + "." + value.name() + ); + } + } + } + + @Test + public void testEmailAddressLowercasing() { + var email = new Email.Builder().address("INFO@TEST.org").hashAddress().build(); + assertEquals("fad0cdbc3804b804116ea8972221d2a1", email.address()); + } +} From cd97535f1abe5302f888a250c6b3be75f2c19771 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 9 Oct 2026 20:44:16 +0000 Subject: [PATCH 2/2] Add phone verification inputs Add phoneVerificationMethod, phoneWasVerificationSuccessful, and phoneVerificationTime to the request. Both /billing and /shipping accept them. They describe the most recent verification of the phone number for the address. The inputs live in AbstractLocation, which Billing and Shipping share. The PhoneVerificationMethod enum serializes with Locale.ROOT so that locales such as tr-TR do not change the value. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++ README.md | 6 ++ .../minfraud/request/AbstractLocation.java | 94 +++++++++++++++++++ .../java/com/maxmind/minfraud/LocaleTest.java | 2 + .../request/AbstractLocationTest.java | 58 ++++++++++++ .../minfraud/request/RequestTestHelper.java | 6 ++ .../test-data/full-request-email-md5.json | 8 +- .../resources/test-data/full-request.json | 8 +- 8 files changed, 186 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb17650..0aedea9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ CHANGELOG 4.5.0 ------------------ +* Added `phoneVerificationMethod`, `phoneWasVerificationSuccessful`, and + `phoneVerificationTime` to the `Billing` and `Shipping` request objects. + These describe the most recent verification of the phone number for the + address. The `PhoneVerificationMethod` enum has the values + `DELIVERED_CODE`, `NETWORK`, and `OTHER`. Do not set + `phoneWasVerificationSuccessful` if no verification was attempted. * Fixed enum serialization and email address hashing in locales that change case conversion, such as Turkish (`tr`) and Azerbaijani (`az`). In those locales, some enum values serialized with a dotless `ı`, for diff --git a/README.md b/README.md index 9cbbd065..caffc206 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() @@ -254,6 +257,9 @@ Transaction request = new Transaction.Builder( .postal("55455") .phoneNumber("313-545-3113") .phoneCountryCode("1") + .phoneVerificationMethod(Shipping.PhoneVerificationMethod.NETWORK) + .phoneVerificationTime(ZonedDateTime.parse("2026-10-02T09:15:00Z")) + .phoneWasVerificationSuccessful(false) .deliverySpeed(Shipping.DeliverySpeed.EXPEDITED) .address("32 Washington Ave.") .address2("18") diff --git a/src/main/java/com/maxmind/minfraud/request/AbstractLocation.java b/src/main/java/com/maxmind/minfraud/request/AbstractLocation.java index 904676e0..6c0eae95 100644 --- a/src/main/java/com/maxmind/minfraud/request/AbstractLocation.java +++ b/src/main/java/com/maxmind/minfraud/request/AbstractLocation.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.minfraud.AbstractModel; +import java.time.ZonedDateTime; +import java.util.Locale; import java.util.regex.Pattern; /** @@ -20,6 +22,9 @@ public abstract sealed class AbstractLocation extends AbstractModel private final String postal; private final String phoneNumber; private final String phoneCountryCode; + private final PhoneVerificationMethod phoneVerificationMethod; + private final Boolean phoneWasVerificationSuccessful; + private final ZonedDateTime phoneVerificationTime; protected AbstractLocation(AbstractLocation.Builder builder) { firstName = builder.firstName; @@ -33,6 +38,9 @@ protected AbstractLocation(AbstractLocation.Builder builder) { postal = builder.postal; phoneNumber = builder.phoneNumber; phoneCountryCode = builder.phoneCountryCode; + phoneVerificationMethod = builder.phoneVerificationMethod; + phoneWasVerificationSuccessful = builder.phoneWasVerificationSuccessful; + phoneVerificationTime = builder.phoneVerificationTime; } /** @@ -56,6 +64,9 @@ abstract static class Builder> { String postal; String phoneNumber; String phoneCountryCode; + PhoneVerificationMethod phoneVerificationMethod; + Boolean phoneWasVerificationSuccessful; + ZonedDateTime phoneVerificationTime; /** * @param name The first name associated with the address @@ -162,6 +173,37 @@ public final T phoneNumber(String number) { return (T) this; } + /** + * @param method The most recent method used to verify the phone number associated with + * the address + * @return The builder object. + */ + public final T phoneVerificationMethod(PhoneVerificationMethod method) { + phoneVerificationMethod = method; + return (T) this; + } + + /** + * @param wasSuccessful Whether the most recent verification of the phone number + * associated with the address succeeded. Do not set this if no + * verification was attempted. + * @return The builder object. + */ + public final T phoneWasVerificationSuccessful(Boolean wasSuccessful) { + phoneWasVerificationSuccessful = wasSuccessful; + return (T) this; + } + + /** + * @param time The date and time of the most recent verification of the phone number + * associated with the address + * @return The builder object. + */ + public final T phoneVerificationTime(ZonedDateTime time) { + phoneVerificationTime = time; + return (T) this; + } + abstract AbstractLocation build(); } @@ -257,4 +299,56 @@ public final String phoneNumber() { public final String phoneCountryCode() { return phoneCountryCode; } + + /** + * @return The most recent method used to verify the phone number associated with the address + */ + @JsonProperty("phone_verification_method") + public final PhoneVerificationMethod phoneVerificationMethod() { + return phoneVerificationMethod; + } + + /** + * @return Whether the most recent verification of the phone number associated with the + * address succeeded + */ + @JsonProperty("phone_was_verification_successful") + public final Boolean phoneWasVerificationSuccessful() { + return phoneWasVerificationSuccessful; + } + + /** + * @return The date and time of the most recent verification of the phone number associated + * with the address + */ + @JsonProperty("phone_verification_time") + public final 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(Locale.ROOT); + } + } } diff --git a/src/test/java/com/maxmind/minfraud/LocaleTest.java b/src/test/java/com/maxmind/minfraud/LocaleTest.java index 3543d263..eba90557 100644 --- a/src/test/java/com/maxmind/minfraud/LocaleTest.java +++ b/src/test/java/com/maxmind/minfraud/LocaleTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.maxmind.minfraud.request.AbstractLocation; import com.maxmind.minfraud.request.Email; import com.maxmind.minfraud.request.Event; import com.maxmind.minfraud.request.Payment; @@ -35,6 +36,7 @@ public void restoreLocale() { @Test public void testEnumToString() { var values = List.[]>of( + AbstractLocation.PhoneVerificationMethod.values(), Shipping.DeliverySpeed.values(), Payment.Processor.values(), Payment.Method.values(), diff --git a/src/test/java/com/maxmind/minfraud/request/AbstractLocationTest.java b/src/test/java/com/maxmind/minfraud/request/AbstractLocationTest.java index b8f71ff4..08db206c 100644 --- a/src/test/java/com/maxmind/minfraud/request/AbstractLocationTest.java +++ b/src/test/java/com/maxmind/minfraud/request/AbstractLocationTest.java @@ -1,10 +1,14 @@ 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.assertThrows; import com.maxmind.minfraud.request.AbstractLocation.Builder; +import com.maxmind.minfraud.request.AbstractLocation.PhoneVerificationMethod; +import java.time.ZonedDateTime; import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; public abstract class AbstractLocationTest { abstract Builder builder(); @@ -100,4 +104,58 @@ public void testPhoneCountryCode() { AbstractLocation loc = this.builder().phoneCountryCode("1").build(); assertEquals("1", loc.phoneCountryCode()); } + + @Test + public void testPhoneVerificationMethod() { + AbstractLocation 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() { + AbstractLocation loc = this.builder().phoneWasVerificationSuccessful(false).build(); + assertFalse(loc.phoneWasVerificationSuccessful()); + } + + @Test + public void testPhoneVerificationTime() { + var time = ZonedDateTime.now(); + AbstractLocation loc = this.builder().phoneVerificationTime(time).build(); + assertEquals(time, loc.phoneVerificationTime()); + } + + @Test + public void testPhoneVerificationSerialization() throws Exception { + AbstractLocation 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); + } + + @Test + public void testPhoneVerificationOmittedWhenUnset() throws Exception { + AbstractLocation loc = this.builder().build(); + JSONAssert.assertEquals("{}", 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..1c20de68 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() @@ -100,6 +103,9 @@ private static Transaction makeTransaction(Email e) throws Exception { .postal("73003") .phoneNumber("123-456-0000") .phoneCountryCode("1") + .phoneVerificationMethod(Shipping.PhoneVerificationMethod.NETWORK) + .phoneWasVerificationSuccessful(false) + .phoneVerificationTime(ZonedDateTime.parse("2026-10-02T09:15:00Z")) .deliverySpeed(Shipping.DeliverySpeed.SAME_DAY) .build() ).payment( 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..361ad816 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", @@ -39,6 +42,9 @@ "postal": "73003", "phone_number": "123-456-0000", "phone_country_code": "1", + "phone_verification_method": "network", + "phone_was_verification_successful": false, + "phone_verification_time": "2026-10-02T09:15:00Z", "delivery_speed": "same_day" }, "payment": { diff --git a/src/test/resources/test-data/full-request.json b/src/test/resources/test-data/full-request.json index 09658526..17fb0b9e 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", @@ -39,6 +42,9 @@ "postal": "73003", "phone_number": "123-456-0000", "phone_country_code": "1", + "phone_verification_method": "network", + "phone_was_verification_successful": false, + "phone_verification_time": "2026-10-02T09:15:00Z", "delivery_speed": "same_day" }, "payment": {