From 01a8c20ee89befaaf81cba61fc10635fcf2afb41 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 5 Apr 2020 23:57:43 +0900 Subject: [PATCH] Use better time precision for time in PaymentEntity --- .../DateTimeMilliJsonConverter.cs | 64 +++++++++++++++++++ .../Bitcoin/BitcoinLikePaymentData.cs | 1 + .../Services/Invoices/InvoiceEntity.cs | 31 ++++++++- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 BTCPayServer/JsonConverters/DateTimeMilliJsonConverter.cs diff --git a/BTCPayServer/JsonConverters/DateTimeMilliJsonConverter.cs b/BTCPayServer/JsonConverters/DateTimeMilliJsonConverter.cs new file mode 100644 index 000000000..49241f077 --- /dev/null +++ b/BTCPayServer/JsonConverters/DateTimeMilliJsonConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Reflection; +using Newtonsoft.Json; +using NBitcoin.JsonConverters; + +namespace BTCPayServer.JsonConverters +{ + class DateTimeMilliJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(DateTime).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()) || + typeof(DateTimeOffset).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()) || + typeof(DateTimeOffset?).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + } + + static DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return null; + if (reader.TokenType == JsonToken.Null) + return null; + var result = UnixTimeToDateTime((ulong)(long)reader.Value); + if (objectType == typeof(DateTime)) + return result.UtcDateTime; + return result; + } + + private DateTimeOffset UnixTimeToDateTime(ulong value) + { + var v = (long)value; + if(v < 0) + throw new FormatException("Invalid datetime (less than 1/1/1970)"); + return unixRef + TimeSpan.FromMilliseconds((long)v); + } + private long DateTimeToUnixTime(in DateTime time) + { + var date = ((DateTimeOffset)time).ToUniversalTime(); + long v = (long)(date - unixRef).TotalMilliseconds; + if(v < 0) + throw new FormatException("Invalid datetime (less than 1/1/1970)"); + return v; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + DateTime time; + if (value is DateTime) + time = (DateTime)value; + else + time = ((DateTimeOffset)value).UtcDateTime; + + if (time < UnixTimeToDateTime(0)) + time = UnixTimeToDateTime(0).UtcDateTime; + writer.WriteValue(DateTimeToUnixTime(time)); + } + + + } +} diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentData.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentData.cs index 43316cbee..2beb8d189 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentData.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentData.cs @@ -40,6 +40,7 @@ namespace BTCPayServer.Payments.Bitcoin public BitcoinAddress Address { get; set; } public IMoney Value { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public PayjoinInformation PayjoinInformation { get; set; } [JsonIgnore] diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 6cc2c1ee2..294047c8e 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -16,6 +16,7 @@ using BTCPayServer.Payments; using NBitpayClient; using BTCPayServer.Payments.Bitcoin; using System.ComponentModel.DataAnnotations.Schema; +using BTCPayServer.JsonConverters; namespace BTCPayServer.Services.Invoices { @@ -919,10 +920,38 @@ namespace BTCPayServer.Services.Invoices [JsonIgnore] public BTCPayNetworkBase Network { get; set; } public int Version { get; set; } - public DateTimeOffset ReceivedTime + + + // Old invoices use ReceivedTimeSeconds whose precision is not sufficient + [Obsolete("Use ReceivedTime instead")] + [JsonProperty("receivedTime", DefaultValueHandling = DefaultValueHandling.Ignore)] + public DateTimeOffset? ReceivedTimeSeconds { get; set; } + [Obsolete("Use ReceivedTime instead")] + [JsonProperty("receivedTimeMs", DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonConverter(typeof(DateTimeMilliJsonConverter))] + public DateTimeOffset? ReceivedTimeMilli + { + get; set; + } + [JsonIgnore] + public DateTimeOffset ReceivedTime + { + get + { +#pragma warning disable 618 + return (ReceivedTimeMilli ?? ReceivedTimeSeconds).Value; +#pragma warning restore 618 + } + set + { +#pragma warning disable 618 + ReceivedTimeMilli = value; +#pragma warning restore 618 + } + } public decimal NetworkFee { get; set; } [Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Outpoint")] public OutPoint Outpoint