From babd62006dc25468e496f0ca6af65ca9f13376fd Mon Sep 17 00:00:00 2001 From: Michael Broshi Date: Thu, 13 Nov 2025 14:26:05 -0500 Subject: [PATCH 1/2] Check if a datetime is in millis --- .../STJUnixDateTimeConverter.cs | 14 +- .../JsonConverters/UnixDateTimeConverter.cs | 14 +- .../Entities/Terminal/Readers/ReaderTest.cs | 140 +++++++++++++++++- 3 files changed, 165 insertions(+), 3 deletions(-) diff --git a/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs b/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs index 8fbf247152..9f6fdcf577 100644 --- a/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs +++ b/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs @@ -45,7 +45,19 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso if (seconds >= 0) { - return DateTimeUtils.UnixEpoch.AddSeconds(seconds); + // As of the 2025-10-29.clover release, there is a bug in the Terminal API + // where the last_seen_at field is returned as milliseconds rather than seconds. + // Since we don't know when/how a fix will be put in to change that behavior, + // we make a heuristic guess to determine if a timestamp is in milliseconds or seconds. + // This check will work until the year ~5138. + if (seconds > 5000000000L) + { + return DateTimeUtils.UnixEpoch.AddMilliseconds(seconds); + } + else + { + return DateTimeUtils.UnixEpoch.AddSeconds(seconds); + } } else { diff --git a/src/Stripe.net/Infrastructure/JsonConverters/UnixDateTimeConverter.cs b/src/Stripe.net/Infrastructure/JsonConverters/UnixDateTimeConverter.cs index 869c954e31..ac6526abf9 100644 --- a/src/Stripe.net/Infrastructure/JsonConverters/UnixDateTimeConverter.cs +++ b/src/Stripe.net/Infrastructure/JsonConverters/UnixDateTimeConverter.cs @@ -82,7 +82,19 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist if (seconds >= 0) { - return DateTimeUtils.UnixEpoch.AddSeconds(seconds); + // As of the 2025-10-29.clover release, there is a bug in the Terminal API + // where the last_seen_at field is returned as milliseconds rather than seconds. + // Since we don't know when/how a fix will be put in to change that behavior, + // we make a heuristic guess to determine if a timestamp is in milliseconds or seconds. + // This check will work until the year ~5138. + if (seconds > 5000000000L) + { + return DateTimeUtils.UnixEpoch.AddMilliseconds(seconds); + } + else + { + return DateTimeUtils.UnixEpoch.AddSeconds(seconds); + } } else { diff --git a/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs b/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs index 2e15d93f0b..bd30485321 100644 --- a/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs +++ b/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs @@ -1,13 +1,17 @@ namespace StripeTests.Terminal { + using System; using Newtonsoft.Json; using Stripe.Terminal; using Xunit; +#if NET6_0_OR_GREATER + using System.Text.Json; +#endif public class ReaderTest : BaseStripeTest { public ReaderTest(StripeMockFixture stripeMockFixture) - : base(stripeMockFixture) +: base(stripeMockFixture) { } @@ -21,5 +25,139 @@ public void Deserialize() Assert.NotNull(reader.Id); Assert.Equal("terminal.reader", reader.Object); } + + [Fact] + public void DeserializeWithLastSeenAtInMilliseconds() + { + // This test verifies that timestamps in milliseconds are handled correctly + // Addresses GitHub issue #3253 where last_seen_at comes as milliseconds instead of seconds + + // Unix timestamp in milliseconds for November 13, 2023 18:26:40 UTC + // 1699900000000 milliseconds = 1699900000 seconds + long timestampMillis = 1699900000000L; + + string json = $@"{{ + ""id"": ""tmr_test123"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader"", + ""last_seen_at"": {timestampMillis}, + ""livemode"": false, + ""location"": ""tml_test456"", + ""metadata"": {{}}, + ""serial_number"": ""123456789"", + ""status"": ""online"" +}}"; + + // This should now work correctly with the updated converter + var reader = JsonConvert.DeserializeObject(json); + + Assert.NotNull(reader); + Assert.NotNull(reader.LastSeenAt); + + // Verify the timestamp was parsed correctly + // Expected: November 13, 2023 18:26:40 UTC (timestamp 1699900000) + var expectedDate = new DateTime(2023, 11, 13, 18, 26, 40, DateTimeKind.Utc); + Assert.Equal(expectedDate, reader.LastSeenAt.Value); + } + + [Fact] + public void DeserializeWithLastSeenAtInSeconds() + { + // This test verifies that regular timestamps in seconds still work correctly + + // Unix timestamp in seconds for November 13, 2023 18:26:40 UTC + long timestampSeconds = 1699900000L; + + string json = $@"{{ + ""id"": ""tmr_test456"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader Seconds"", + ""last_seen_at"": {timestampSeconds}, + ""livemode"": false, + ""location"": ""tml_test789"", + ""metadata"": {{}}, + ""serial_number"": ""987654321"", + ""status"": ""online"" +}}"; + + var reader = JsonConvert.DeserializeObject(json); + + Assert.NotNull(reader); + Assert.NotNull(reader.LastSeenAt); + + // Verify the timestamp was parsed correctly + // Expected: November 13, 2023 18:26:40 UTC (timestamp 1699900000) + var expectedDate = new DateTime(2023, 11, 13, 18, 26, 40, DateTimeKind.Utc); + Assert.Equal(expectedDate, reader.LastSeenAt.Value); + } + +#if NET6_0_OR_GREATER + [Fact] + public void DeserializeWithLastSeenAtInMilliseconds_SystemTextJson() + { +// This test verifies that timestamps in milliseconds are handled correctly in System.Text.Json +// Addresses GitHub issue #3253 where last_seen_at comes as milliseconds instead of seconds + +// Unix timestamp in milliseconds for November 13, 2023 18:26:40 UTC +// 1699900000000 milliseconds = 1699900000 seconds +long timestampMillis = 1699900000000L; +string json = $@"{{ + ""id"": ""tmr_test123"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader STJ"", + ""last_seen_at"": {timestampMillis}, + ""livemode"": false, + ""location"": ""tml_test456"", + ""metadata"": {{}}, + ""serial_number"": ""123456789"", + ""status"": ""online"" +}}"; + + // This should now work correctly with the updated STJ converter + var reader = System.Text.Json.JsonSerializer.Deserialize(json); + + Assert.NotNull(reader); + Assert.NotNull(reader.LastSeenAt); + + // Verify the timestamp was parsed correctly + // Expected: November 13, 2023 18:26:40 UTC (timestamp 1699900000) + var expectedDate = new DateTime(2023, 11, 13, 18, 26, 40, DateTimeKind.Utc); + Assert.Equal(expectedDate, reader.LastSeenAt.Value); + } + + [Fact] + public void DeserializeWithLastSeenAtInSeconds_SystemTextJson() + { +// This test verifies that regular timestamps in seconds still work correctly in System.Text.Json + +// Unix timestamp in seconds for November 13, 2023 18:26:40 UTC +long timestampSeconds = 1699900000L; +string json = $@"{{ + ""id"": ""tmr_test456"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader Seconds STJ"", + ""last_seen_at"": {timestampSeconds}, + ""livemode"": false, + ""location"": ""tml_test789"", + ""metadata"": {{}}, + ""serial_number"": ""987654321"", + ""status"": ""online"" +}}"; + + var reader = System.Text.Json.JsonSerializer.Deserialize(json); + + Assert.NotNull(reader); + Assert.NotNull(reader.LastSeenAt); + + // Verify the timestamp was parsed correctly + // Expected: November 13, 2023 18:26:40 UTC (timestamp 1699900000) + var expectedDate = new DateTime(2023, 11, 13, 18, 26, 40, DateTimeKind.Utc); + Assert.Equal(expectedDate, reader.LastSeenAt.Value); + } +#endif } } From 32aa9394ab119091eb0dcca2fdd32e28e543735b Mon Sep 17 00:00:00 2001 From: Prathmesh Ranaut Date: Tue, 18 Nov 2025 11:05:42 -0500 Subject: [PATCH 2/2] Fixed ReaderTest --- .../Entities/Terminal/Readers/ReaderTest.cs | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs b/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs index bd30485321..f7992c98d5 100644 --- a/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs +++ b/src/StripeTests/Entities/Terminal/Readers/ReaderTest.cs @@ -70,17 +70,17 @@ public void DeserializeWithLastSeenAtInSeconds() long timestampSeconds = 1699900000L; string json = $@"{{ - ""id"": ""tmr_test456"", - ""object"": ""terminal.reader"", - ""device_type"": ""simulated_stripe_s700"", - ""label"": ""Test Reader Seconds"", - ""last_seen_at"": {timestampSeconds}, - ""livemode"": false, - ""location"": ""tml_test789"", - ""metadata"": {{}}, - ""serial_number"": ""987654321"", - ""status"": ""online"" -}}"; + ""id"": ""tmr_test456"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader Seconds"", + ""last_seen_at"": {timestampSeconds}, + ""livemode"": false, + ""location"": ""tml_test789"", + ""metadata"": {{}}, + ""serial_number"": ""987654321"", + ""status"": ""online"" + }}"; var reader = JsonConvert.DeserializeObject(json); @@ -97,24 +97,24 @@ public void DeserializeWithLastSeenAtInSeconds() [Fact] public void DeserializeWithLastSeenAtInMilliseconds_SystemTextJson() { -// This test verifies that timestamps in milliseconds are handled correctly in System.Text.Json -// Addresses GitHub issue #3253 where last_seen_at comes as milliseconds instead of seconds + // This test verifies that timestamps in milliseconds are handled correctly in System.Text.Json + // Addresses GitHub issue #3253 where last_seen_at comes as milliseconds instead of seconds -// Unix timestamp in milliseconds for November 13, 2023 18:26:40 UTC -// 1699900000000 milliseconds = 1699900000 seconds -long timestampMillis = 1699900000000L; -string json = $@"{{ - ""id"": ""tmr_test123"", - ""object"": ""terminal.reader"", - ""device_type"": ""simulated_stripe_s700"", - ""label"": ""Test Reader STJ"", - ""last_seen_at"": {timestampMillis}, - ""livemode"": false, - ""location"": ""tml_test456"", - ""metadata"": {{}}, - ""serial_number"": ""123456789"", - ""status"": ""online"" -}}"; + // Unix timestamp in milliseconds for November 13, 2023 18:26:40 UTC + // 1699900000000 milliseconds = 1699900000 seconds + long timestampMillis = 1699900000000L; + string json = $@"{{ + ""id"": ""tmr_test123"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader STJ"", + ""last_seen_at"": {timestampMillis}, + ""livemode"": false, + ""location"": ""tml_test456"", + ""metadata"": {{}}, + ""serial_number"": ""123456789"", + ""status"": ""online"" + }}"; // This should now work correctly with the updated STJ converter var reader = System.Text.Json.JsonSerializer.Deserialize(json); @@ -131,22 +131,22 @@ public void DeserializeWithLastSeenAtInMilliseconds_SystemTextJson() [Fact] public void DeserializeWithLastSeenAtInSeconds_SystemTextJson() { -// This test verifies that regular timestamps in seconds still work correctly in System.Text.Json + // This test verifies that regular timestamps in seconds still work correctly in System.Text.Json -// Unix timestamp in seconds for November 13, 2023 18:26:40 UTC -long timestampSeconds = 1699900000L; -string json = $@"{{ - ""id"": ""tmr_test456"", - ""object"": ""terminal.reader"", - ""device_type"": ""simulated_stripe_s700"", - ""label"": ""Test Reader Seconds STJ"", - ""last_seen_at"": {timestampSeconds}, - ""livemode"": false, - ""location"": ""tml_test789"", - ""metadata"": {{}}, - ""serial_number"": ""987654321"", - ""status"": ""online"" -}}"; + // Unix timestamp in seconds for November 13, 2023 18:26:40 UTC + long timestampSeconds = 1699900000L; + string json = $@"{{ + ""id"": ""tmr_test456"", + ""object"": ""terminal.reader"", + ""device_type"": ""simulated_stripe_s700"", + ""label"": ""Test Reader Seconds STJ"", + ""last_seen_at"": {timestampSeconds}, + ""livemode"": false, + ""location"": ""tml_test789"", + ""metadata"": {{}}, + ""serial_number"": ""987654321"", + ""status"": ""online"" + }}"; var reader = System.Text.Json.JsonSerializer.Deserialize(json);