From bacdfe33cae3563acfb05a9acab57b0ccb944fd2 Mon Sep 17 00:00:00 2001 From: Alexander Olesiyuk Date: Mon, 16 Dec 2024 18:57:37 +0300 Subject: [PATCH 1/3] Explicitly configure ContractResolver for SaleToPOIMessage serialization --- Adyen.Test/SerializerTest.cs | 88 ++++++++++++++++++- .../Converter/JsonConvertSerializerWrapper.cs | 30 +++++-- .../Converter/SaleToPoiMessageConverter.cs | 2 - .../SaleToPoiMessageSecuredConverter.cs | 2 - 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/Adyen.Test/SerializerTest.cs b/Adyen.Test/SerializerTest.cs index f5963fb0c..0fb4d9b71 100644 --- a/Adyen.Test/SerializerTest.cs +++ b/Adyen.Test/SerializerTest.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; using Adyen.ApiSerialization; using Adyen.Model.Checkout; using Adyen.Model.TerminalApi; +using Adyen.Security; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; using PaymentResponse = Adyen.Model.TerminalApi.PaymentResponse; namespace Adyen.Test @@ -87,9 +87,91 @@ public void CheckoutSessionResponseCheckForIdTest() Assert.IsTrue(deserializedResponse.ToJson().Contains("\"id\": \"CS0068299CB8DA273A\",")); } + [TestMethod] + public void EnsureSaleToPoiMessageSerializationDoesNotDependOnJsonConvertDefaultSettings() + { + var saleToPoiMessage = MockPosApiRequest.CreatePosPaymentRequest(); + var serializedWithEmptyDefaultSettings = GetSerializedSaleToPoiMessage(saleToPoiMessage); + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + } + }; + + try + { + var serializedWithUpdatedDefaultSettings = GetSerializedSaleToPoiMessage(saleToPoiMessage); + + Assert.AreEqual(serializedWithEmptyDefaultSettings, serializedWithUpdatedDefaultSettings); + } + finally + { + JsonConvert.DefaultSettings = null; + } + } + + [TestMethod] + public void EnsureSaleToPoiMessageSecuredSerializationDoesNotDependOnJsonConvertDefaultSettings() + { + var saleToPoiMessage = MockPosApiRequest.CreatePosPaymentRequest(); + var serializedWithEmptyDefaultSettings = GetSerializedSaleToPoiMessageSecured(saleToPoiMessage); + + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + } + }; + + try + { + var serializedWithUpdatedDefaultSettings = GetSerializedSaleToPoiMessageSecured(saleToPoiMessage); + + Assert.AreEqual(serializedWithEmptyDefaultSettings, serializedWithUpdatedDefaultSettings); + } + finally + { + JsonConvert.DefaultSettings = null; + } + } + private static string GetSaleToPoiMessage(string online) { return "{\"SaleToPOIResponse\": {\"PaymentResponse\": {\"POIData\": {},\"PaymentResult\": {\"AuthenticationMethod\": [\"" + online + "\"],\"PaymentAcquirerData\": {\"AcquirerPOIID\": \"MX925-260390740\",\"MerchantID\": \"PME_POS\"},\"PaymentType\": \"Normal\"},\"Response\": {\"Result\": \"Success\"}},\"MessageHeader\": {\"ProtocolVersion\": \"3.0\",\"SaleID\": \"Appie\",\"MessageClass\": \"Service\",\"MessageCategory\": \"Payment\",\"ServiceID\": \"20095135\",\"POIID\": \"MX925-260390740\",\"MessageType\": \"Response\"}}}"; } + + private static string GetSerializedSaleToPoiMessage(SaleToPOIMessage saleToPoiMessage) + { + var saleToPoiMessageSerializer = new SaleToPoiMessageSerializer(); + return saleToPoiMessageSerializer.Serialize(saleToPoiMessage); + } + + private static string GetSerializedSaleToPoiMessageSecured(SaleToPOIMessage saleToPoiMessage) + { + var saleToPoiMessageSerializer = new SaleToPoiMessageSerializer(); + var serializedSaleToPoiMessage = saleToPoiMessageSerializer.Serialize(saleToPoiMessage); + + var encryptionCredentialDetails = new EncryptionCredentialDetails + { + AdyenCryptoVersion = 1, + KeyIdentifier = "CryptoKeyIdentifier12345", + Password = "p@ssw0rd123456" + }; + var messageSecuredEncryptor = new SaleToPoiMessageSecuredEncryptor(); + var saleToPoiMessageSecured = messageSecuredEncryptor.Encrypt( + serializedSaleToPoiMessage, + saleToPoiMessage.MessageHeader, + encryptionCredentialDetails); + + // Clear SecurityTrailer.Nonce and NexoBlob as they are randomly generated every run + saleToPoiMessageSecured.NexoBlob = null; + saleToPoiMessageSecured.SecurityTrailer.Nonce = null; + + return saleToPoiMessageSerializer.Serialize(saleToPoiMessageSecured); + } } } diff --git a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs index 3aeabbdad..b06d86b07 100644 --- a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs +++ b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs @@ -1,7 +1,9 @@ -using Adyen.Model.TerminalApi; +using System.Collections.Generic; +using Adyen.Model.TerminalApi; using Adyen.Security; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; namespace Adyen.ApiSerialization.Converter { @@ -11,19 +13,31 @@ internal class JsonConvertSerializerWrapper internal static string Serialize(SaleToPOIMessage saleToPoiMessage) { - var serialize= JsonConvert.SerializeObject(saleToPoiMessage, - new SaleToPoiMessageConverter(), - new StringEnumConverter(), - new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat }); + var serialize = JsonConvert.SerializeObject(saleToPoiMessage, + GetSerializerSettings(new SaleToPoiMessageConverter())); return serialize; } internal static string Serialize(SaleToPoiMessageSecured saleToPoiMessageSecured) { return JsonConvert.SerializeObject(saleToPoiMessageSecured, - new SaleToPoiMessageSecuredConverter(), - new StringEnumConverter(), - new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat }); + GetSerializerSettings(new SaleToPoiMessageSecuredConverter())); + } + + private static JsonSerializerSettings GetSerializerSettings(JsonConverter messageConverter) + { + return new JsonSerializerSettings + { + Converters = new List + { + messageConverter, + new StringEnumConverter(), + new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat } + }, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ContractResolver = new DefaultContractResolver() + }; } } } diff --git a/Adyen/ApiSerialization/Converter/SaleToPoiMessageConverter.cs b/Adyen/ApiSerialization/Converter/SaleToPoiMessageConverter.cs index 6c8ccb59e..a10b19a5e 100644 --- a/Adyen/ApiSerialization/Converter/SaleToPoiMessageConverter.cs +++ b/Adyen/ApiSerialization/Converter/SaleToPoiMessageConverter.cs @@ -8,8 +8,6 @@ internal class SaleToPoiMessageConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - serializer.NullValueHandling = NullValueHandling.Ignore; - serializer.MissingMemberHandling = MissingMemberHandling.Ignore; writer.WriteStartObject(); writer.WritePropertyName(value.GetType().Name); writer.WriteStartObject(); diff --git a/Adyen/ApiSerialization/Converter/SaleToPoiMessageSecuredConverter.cs b/Adyen/ApiSerialization/Converter/SaleToPoiMessageSecuredConverter.cs index a9a70649a..82eb7398c 100644 --- a/Adyen/ApiSerialization/Converter/SaleToPoiMessageSecuredConverter.cs +++ b/Adyen/ApiSerialization/Converter/SaleToPoiMessageSecuredConverter.cs @@ -14,8 +14,6 @@ internal class SaleToPoiMessageSecuredConverter : JsonConverter public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - serializer.NullValueHandling = NullValueHandling.Ignore; - writer.WriteStartObject(); writer.WritePropertyName(GetProperTypeNameForSerialization(value.GetType())); From 5fd197b9e49b3b2fb98a6576dfb4af6f89b9c3c8 Mon Sep 17 00:00:00 2001 From: Alexander Olesiyuk Date: Tue, 24 Dec 2024 12:32:22 +0300 Subject: [PATCH 2/3] Minor improvement --- .../Converter/JsonConvertSerializerWrapper.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs index b06d86b07..72c642a87 100644 --- a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs +++ b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs @@ -13,31 +13,29 @@ internal class JsonConvertSerializerWrapper internal static string Serialize(SaleToPOIMessage saleToPoiMessage) { - var serialize = JsonConvert.SerializeObject(saleToPoiMessage, - GetSerializerSettings(new SaleToPoiMessageConverter())); - return serialize; + return Serialize(saleToPoiMessage, new SaleToPoiMessageConverter()); } internal static string Serialize(SaleToPoiMessageSecured saleToPoiMessageSecured) { - return JsonConvert.SerializeObject(saleToPoiMessageSecured, - GetSerializerSettings(new SaleToPoiMessageSecuredConverter())); + return Serialize(saleToPoiMessageSecured, new SaleToPoiMessageSecuredConverter()); } - private static JsonSerializerSettings GetSerializerSettings(JsonConverter messageConverter) + private static string Serialize(object message, JsonConverter messageConverter) { - return new JsonSerializerSettings - { - Converters = new List + return JsonConvert.SerializeObject(message, + new JsonSerializerSettings { - messageConverter, - new StringEnumConverter(), - new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat } - }, - NullValueHandling = NullValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - ContractResolver = new DefaultContractResolver() - }; + Converters = new List + { + messageConverter, + new StringEnumConverter(), + new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat } + }, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ContractResolver = new DefaultContractResolver() + }); } } } From 8b2899f50836b0c19b9ed6f62ebf2d63307bb493 Mon Sep 17 00:00:00 2001 From: Alexander Olesiyuk Date: Tue, 24 Dec 2024 19:11:46 +0300 Subject: [PATCH 3/3] PR fixes --- Adyen.Test/SerializerTest.cs | 88 ++++++++++++++++--- .../Converter/JsonConvertSerializerWrapper.cs | 32 +++---- 2 files changed, 92 insertions(+), 28 deletions(-) diff --git a/Adyen.Test/SerializerTest.cs b/Adyen.Test/SerializerTest.cs index 0fb4d9b71..9dac6609b 100644 --- a/Adyen.Test/SerializerTest.cs +++ b/Adyen.Test/SerializerTest.cs @@ -1,12 +1,15 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; using Adyen.ApiSerialization; using Adyen.Model.Checkout; using Adyen.Model.TerminalApi; +using Adyen.Model.TerminalApi.Message; using Adyen.Security; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using PaymentRequest = Adyen.Model.TerminalApi.PaymentRequest; using PaymentResponse = Adyen.Model.TerminalApi.PaymentResponse; namespace Adyen.Test @@ -88,10 +91,17 @@ public void CheckoutSessionResponseCheckForIdTest() } [TestMethod] - public void EnsureSaleToPoiMessageSerializationDoesNotDependOnJsonConvertDefaultSettings() + public void SaleToPoiMessageSerializationTest() { - var saleToPoiMessage = MockPosApiRequest.CreatePosPaymentRequest(); - var serializedWithEmptyDefaultSettings = GetSerializedSaleToPoiMessage(saleToPoiMessage); + var saleToPoiMessage = PosPaymentRequest; + var serialized = SerializeSaleToPoiMessage(saleToPoiMessage); + Assert.AreEqual(serialized, ExpectedSaleToPoiMessageJson); + } + + [TestMethod] + public void SaleToPoiMessageWithUpdatedJsonConvertDefaultSettingsSerializationTest() + { + var saleToPoiMessage = PosPaymentRequest; JsonConvert.DefaultSettings = () => new JsonSerializerSettings { @@ -103,9 +113,9 @@ public void EnsureSaleToPoiMessageSerializationDoesNotDependOnJsonConvertDefault try { - var serializedWithUpdatedDefaultSettings = GetSerializedSaleToPoiMessage(saleToPoiMessage); + var serialized = SerializeSaleToPoiMessage(saleToPoiMessage); - Assert.AreEqual(serializedWithEmptyDefaultSettings, serializedWithUpdatedDefaultSettings); + Assert.AreEqual(serialized, ExpectedSaleToPoiMessageJson); } finally { @@ -114,10 +124,17 @@ public void EnsureSaleToPoiMessageSerializationDoesNotDependOnJsonConvertDefault } [TestMethod] - public void EnsureSaleToPoiMessageSecuredSerializationDoesNotDependOnJsonConvertDefaultSettings() + public void SaleToPoiMessageSecuredSerializationTest() { - var saleToPoiMessage = MockPosApiRequest.CreatePosPaymentRequest(); - var serializedWithEmptyDefaultSettings = GetSerializedSaleToPoiMessageSecured(saleToPoiMessage); + var saleToPoiMessage = PosPaymentRequest; + var serialized = SerializeSaleToPoiMessageSecured(saleToPoiMessage); + Assert.AreEqual(serialized, ExpectedSaleToPoiMessageSecuredJson); + } + + [TestMethod] + public void SaleToPoiMessageSecuredWithUpdatedJsonConvertDefaultSettingsSerializationTest() + { + var saleToPoiMessage = PosPaymentRequest; JsonConvert.DefaultSettings = () => new JsonSerializerSettings { @@ -129,9 +146,9 @@ public void EnsureSaleToPoiMessageSecuredSerializationDoesNotDependOnJsonConvert try { - var serializedWithUpdatedDefaultSettings = GetSerializedSaleToPoiMessageSecured(saleToPoiMessage); + var serialized = SerializeSaleToPoiMessageSecured(saleToPoiMessage); - Assert.AreEqual(serializedWithEmptyDefaultSettings, serializedWithUpdatedDefaultSettings); + Assert.AreEqual(serialized, ExpectedSaleToPoiMessageSecuredJson); } finally { @@ -144,13 +161,14 @@ private static string GetSaleToPoiMessage(string online) return "{\"SaleToPOIResponse\": {\"PaymentResponse\": {\"POIData\": {},\"PaymentResult\": {\"AuthenticationMethod\": [\"" + online + "\"],\"PaymentAcquirerData\": {\"AcquirerPOIID\": \"MX925-260390740\",\"MerchantID\": \"PME_POS\"},\"PaymentType\": \"Normal\"},\"Response\": {\"Result\": \"Success\"}},\"MessageHeader\": {\"ProtocolVersion\": \"3.0\",\"SaleID\": \"Appie\",\"MessageClass\": \"Service\",\"MessageCategory\": \"Payment\",\"ServiceID\": \"20095135\",\"POIID\": \"MX925-260390740\",\"MessageType\": \"Response\"}}}"; } - private static string GetSerializedSaleToPoiMessage(SaleToPOIMessage saleToPoiMessage) + private static string SerializeSaleToPoiMessage(SaleToPOIMessage saleToPoiMessage) { var saleToPoiMessageSerializer = new SaleToPoiMessageSerializer(); return saleToPoiMessageSerializer.Serialize(saleToPoiMessage); } - private static string GetSerializedSaleToPoiMessageSecured(SaleToPOIMessage saleToPoiMessage) + + private static string SerializeSaleToPoiMessageSecured(SaleToPOIMessage saleToPoiMessage) { var saleToPoiMessageSerializer = new SaleToPoiMessageSerializer(); var serializedSaleToPoiMessage = saleToPoiMessageSerializer.Serialize(saleToPoiMessage); @@ -173,5 +191,49 @@ private static string GetSerializedSaleToPoiMessageSecured(SaleToPOIMessage sale return saleToPoiMessageSerializer.Serialize(saleToPoiMessageSecured); } + + private static SaleToPOIRequest PosPaymentRequest => + new SaleToPOIRequest + { + MessageHeader = new MessageHeader + { + MessageType = MessageType.Request, + MessageClass = MessageClassType.Service, + MessageCategory = MessageCategoryType.Payment, + SaleID = "POSSystemID12345", + POIID = "MX915-284251016", + ServiceID = "12345678" + }, + MessagePayload = new PaymentRequest + { + SaleData = new SaleData + { + SaleTransactionID = new TransactionIdentification + { + TransactionID = "PosAuth", + TimeStamp = new DateTime(2025, 1, 1) + }, + TokenRequestedType = TokenRequestedType.Customer, + }, + PaymentTransaction = new PaymentTransaction + { + AmountsReq = new AmountsReq + { + Currency = "EUR", + RequestedAmount = 10100 + } + }, + PaymentData = new PaymentData + { + PaymentType = PaymentType.Normal + } + } + }; + + private static string ExpectedSaleToPoiMessageJson => + "{\"SaleToPOIRequest\":{\"MessageHeader\":{\"MessageClass\":\"Service\",\"MessageCategory\":\"Payment\",\"MessageType\":\"Request\",\"ServiceID\":\"12345678\",\"SaleID\":\"POSSystemID12345\",\"POIID\":\"MX915-284251016\",\"ProtocolVersion\":\"3.0\"},\"PaymentRequest\":{\"SaleData\":{\"SaleTransactionID\":{\"TransactionID\":\"PosAuth\",\"TimeStamp\":\"2025-01-01T00:00:00\"},\"SaleToAcquirerData\":\"eyJhcHBsaWNhdGlvbkluZm8iOnsiYWR5ZW5MaWJyYXJ5Ijp7Im5hbWUiOiJhZHllbi1kb3RuZXQtYXBpLWxpYnJhcnkiLCJ2ZXJzaW9uIjoiMjYuMC4wIn19fQ==\",\"TokenRequestedType\":\"Customer\"},\"PaymentTransaction\":{\"AmountsReq\":{\"Currency\":\"EUR\",\"RequestedAmount\":10100.0}},\"PaymentData\":{\"PaymentType\":\"Normal\"}}}}"; + + private static string ExpectedSaleToPoiMessageSecuredJson => + "{\"SaleToPOIRequest\":{\"MessageHeader\":{\"MessageClass\":\"Service\",\"MessageCategory\":\"Payment\",\"MessageType\":\"Request\",\"ServiceID\":\"12345678\",\"SaleID\":\"POSSystemID12345\",\"POIID\":\"MX915-284251016\",\"ProtocolVersion\":\"3.0\"},\"NexoBlob\":null,\"SecurityTrailer\":{\"AdyenCryptoVersion\":1,\"KeyIdentifier\":\"CryptoKeyIdentifier12345\",\"KeyVersion\":0,\"Hmac\":\"0lPogF5Mg97Nty9ZUuAnb3v8pvZTZvwouxdMp0HV+yQ=\"}}}"; } } diff --git a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs index 72c642a87..d03d02db9 100644 --- a/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs +++ b/Adyen/ApiSerialization/Converter/JsonConvertSerializerWrapper.cs @@ -11,31 +11,33 @@ internal class JsonConvertSerializerWrapper { private const string DateTimeFormat = "yyyy-MM-ddTHH\\:mm\\:ss"; + private static readonly JsonSerializerSettings SaleToPoiMessageSerializerSettings = CreateSerializerSettings(new SaleToPoiMessageConverter()); + private static readonly JsonSerializerSettings SaleToPoiMessageSecuredSerializerSettings = CreateSerializerSettings(new SaleToPoiMessageSecuredConverter()); + internal static string Serialize(SaleToPOIMessage saleToPoiMessage) { - return Serialize(saleToPoiMessage, new SaleToPoiMessageConverter()); + return JsonConvert.SerializeObject(saleToPoiMessage, SaleToPoiMessageSerializerSettings); } internal static string Serialize(SaleToPoiMessageSecured saleToPoiMessageSecured) { - return Serialize(saleToPoiMessageSecured, new SaleToPoiMessageSecuredConverter()); + return JsonConvert.SerializeObject(saleToPoiMessageSecured, SaleToPoiMessageSecuredSerializerSettings); } - private static string Serialize(object message, JsonConverter messageConverter) + private static JsonSerializerSettings CreateSerializerSettings(JsonConverter messageConverter) { - return JsonConvert.SerializeObject(message, - new JsonSerializerSettings + return new JsonSerializerSettings + { + Converters = new List { - Converters = new List - { - messageConverter, - new StringEnumConverter(), - new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat } - }, - NullValueHandling = NullValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - ContractResolver = new DefaultContractResolver() - }); + messageConverter, + new StringEnumConverter(), + new IsoDateTimeConverter { DateTimeFormat = DateTimeFormat } + }, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ContractResolver = new DefaultContractResolver() + }; } } }