From 1600dd4759a5cdefef6e0e704a849cf7e071ecfd Mon Sep 17 00:00:00 2001 From: d11n Date: Tue, 11 Jul 2023 08:32:01 +0200 Subject: [PATCH] POS: Backwards-compatible price parsing (#5163) * POS: Backwards-compatible price parsing Fixes #5159 and a regression introduced in bbff9710bf2f4a66bd6f4cd9e8ee55618d0ca5e0: The price in posData needs to be parsed in a backwards-compatible manner, as the old format of price as an object exists in the invoice metadata. * Test corner cases --------- Co-authored-by: nicolas.dorier --- BTCPayServer.Tests/FastTests.cs | 31 +++++++++++++ BTCPayServer/Services/Invoices/PosAppData.cs | 49 ++++++++++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/BTCPayServer.Tests/FastTests.cs b/BTCPayServer.Tests/FastTests.cs index 704721b27..157aa6371 100644 --- a/BTCPayServer.Tests/FastTests.cs +++ b/BTCPayServer.Tests/FastTests.cs @@ -1145,6 +1145,37 @@ namespace BTCPayServer.Tests Assert.Equal("000000161", m.OrderId); } + [Fact] + public void CanParseOldPosAppData() + { + var data = new JObject() + { + ["price"] = 1.64m + }.ToString(); + Assert.Equal(1.64m, JsonConvert.DeserializeObject(data).Price); + + data = new JObject() + { + ["price"] = new JObject() + { + ["value"] = 1.65m + } + }.ToString(); + Assert.Equal(1.65m, JsonConvert.DeserializeObject(data).Price); + + data = new JObject() + { + ["price"] = new JObject() + { + ["value"] = null + } + }.ToString(); + Assert.Equal(0.0m, JsonConvert.DeserializeObject(data).Price); + + var o = JObject.Parse(JsonConvert.SerializeObject(new PosAppCartItem() { Price = 1.356m })); + Assert.Equal(1.356m, o["price"].Value()); + } + [Fact] public void CanParseCurrencyValue() { diff --git a/BTCPayServer/Services/Invoices/PosAppData.cs b/BTCPayServer/Services/Invoices/PosAppData.cs index 2aecb1f13..0671874e3 100644 --- a/BTCPayServer/Services/Invoices/PosAppData.cs +++ b/BTCPayServer/Services/Invoices/PosAppData.cs @@ -1,5 +1,8 @@ +using System; +using System.Globalization; using BTCPayServer.Plugins.PointOfSale.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.Invoices; @@ -33,6 +36,7 @@ public class PosAppCartItem public string Id { get; set; } [JsonProperty(PropertyName = "price")] + [JsonConverter(typeof(PosAppCartItemPriceJsonConverter))] public decimal Price { get; set; } [JsonProperty(PropertyName = "title")] @@ -48,11 +52,46 @@ public class PosAppCartItem public string Image { get; set; } } -public class PosAppCartItemPrice +public class PosAppCartItemPriceJsonConverter : JsonConverter { - [JsonProperty(PropertyName = "formatted")] - public string Formatted { get; set; } + public override bool CanConvert(Type objectType) + { + return objectType == typeof(decimal) || objectType == typeof(object); + } - [JsonProperty(PropertyName = "type")] - public ViewPointOfSaleViewModel.ItemPriceType Type { get; set; } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + JToken token = JToken.Load(reader); + switch (token.Type) + { + case JTokenType.Float: + if (objectType == typeof(decimal)) + return token.Value(); + throw new JsonSerializationException($"Unexpected object type: {objectType}"); + case JTokenType.Integer: + case JTokenType.String: + if (objectType == typeof(decimal)) + return decimal.Parse(token.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture); + throw new JsonSerializationException($"Unexpected object type: {objectType}"); + case JTokenType.Null: + return null; + case JTokenType.Object: + return token.ToObject()?["value"]?.Value(); + default: + throw new JsonSerializationException($"Unexpected token type: {token.Type}"); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + switch (value) + { + case null: + break; + case decimal x: + writer.WriteValue(x.ToString(CultureInfo.InvariantCulture)); + break; + } + } }