loyaltyTotals;
/** The Payment instrument. */
- @XmlElement(name = "PaymentInstrumentType", required = true)
+ @SerializedName("PaymentInstrumentType")
@Schema(description = "Type of payment instrument.")
protected PaymentInstrumentType paymentInstrumentType;
/** The Acquirer id. */
- @XmlElement(name = "AcquirerID")
+ @SerializedName("AcquirerID")
@Schema(description = "Identification of the Acquirer --Rule: If available")
protected String acquirerID;
/** The Error condition. */
- @XmlElement(name = "ErrorCondition")
+ @SerializedName("ErrorCondition")
@Schema(
description =
"Condition that has produced an error on the processing of a message request --Rule: if Response.Result is Partial, and the reconciliation with this Acquirer failed.")
protected ErrorConditionType errorCondition;
/** The Host reconciliation id. */
- @XmlElement(name = "HostReconciliationID")
+ @SerializedName("HostReconciliationID")
@Schema(
description =
"Identifier of a reconciliation period with a payment or loyalty host. --Rule: If available")
protected String hostReconciliationID;
/** The Card brand. */
- @XmlElement(name = "CardBrand")
+ @SerializedName("CardBrand")
@Schema(
description =
"Type of payment or loyalty card --Rule: If configured to present totals per card brand, and Response.Result is Success")
protected String cardBrand;
/** The Poiid. */
- @XmlElement(name = "POIID")
+ @SerializedName("POIID")
@Schema(
description =
"Identification of a POI System or a POI Terminal for the Sale to POI protocol --Rule: If requested in the message request")
protected String poiid;
/** The Sale id. */
- @XmlElement(name = "SaleID")
+ @SerializedName("SaleID")
@Schema(
description =
"Identification of a Sale System or a Sale Terminal for the Sale to POI protocol --Rule: If requested in the message request")
protected String saleID;
/** The Operator id. */
- @XmlElement(name = "OperatorID")
+ @SerializedName("OperatorID")
@Schema(
description =
"Identification of the Cashier or Operator. --Rule: If requested in the message request")
protected String operatorID;
/** The Shift number. */
- @XmlElement(name = "ShiftNumber")
+ @SerializedName("ShiftNumber")
@Schema(description = "Shift number. --Rule: If requested in the message request")
protected String shiftNumber;
/** The Totals group id. */
- @XmlElement(name = "TotalsGroupID")
+ @SerializedName("TotalsGroupID")
@Schema(
description =
"Identification of a group of transaction on a POI Terminal, having the same Sale features. --Rule: If requested in the message request")
protected String totalsGroupID;
/** The Payment currency. */
- @XmlElement(name = "PaymentCurrency")
+ @SerializedName("PaymentCurrency")
@Schema(description = "Currency of a monetary amount.")
protected String paymentCurrency;
/** The Loyalty unit. */
- @XmlElement(name = "LoyaltyUnit")
+ @SerializedName("LoyaltyUnit")
@Schema(description = "Unit of a loyalty amount.")
protected LoyaltyUnitType loyaltyUnit;
/** The Loyalty currency. */
- @XmlElement(name = "LoyaltyCurrency")
+ @SerializedName("LoyaltyCurrency")
@Schema(description = "Currency of a monetary amount. --Rule: If LoyaltyUnit is Monetary")
protected String loyaltyCurrency;
diff --git a/src/main/java/com/adyen/model/nexo/TransactionType.java b/src/main/java/com/adyen/model/nexo/TransactionType.java
index af98ad51b..b83bb0045 100644
--- a/src/main/java/com/adyen/model/nexo/TransactionType.java
+++ b/src/main/java/com/adyen/model/nexo/TransactionType.java
@@ -1,10 +1,8 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Arrays;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlEnumValue;
-import javax.xml.bind.annotation.XmlType;
/**
* Java class for TransactionType.
@@ -37,39 +35,37 @@
* </simpleType>
*
*/
-@XmlType(name = "TransactionType")
-@XmlEnum
public enum TransactionType {
/** Payment Debit transactions (e.g. if PaymentType is "Normal") */
- @XmlEnumValue("Debit")
+ @SerializedName("Debit")
@Schema(description = "Payment Debit transactions (e.g. if PaymentType is \"Normal\")")
DEBIT("Debit"),
/** Payment Credit transactions (e.g. if PaymentType is "Refund") */
- @XmlEnumValue("Credit")
+ @SerializedName("Credit")
@Schema(description = "Payment Credit transactions (e.g. if PaymentType is \"Refund\")")
CREDIT("Credit"),
/** Payment Reversal Debit transactions */
- @XmlEnumValue("ReverseDebit")
+ @SerializedName("ReverseDebit")
@Schema(description = "Payment Reversal Debit transactions")
REVERSE_DEBIT("ReverseDebit"),
/** Payment Reversal Credit transactions */
- @XmlEnumValue("ReverseCredit")
+ @SerializedName("ReverseCredit")
@Schema(description = "Payment Reversal Credit transactions")
REVERSE_CREDIT("ReverseCredit"),
/** Outstanding OneTimeReservation transactions, i.e. between OneTimeReservation and Completion */
- @XmlEnumValue("OneTimeReservation")
+ @SerializedName("OneTimeReservation")
@Schema(
description =
"Outstanding OneTimeReservation transactions, i.e. between OneTimeReservation and Completion")
ONE_TIME_RESERVATION("OneTimeReservation"),
/** OneTimeReservation transactions which have been completed by the Completion. */
- @XmlEnumValue("CompletedDeffered")
+ @SerializedName("CompletedDeffered")
@Schema(
description = "OneTimeReservation transactions which have been completed by the Completion.")
COMPLETED_DEFFERED("CompletedDeffered"),
@@ -78,7 +74,7 @@ public enum TransactionType {
* Outstanding FirstReservation transactions, i.e. between FirstReservation and UpdateReservation
* or Completion
*/
- @XmlEnumValue("FirstReservation")
+ @SerializedName("FirstReservation")
@Schema(
description =
"Outstanding FirstReservation transactions, i.e. between FirstReservation and UpdateReservation or Completion")
@@ -88,64 +84,64 @@ public enum TransactionType {
* Outstanding UpdateReservation transactions, i.e. between UpdateReservation and
* UpdateReservation or Completion
*/
- @XmlEnumValue("UpdateReservation")
+ @SerializedName("UpdateReservation")
@Schema(
description =
"Outstanding UpdateReservation transactions, i.e. between UpdateReservation and UpdateReservation or Completion")
UPDATE_RESERVATION("UpdateReservation"),
/** Reservation transactions which have been completed by the Completion. */
- @XmlEnumValue("CompletedReservation")
+ @SerializedName("CompletedReservation")
@Schema(description = "Reservation transactions which have been completed by the Completion.")
COMPLETED_RESERVATION("CompletedReservation"),
/** Cash Advance transactions. */
- @XmlEnumValue("CashAdvance")
+ @SerializedName("CashAdvance")
@Schema(description = "Cash Advance transactions.")
CASH_ADVANCE("CashAdvance"),
/** Issuer instalment transactions. */
- @XmlEnumValue("IssuerInstalment")
+ @SerializedName("IssuerInstalment")
@Schema(description = "Issuer instalment transactions.")
ISSUER_INSTALMENT("IssuerInstalment"),
/** ResultErrorCondition */
- @XmlEnumValue("Declined")
+ @SerializedName("Declined")
@Schema(description = "ResultErrorCondition")
DECLINED("Declined"),
/** ResultErrorCondition */
- @XmlEnumValue("Failed")
+ @SerializedName("Failed")
@Schema(description = "ResultErrorCondition")
FAILED("Failed"),
/** Loyalty Award Transaction */
- @XmlEnumValue("Award")
+ @SerializedName("Award")
@Schema(description = "Loyalty Award Transaction")
AWARD("Award"),
/** Loyalty Reversal Award Transaction */
- @XmlEnumValue("ReverseAward")
+ @SerializedName("ReverseAward")
@Schema(description = "Loyalty Reversal Award Transaction")
REVERSE_AWARD("ReverseAward"),
/** Loyalty Redemption Transaction */
- @XmlEnumValue("Redemption")
+ @SerializedName("Redemption")
@Schema(description = "Loyalty Redemption Transaction")
REDEMPTION("Redemption"),
/** Loyalty Reversal Redemption Transaction */
- @XmlEnumValue("ReverseRedemption")
+ @SerializedName("ReverseRedemption")
@Schema(description = "Loyalty Reversal Redemption Transaction")
REVERSE_REDEMPTION("ReverseRedemption"),
/** Loyalty Rebate Transaction */
- @XmlEnumValue("Rebate")
+ @SerializedName("Rebate")
@Schema(description = "Loyalty Rebate Transaction")
REBATE("Rebate"),
/** Loyalty Reversal Rebate Transaction */
- @XmlEnumValue("ReverseRebate")
+ @SerializedName("ReverseRebate")
@Schema(description = "Loyalty Reversal Rebate Transaction")
REVERSE_REBATE("ReverseRebate");
private final String value;
diff --git a/src/main/java/com/adyen/model/nexo/TransmitRequest.java b/src/main/java/com/adyen/model/nexo/TransmitRequest.java
index 5ef5e2c6e..f2edd88a4 100644
--- a/src/main/java/com/adyen/model/nexo/TransmitRequest.java
+++ b/src/main/java/com/adyen/model/nexo/TransmitRequest.java
@@ -1,11 +1,8 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigInteger;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlType;
/**
* Definition: Content of the Transmit Request messageType. -- Usage: It contains a messageType to
@@ -30,29 +27,25 @@
* </complexType>
*
*/
-@XmlAccessorType(XmlAccessType.FIELD)
-@XmlType(
- name = "TransmitRequest",
- propOrder = {"message"})
public class TransmitRequest {
/** The Message. */
- @XmlElement(name = "Message", required = true)
+ @SerializedName("Message")
@Schema(description = "Content of a transmitted message.")
protected byte[] message;
/** The Wait response flag. */
- @XmlElement(name = "WaitResponseFlag")
+ @SerializedName("WaitResponseFlag")
@Schema(description = "Indicates that a response message has to be received.")
protected Boolean waitResponseFlag;
/** The Maximum transmit time. */
- @XmlElement(name = "MaximumTransmitTime", required = true)
+ @SerializedName("MaximumTransmitTime")
@Schema(description = "Maximum time in seconds of transmission.")
protected BigInteger maximumTransmitTime;
/** The Destination address. */
- @XmlElement(name = "DestinationAddress", required = true)
+ @SerializedName("DestinationAddress")
@Schema(
description =
"Transport address containing the IP address or the DNS (Domain Name Server) address, followed by the character ':' and")
diff --git a/src/main/java/com/adyen/model/nexo/TransmitResponse.java b/src/main/java/com/adyen/model/nexo/TransmitResponse.java
index bef971c79..13cfa5a5f 100644
--- a/src/main/java/com/adyen/model/nexo/TransmitResponse.java
+++ b/src/main/java/com/adyen/model/nexo/TransmitResponse.java
@@ -1,10 +1,7 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlType;
/**
* Definition: Content of the Transmit Response messageType. -- Usage: It conveys the response of
@@ -27,19 +24,15 @@
* </complexType>
*
*/
-@XmlAccessorType(XmlAccessType.FIELD)
-@XmlType(
- name = "TransmitResponse",
- propOrder = {"response", "message"})
public class TransmitResponse {
/** The Response. */
- @XmlElement(name = "Response", required = true)
+ @SerializedName("Response")
@Schema(description = "Result of a message request processing.")
protected Response response;
/** The Message. */
- @XmlElement(name = "Message")
+ @SerializedName("Message")
@Schema(description = "Content of a transmitted message.")
protected byte[] message;
diff --git a/src/main/java/com/adyen/model/nexo/UTMCoordinates.java b/src/main/java/com/adyen/model/nexo/UTMCoordinates.java
index da758c6e6..e1dab0d4c 100644
--- a/src/main/java/com/adyen/model/nexo/UTMCoordinates.java
+++ b/src/main/java/com/adyen/model/nexo/UTMCoordinates.java
@@ -1,10 +1,7 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlType;
/**
* Definition: Location on the Earth specified by the Universal Transverse Mercator coordinate
@@ -29,26 +26,22 @@
* </complexType>
*
*/
-@XmlAccessorType(XmlAccessType.FIELD)
-@XmlType(
- name = "UTMCoordinates",
- propOrder = {"utmZone", "utmEastward", "utmNorthward"})
public class UTMCoordinates {
/** The Utm zone. */
- @XmlElement(name = "UTMZone", required = true)
+ @SerializedName("UTMZone")
@Schema(
description =
"UTM grid zone combination of the longitude zone (1 to 60) and the latitude band (C to X, excluding I and O).")
protected String utmZone;
/** The Utm eastward. */
- @XmlElement(name = "UTMEastward", required = true)
+ @SerializedName("UTMEastward")
@Schema(description = "X-coordinate of the Universal Transverse Mercator coordinate system.")
protected String utmEastward;
/** The Utm northward. */
- @XmlElement(name = "UTMNorthward", required = true)
+ @SerializedName("UTMNorthward")
@Schema(description = "Y-coordinate of the Universal Transverse Mercator coordinate system.")
protected String utmNorthward;
diff --git a/src/main/java/com/adyen/model/nexo/UnitOfMeasureType.java b/src/main/java/com/adyen/model/nexo/UnitOfMeasureType.java
index 952bc34a2..f4e08b60c 100644
--- a/src/main/java/com/adyen/model/nexo/UnitOfMeasureType.java
+++ b/src/main/java/com/adyen/model/nexo/UnitOfMeasureType.java
@@ -1,10 +1,8 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Arrays;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlEnumValue;
-import javax.xml.bind.annotation.XmlType;
/**
* Java class for UnitOfMeasureType.
@@ -37,102 +35,100 @@
* </simpleType>
*
*/
-@XmlType(name = "UnitOfMeasureType")
-@XmlEnum
public enum UnitOfMeasureType {
/** Case or Carton */
- @XmlEnumValue("Case")
+ @SerializedName("Case")
@Schema(description = "Case or Carton")
CASE("Case"),
/** Foot */
- @XmlEnumValue("Foot")
+ @SerializedName("Foot")
@Schema(description = "Foot")
FOOT("Foot"),
/** Gallon (UK) */
- @XmlEnumValue("UKGallon")
+ @SerializedName("UKGallon")
@Schema(description = "Gallon (UK)")
UK_GALLON("UKGallon"),
/** Gallon (US) */
- @XmlEnumValue("USGallon")
+ @SerializedName("USGallon")
@Schema(description = "Gallon (US)")
US_GALLON("USGallon"),
/** Gram */
- @XmlEnumValue("Gram")
+ @SerializedName("Gram")
@Schema(description = "Gram")
GRAM("Gram"),
/** Inch */
- @XmlEnumValue("Inch")
+ @SerializedName("Inch")
@Schema(description = "Inch")
INCH("Inch"),
/** Kilogram */
- @XmlEnumValue("Kilogram")
+ @SerializedName("Kilogram")
@Schema(description = "Kilogram")
KILOGRAM("Kilogram"),
/** Pound */
- @XmlEnumValue("Pound")
+ @SerializedName("Pound")
@Schema(description = "Pound")
POUND("Pound"),
/** Meter */
- @XmlEnumValue("Meter")
+ @SerializedName("Meter")
@Schema(description = "Meter")
METER("Meter"),
/** Centimetre */
- @XmlEnumValue("Centimetre")
+ @SerializedName("Centimetre")
@Schema(description = "Centimetre")
CENTIMETRE("Centimetre"),
/** Litre */
- @XmlEnumValue("Litre")
+ @SerializedName("Litre")
@Schema(description = "Litre")
LITRE("Litre"),
/** Centilitre */
- @XmlEnumValue("Centilitre")
+ @SerializedName("Centilitre")
@Schema(description = "Centilitre")
CENTILITRE("Centilitre"),
/** Ounce */
- @XmlEnumValue("Ounce")
+ @SerializedName("Ounce")
@Schema(description = "Ounce")
OUNCE("Ounce"),
/** Quart */
- @XmlEnumValue("Quart")
+ @SerializedName("Quart")
@Schema(description = "Quart")
QUART("Quart"),
/** Pint */
- @XmlEnumValue("Pint")
+ @SerializedName("Pint")
@Schema(description = "Pint")
PINT("Pint"),
/** Mile */
- @XmlEnumValue("Mile")
+ @SerializedName("Mile")
@Schema(description = "Mile")
MILE("Mile"),
/** Kilometre */
- @XmlEnumValue("Kilometre")
+ @SerializedName("Kilometre")
@Schema(description = "Kilometre")
KILOMETRE("Kilometre"),
/** Yard */
- @XmlEnumValue("Yard")
+ @SerializedName("Yard")
@Schema(description = "Yard")
YARD("Yard"),
/** Other unit than the previous one */
- @XmlEnumValue("Other")
+ @SerializedName("Other")
@Schema(description = "Other unit than the previous one")
OTHER("Other");
private final String value;
diff --git a/src/main/java/com/adyen/model/nexo/VersionType.java b/src/main/java/com/adyen/model/nexo/VersionType.java
index 65e92aac9..694d85354 100644
--- a/src/main/java/com/adyen/model/nexo/VersionType.java
+++ b/src/main/java/com/adyen/model/nexo/VersionType.java
@@ -1,9 +1,7 @@
package com.adyen.model.nexo;
+import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlEnumValue;
-import javax.xml.bind.annotation.XmlType;
/**
* Java class for VersionType.
@@ -23,32 +21,30 @@
* </simpleType>
*
*/
-@XmlType(name = "VersionType")
-@XmlEnum
public enum VersionType {
/** Version 0 */
- @XmlEnumValue("v0")
+ @SerializedName("v0")
V_0("v0"),
/** Version 1 */
- @XmlEnumValue("v1")
+ @SerializedName("v1")
V_1("v1"),
/** Version 2 */
- @XmlEnumValue("v2")
+ @SerializedName("v2")
V_2("v2"),
/** Version 3 */
- @XmlEnumValue("v3")
+ @SerializedName("v3")
V_3("v3"),
/** Version 4 */
- @XmlEnumValue("v4")
+ @SerializedName("v4")
V_4("v4"),
/** Version 5 */
- @XmlEnumValue("v5")
+ @SerializedName("v5")
V_5("v5");
private final String value;
diff --git a/src/main/java/com/adyen/terminal/serialization/AuthenticatedDataTypeAdapterFactory.java b/src/main/java/com/adyen/terminal/serialization/AuthenticatedDataTypeAdapterFactory.java
new file mode 100644
index 000000000..99fe8a97f
--- /dev/null
+++ b/src/main/java/com/adyen/terminal/serialization/AuthenticatedDataTypeAdapterFactory.java
@@ -0,0 +1,91 @@
+/*
+ * ######
+ * ######
+ * ############ ####( ###### #####. ###### ############ ############
+ * ############# #####( ###### #####. ###### ############# #############
+ * ###### #####( ###### #####. ###### ##### ###### ##### ######
+ * ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
+ * ###### ###### #####( ###### #####. ###### ##### ##### ######
+ * ############# ############# ############# ############# ##### ######
+ * ############ ############ ############# ############ ##### ######
+ * ######
+ * #############
+ * ############
+ *
+ * Adyen Java API Library
+ *
+ * Copyright (c) 2026 Adyen B.V.
+ * This file is open source and available under the MIT license.
+ * See the LICENSE file for more info.
+ */
+
+package com.adyen.terminal.serialization;
+
+import com.adyen.model.nexo.AuthenticatedData;
+import com.adyen.model.nexo.KEK;
+import com.adyen.model.nexo.KeyTransport;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Gson {@link TypeAdapterFactory} for {@link com.adyen.model.nexo.AuthenticatedData}.
+ *
+ * Handles polymorphic deserialization of the {@code keyTransportOrKEK} list by inspecting each
+ * element's fields: items containing {@code KEKIdentifier} are deserialized as {@link
+ * com.adyen.model.nexo.KEK}, all others as {@link com.adyen.model.nexo.KeyTransport}. Serialization
+ * is delegated to Gson's default behavior.
+ */
+public class AuthenticatedDataTypeAdapterFactory implements TypeAdapterFactory {
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ if (!AuthenticatedData.class.isAssignableFrom(type.getRawType())) {
+ return null;
+ }
+
+ TypeAdapter delegate =
+ gson.getDelegateAdapter(this, TypeToken.get(AuthenticatedData.class));
+ TypeAdapter jsonObjectAdapter = gson.getAdapter(JsonObject.class);
+
+ return (TypeAdapter)
+ new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, AuthenticatedData value) throws IOException {
+ delegate.write(out, value);
+ }
+
+ @Override
+ public AuthenticatedData read(JsonReader in) throws IOException {
+ JsonObject jsonObject = jsonObjectAdapter.read(in);
+
+ JsonArray keyTransportOrKEKArray = null;
+ if (jsonObject.has("keyTransportOrKEK")) {
+ keyTransportOrKEKArray = jsonObject.getAsJsonArray("keyTransportOrKEK");
+ jsonObject.remove("keyTransportOrKEK");
+ }
+ AuthenticatedData result = delegate.fromJsonTree(jsonObject);
+ if (keyTransportOrKEKArray != null) {
+ for (JsonElement element : keyTransportOrKEKArray) {
+ JsonObject item = element.getAsJsonObject();
+ if (item.has("KEKIdentifier")) {
+ result.getKeyTransportOrKEK().add(gson.fromJson(item, KEK.class));
+ } else {
+ result.getKeyTransportOrKEK().add(gson.fromJson(item, KeyTransport.class));
+ }
+ }
+ }
+ return result;
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/adyen/terminal/serialization/TerminalAPIGsonBuilder.java b/src/main/java/com/adyen/terminal/serialization/TerminalAPIGsonBuilder.java
index d09e77bcf..e460558b4 100644
--- a/src/main/java/com/adyen/terminal/serialization/TerminalAPIGsonBuilder.java
+++ b/src/main/java/com/adyen/terminal/serialization/TerminalAPIGsonBuilder.java
@@ -21,41 +21,26 @@
package com.adyen.terminal.serialization;
-import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
+
import javax.xml.datatype.XMLGregorianCalendar;
public final class TerminalAPIGsonBuilder {
- private TerminalAPIGsonBuilder() {}
+ private TerminalAPIGsonBuilder() {
+ // not meant to be initialized
+ }
- public static Gson create() {
- GsonBuilder gsonBuilder = new GsonBuilder();
- FieldNamingStrategy fieldNamingStrategy =
- field -> {
- if (field.getAnnotation(XmlElement.class) != null) {
- XmlElement xmlElement = field.getAnnotation(XmlElement.class);
- return xmlElement.name();
- } else if (field.getAnnotation(XmlAttribute.class) != null) {
- XmlAttribute xmlAttribute = field.getAnnotation(XmlAttribute.class);
- return xmlAttribute.name();
- } else if (field.getAnnotation(XmlRootElement.class) != null) {
- XmlRootElement xmlRootElement = field.getAnnotation(XmlRootElement.class);
- return xmlRootElement.name();
- }
- return field.getName();
- };
- gsonBuilder.setFieldNamingStrategy(fieldNamingStrategy);
- gsonBuilder.registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter());
- gsonBuilder.registerTypeHierarchyAdapter(
- XMLGregorianCalendar.class, new XMLGregorianCalendarTypeAdapter());
- gsonBuilder.registerTypeAdapterFactory(new XMLEnumAdapterFactory());
- gsonBuilder.disableHtmlEscaping();
+ public static Gson create() {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ gsonBuilder.registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter());
+ gsonBuilder.registerTypeHierarchyAdapter(
+ XMLGregorianCalendar.class, new XMLGregorianCalendarTypeAdapter());
+ gsonBuilder.registerTypeAdapterFactory(new XMLEnumAdapterFactory());
+ gsonBuilder.registerTypeAdapterFactory(new AuthenticatedDataTypeAdapterFactory());
+ gsonBuilder.disableHtmlEscaping();
- return gsonBuilder.create();
- }
+ return gsonBuilder.create();
+ }
}
diff --git a/src/main/java/com/adyen/terminal/serialization/XMLEnumTypeAdapter.java b/src/main/java/com/adyen/terminal/serialization/XMLEnumTypeAdapter.java
index 5645a6ace..ce11f0a21 100644
--- a/src/main/java/com/adyen/terminal/serialization/XMLEnumTypeAdapter.java
+++ b/src/main/java/com/adyen/terminal/serialization/XMLEnumTypeAdapter.java
@@ -22,15 +22,15 @@
package com.adyen.terminal.serialization;
import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.SerializedName;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Method;
-import javax.xml.bind.annotation.XmlEnumValue;
/**
- * Serializes and deserializes enums that use {@link XmlEnumValue} annotations for GSON.
+ * Serializes and deserializes enums that use annotations for GSON.
*
* @param The enum type.
*/
@@ -48,7 +48,7 @@ public XMLEnumTypeAdapter(Class clazz) {
}
/**
- * Writes the enum value to JSON, using the {@link XmlEnumValue} if present.
+ * Writes the enum value to JSON, using the {@link SerializedName} if present.
*
* @param out the GSON {@link JsonWriter} to write to.
* @param value the enum constant to write to JSON.
@@ -62,11 +62,11 @@ public void write(JsonWriter out, T value) throws IOException {
Enum enumValue = (Enum) value;
try {
- if (enumValue.getClass().getField(enumValue.name()).getAnnotation(XmlEnumValue.class)
+ if (enumValue.getClass().getField(enumValue.name()).getAnnotation(SerializedName.class)
!= null) {
- XmlEnumValue xmlEnumValue =
- enumValue.getClass().getField(enumValue.name()).getAnnotation(XmlEnumValue.class);
- out.value(xmlEnumValue.value());
+ SerializedName serializedName =
+ enumValue.getClass().getField(enumValue.name()).getAnnotation(SerializedName.class);
+ out.value(serializedName.value());
} else {
out.value(enumValue.name());
}
diff --git a/src/test/java/com/adyen/model/nexo/AbortRequestTest.java b/src/test/java/com/adyen/model/nexo/AbortRequestTest.java
index 6e4323997..db1074b67 100644
--- a/src/test/java/com/adyen/model/nexo/AbortRequestTest.java
+++ b/src/test/java/com/adyen/model/nexo/AbortRequestTest.java
@@ -1,6 +1,8 @@
package com.adyen.model.nexo;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
import com.google.gson.Gson;
@@ -66,4 +68,48 @@ public void testShouldSerializeAndDeserializeFromMockFile() throws IOException {
deserializedAbortRequest.getDisplayOutput().getInfoQualify(),
roundTripAbortRequest.getDisplayOutput().getInfoQualify());
}
+
+ @Test
+ public void testDeserializationWithMissingRequiredFields() throws IOException {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ // JSON missing MessageReference (required field)
+ String jsonMissingMessageReference =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/abort-request-missing-message-reference.json");
+
+ AbortRequest result1 =
+ terminalApiGson.fromJson(jsonMissingMessageReference, AbortRequest.class);
+ assertNotNull(result1, "Deserialization should succeed even with missing required field");
+ assertNull(
+ result1.getMessageReference(), "MessageReference should be null when missing from JSON");
+ assertNotNull(result1.getAbortReason(), "AbortReason should be present");
+
+ // JSON missing AbortReason (required field)
+ String jsonMissingAbortReason =
+ NexoTestUtils.readResource("mocks/terminal-api/abort-request-missing-abort-reason.json");
+
+ AbortRequest result2 = terminalApiGson.fromJson(jsonMissingAbortReason, AbortRequest.class);
+ assertNotNull(result2, "Deserialization should succeed even with missing required field");
+ assertNotNull(result2.getMessageReference(), "MessageReference should be present");
+ assertNull(result2.getAbortReason(), "AbortReason should be null when missing from JSON");
+ }
+
+ @Test
+ public void testSerializationWithNullRequiredFields() {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ AbortRequest request = new AbortRequest();
+ request.setAbortReason("cancelled-by-shopper");
+ // Not setting MessageReference (required field)
+
+ String serialized = terminalApiGson.toJson(request);
+ assertNotNull(serialized, "Serialization should succeed even with null required field");
+
+ // Verify it can be deserialized back
+ AbortRequest deserialized = terminalApiGson.fromJson(serialized, AbortRequest.class);
+ assertNotNull(deserialized);
+ assertNull(
+ deserialized.getMessageReference(), "MessageReference should remain null after round-trip");
+ }
}
diff --git a/src/test/java/com/adyen/model/nexo/AuthenticatedDataTest.java b/src/test/java/com/adyen/model/nexo/AuthenticatedDataTest.java
new file mode 100644
index 000000000..85c3f15a1
--- /dev/null
+++ b/src/test/java/com/adyen/model/nexo/AuthenticatedDataTest.java
@@ -0,0 +1,158 @@
+package com.adyen.model.nexo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+public class AuthenticatedDataTest {
+
+ @Test
+ public void testShouldSerializeAndDeserializeKeyTransportFromMockFile() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource("mocks/terminal-api/authenticated-data-key-transport.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ AuthenticatedData deserializedAuthenticatedData =
+ terminalApiGson.fromJson(mockJson, AuthenticatedData.class);
+ assertEquals(1, deserializedAuthenticatedData.getKeyTransportOrKEK().size());
+ assertInstanceOf(
+ KeyTransport.class,
+ deserializedAuthenticatedData.getKeyTransportOrKEK().get(0),
+ "Should deserialize as KeyTransport");
+
+ KeyTransport keyTransport =
+ (KeyTransport) deserializedAuthenticatedData.getKeyTransportOrKEK().get(0);
+ assertNotNull(keyTransport.getRecipientIdentifier());
+ assertEquals(AlgorithmType.RSA_ENCRYPTION, keyTransport.getKeyEncryptionAlgorithm().getAlgorithm());
+
+ String serializedJson = terminalApiGson.toJson(deserializedAuthenticatedData);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testShouldSerializeAndDeserializeKekFromMockFile() throws IOException {
+ String mockJson = NexoTestUtils.readResource("mocks/terminal-api/authenticated-data-kek.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ AuthenticatedData deserializedAuthenticatedData =
+ terminalApiGson.fromJson(mockJson, AuthenticatedData.class);
+ assertEquals(1, deserializedAuthenticatedData.getKeyTransportOrKEK().size());
+ assertInstanceOf(
+ KEK.class,
+ deserializedAuthenticatedData.getKeyTransportOrKEK().get(0),
+ "Should deserialize as KEK");
+
+ KEK kek = (KEK) deserializedAuthenticatedData.getKeyTransportOrKEK().get(0);
+ assertNotNull(kek.getKEKIdentifier());
+ assertEquals("kid-1", kek.getKEKIdentifier().getKeyIdentifier());
+ assertEquals(AlgorithmType.DES_EDE_3_CBC, kek.getKeyEncryptionAlgorithm().getAlgorithm());
+
+ String serializedJson = terminalApiGson.toJson(deserializedAuthenticatedData);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testDeserializationWithMissingRequiredFields() throws IOException {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ // JSON missing MACAlgorithm (required field)
+ String jsonMissingMacAlgorithm =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/authenticated-data-missing-mac-algorithm.json");
+ AuthenticatedData result1 =
+ terminalApiGson.fromJson(jsonMissingMacAlgorithm, AuthenticatedData.class);
+ assertNotNull(result1, "Deserialization should succeed even with missing required field");
+ assertNull(result1.getMACAlgorithm(), "MACAlgorithm should be null when missing from JSON");
+ assertNotNull(result1.getEncapsulatedContent(), "EncapsulatedContent should be present");
+ assertNotNull(result1.getMAC(), "MAC should be present");
+
+ // JSON missing EncapsulatedContent (required field)
+ String jsonMissingEncapsulatedContent =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/authenticated-data-missing-encapsulated-content.json");
+ AuthenticatedData result2 =
+ terminalApiGson.fromJson(jsonMissingEncapsulatedContent, AuthenticatedData.class);
+ assertNotNull(result2, "Deserialization should succeed even with missing required field");
+ assertNotNull(result2.getMACAlgorithm(), "MACAlgorithm should be present");
+ assertNull(
+ result2.getEncapsulatedContent(),
+ "EncapsulatedContent should be null when missing from JSON");
+ assertNotNull(result2.getMAC(), "MAC should be present");
+
+ // JSON missing MAC (required field)
+ String jsonMissingMac =
+ NexoTestUtils.readResource("mocks/terminal-api/authenticated-data-missing-mac.json");
+ AuthenticatedData result3 = terminalApiGson.fromJson(jsonMissingMac, AuthenticatedData.class);
+ assertNotNull(result3, "Deserialization should succeed even with missing required field");
+ assertNotNull(result3.getMACAlgorithm(), "MACAlgorithm should be present");
+ assertNotNull(result3.getEncapsulatedContent(), "EncapsulatedContent should be present");
+ assertNull(result3.getMAC(), "MAC should be null when missing from JSON");
+ }
+
+ @Test
+ public void testSerializationWithNullRequiredFields() {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ AuthenticatedData authenticatedData = new AuthenticatedData();
+ authenticatedData.setVersion(VersionType.V_0);
+ // Not setting MACAlgorithm, EncapsulatedContent, MAC (required fields)
+
+ String serialized = terminalApiGson.toJson(authenticatedData);
+ assertNotNull(serialized, "Serialization should succeed even with null required fields");
+
+ AuthenticatedData deserialized = terminalApiGson.fromJson(serialized, AuthenticatedData.class);
+ assertNotNull(deserialized);
+ assertNull(deserialized.getMACAlgorithm(), "MACAlgorithm should remain null after round-trip");
+ assertNull(
+ deserialized.getEncapsulatedContent(),
+ "EncapsulatedContent should remain null after round-trip");
+ assertNull(deserialized.getMAC(), "MAC should remain null after round-trip");
+ }
+
+ @Test
+ public void testDeserializationIsCaseSensitive() throws IOException {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ String wrongCaseJson =
+ NexoTestUtils.readResource("mocks/terminal-api/authenticated-data-wrong-case.json");
+
+ AuthenticatedData result = terminalApiGson.fromJson(wrongCaseJson, AuthenticatedData.class);
+ assertNotNull(result, "Deserialization should succeed but fields with wrong casing should not be mapped");
+ assertNull(result.getMACAlgorithm(), "MACAlgorithm should be null when JSON key has wrong casing");
+ assertNull(result.getEncapsulatedContent(), "EncapsulatedContent should be null when JSON key has wrong casing");
+ assertNull(result.getMAC(), "MAC should be null when JSON key has wrong casing");
+ assertTrue(result.getKeyTransportOrKEK().isEmpty(), "KeyTransportOrKEK should be null when JSON key has wrong casing");
+ assertEquals(VersionType.V_0, result.getVersion(), "Version should fall back to default v0 when JSON key has wrong casing");
+ }
+
+ @Test
+ public void testDeserializationWithMissingNonRequiredField() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource("mocks/terminal-api/authenticated-data-missing-version.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ AuthenticatedData deserializedAuthenticatedData =
+ terminalApiGson.fromJson(mockJson, AuthenticatedData.class);
+ assertNotNull(deserializedAuthenticatedData);
+ assertEquals(VersionType.V_0, deserializedAuthenticatedData.getVersion());
+ assertEquals(1, deserializedAuthenticatedData.getKeyTransportOrKEK().size());
+
+ String serializedJson = terminalApiGson.toJson(deserializedAuthenticatedData);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+}
diff --git a/src/test/java/com/adyen/model/nexo/EventNotificationTest.java b/src/test/java/com/adyen/model/nexo/EventNotificationTest.java
new file mode 100644
index 000000000..bc39ff4ff
--- /dev/null
+++ b/src/test/java/com/adyen/model/nexo/EventNotificationTest.java
@@ -0,0 +1,110 @@
+package com.adyen.model.nexo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+public class EventNotificationTest {
+
+ @Test
+ public void testShouldSerializeAndDeserializeFromMockFile() throws IOException {
+ String mockJson = NexoTestUtils.readResource("mocks/terminal-api/event-notification.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ EventNotification deserializedEventNotification =
+ terminalApiGson.fromJson(mockJson, EventNotification.class);
+ assertEquals("2026-03-27T10:30:00Z", deserializedEventNotification.getTimeStamp().toString());
+ assertEquals(
+ EventToNotifyType.BEGIN_MAINTENANCE, deserializedEventNotification.getEventToNotify());
+ assertEquals("Maintenance window started", deserializedEventNotification.getEventDetails());
+ assertTrue(deserializedEventNotification.isMaintenanceRequiredFlag());
+ assertEquals("en", deserializedEventNotification.getCustomerLanguage());
+ assertNotNull(deserializedEventNotification.getDisplayOutput());
+ assertEquals(
+ DeviceType.CUSTOMER_DISPLAY, deserializedEventNotification.getDisplayOutput().getDevice());
+ assertEquals(
+ InfoQualifyType.DISPLAY, deserializedEventNotification.getDisplayOutput().getInfoQualify());
+ assertEquals(
+ "System maintenance in progress",
+ deserializedEventNotification
+ .getDisplayOutput()
+ .getOutputContent()
+ .getOutputText()
+ .get(0)
+ .getText());
+
+ String serializedJson = terminalApiGson.toJson(deserializedEventNotification);
+
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+
+ EventNotification roundTripEventNotification =
+ terminalApiGson.fromJson(serializedJson, EventNotification.class);
+ assertEquals(
+ deserializedEventNotification.getTimeStamp().toString(),
+ roundTripEventNotification.getTimeStamp().toString());
+ assertEquals(
+ deserializedEventNotification.getEventToNotify(),
+ roundTripEventNotification.getEventToNotify());
+ assertEquals(
+ deserializedEventNotification.getEventDetails(),
+ roundTripEventNotification.getEventDetails());
+ assertEquals(
+ deserializedEventNotification.isMaintenanceRequiredFlag(),
+ roundTripEventNotification.isMaintenanceRequiredFlag());
+ }
+
+ @Test
+ public void testDeserializationWithMissingRequiredFields() throws IOException {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ // JSON missing TimeStamp (required field)
+ String jsonMissingTimeStamp =
+ NexoTestUtils.readResource("mocks/terminal-api/event-notification-missing-timestamp.json");
+
+ EventNotification result1 =
+ terminalApiGson.fromJson(jsonMissingTimeStamp, EventNotification.class);
+ assertNotNull(result1, "Deserialization should succeed even with missing required field");
+ assertNull(result1.getTimeStamp(), "TimeStamp should be null when missing from JSON");
+ assertNotNull(result1.getEventToNotify(), "EventToNotify should be present");
+
+ // JSON missing EventToNotify (required field)
+ String jsonMissingEventToNotify =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/event-notification-missing-event-to-notify.json");
+
+ EventNotification result2 =
+ terminalApiGson.fromJson(jsonMissingEventToNotify, EventNotification.class);
+ assertNotNull(result2, "Deserialization should succeed even with missing required field");
+ assertNotNull(result2.getTimeStamp(), "TimeStamp should be present");
+ assertNull(result2.getEventToNotify(), "EventToNotify should be null when missing from JSON");
+ }
+
+ @Test
+ public void testSerializationWithNullRequiredFields() {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ EventNotification notification = new EventNotification();
+ notification.setEventDetails("Test event");
+ // Not setting TimeStamp and EventToNotify (required fields)
+
+ String serialized = terminalApiGson.toJson(notification);
+ assertNotNull(serialized, "Serialization should succeed even with null required fields");
+
+ // Verify it can be deserialized back
+ EventNotification deserialized = terminalApiGson.fromJson(serialized, EventNotification.class);
+ assertNotNull(deserialized);
+ assertNull(deserialized.getTimeStamp(), "TimeStamp should remain null after round-trip");
+ assertNull(
+ deserialized.getEventToNotify(), "EventToNotify should remain null after round-trip");
+ }
+}
diff --git a/src/test/java/com/adyen/model/nexo/SaleTerminalDataTest.java b/src/test/java/com/adyen/model/nexo/SaleTerminalDataTest.java
new file mode 100644
index 000000000..c22338cef
--- /dev/null
+++ b/src/test/java/com/adyen/model/nexo/SaleTerminalDataTest.java
@@ -0,0 +1,55 @@
+package com.adyen.model.nexo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+public class SaleTerminalDataTest {
+
+ @Test
+ public void testShouldSerializeAndDeserializeListField() throws IOException {
+ String mockJson = NexoTestUtils.readResource("mocks/terminal-api/sale-terminal-data.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleTerminalData deserialized = terminalApiGson.fromJson(mockJson, SaleTerminalData.class);
+ assertNotNull(deserialized.getSaleCapabilities());
+ assertEquals(3, deserialized.getSaleCapabilities().size());
+ assertEquals(SaleCapabilitiesType.CASHIER_STATUS, deserialized.getSaleCapabilities().get(0));
+ assertEquals(SaleCapabilitiesType.CASHIER_DISPLAY, deserialized.getSaleCapabilities().get(1));
+ assertEquals(SaleCapabilitiesType.CUSTOMER_DISPLAY, deserialized.getSaleCapabilities().get(2));
+ assertNotNull(deserialized.getSaleProfile());
+ assertEquals(TerminalEnvironmentType.ATTENDED, deserialized.getTerminalEnvironment());
+ assertEquals("group-1", deserialized.getTotalsGroupID());
+
+ String serializedJson = terminalApiGson.toJson(deserialized);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testShouldSerializeAndDeserializeEmptyList() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource("mocks/terminal-api/sale-terminal-data-empty-capabilities.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleTerminalData deserialized = terminalApiGson.fromJson(mockJson, SaleTerminalData.class);
+ assertNotNull(deserialized.getSaleCapabilities());
+ assertEquals(0, deserialized.getSaleCapabilities().size());
+ assertEquals(TerminalEnvironmentType.ATTENDED, deserialized.getTerminalEnvironment());
+ assertNull(deserialized.getSaleProfile());
+ assertNull(deserialized.getTotalsGroupID());
+
+ String serializedJson = terminalApiGson.toJson(deserialized);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+}
diff --git a/src/test/java/com/adyen/model/nexo/SaleToPOIResponseTest.java b/src/test/java/com/adyen/model/nexo/SaleToPOIResponseTest.java
new file mode 100644
index 000000000..33dd0849d
--- /dev/null
+++ b/src/test/java/com/adyen/model/nexo/SaleToPOIResponseTest.java
@@ -0,0 +1,130 @@
+package com.adyen.model.nexo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+public class SaleToPOIResponseTest {
+
+ @Test
+ public void testShouldDeserializeWithDiagnosisResponse() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource("mocks/terminal-api/sale-to-poi-response-diagnosis.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleToPOIResponse deserialized = terminalApiGson.fromJson(mockJson, SaleToPOIResponse.class);
+ assertNotNull(deserialized.getMessageHeader());
+ assertEquals("POSSystemID12345", deserialized.getMessageHeader().getSaleID());
+ assertEquals(
+ MessageCategoryType.DIAGNOSIS, deserialized.getMessageHeader().getMessageCategory());
+ assertEquals(MessageType.RESPONSE, deserialized.getMessageHeader().getMessageType());
+ assertNotNull(deserialized.getDiagnosisResponse());
+ assertEquals(ResultType.SUCCESS, deserialized.getDiagnosisResponse().getResponse().getResult());
+ assertEquals(2, deserialized.getDiagnosisResponse().getLoggedSaleID().size());
+ assertEquals("sale-1", deserialized.getDiagnosisResponse().getLoggedSaleID().get(0));
+ assertNotNull(deserialized.getDiagnosisResponse().getPOIStatus());
+ assertNull(deserialized.getPaymentResponse());
+ assertNull(deserialized.getDisplayResponse());
+ assertNull(deserialized.getSecurityTrailer());
+
+ String serializedJson = terminalApiGson.toJson(deserialized);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testShouldDeserializeWithDisplayResponse() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource("mocks/terminal-api/sale-to-poi-response-display.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleToPOIResponse deserialized = terminalApiGson.fromJson(mockJson, SaleToPOIResponse.class);
+ assertNotNull(deserialized.getMessageHeader());
+ assertEquals(MessageCategoryType.DISPLAY, deserialized.getMessageHeader().getMessageCategory());
+ assertNotNull(deserialized.getDisplayResponse());
+ assertEquals(1, deserialized.getDisplayResponse().getOutputResult().size());
+ assertEquals(
+ DeviceType.CASHIER_DISPLAY,
+ deserialized.getDisplayResponse().getOutputResult().get(0).getDevice());
+ assertEquals(
+ ResultType.SUCCESS,
+ deserialized.getDisplayResponse().getOutputResult().get(0).getResponse().getResult());
+ assertNull(deserialized.getDiagnosisResponse());
+ assertNull(deserialized.getPaymentResponse());
+
+ String serializedJson = terminalApiGson.toJson(deserialized);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testShouldDeserializeWithTransactionStatusResponse() throws IOException {
+ String mockJson =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/sale-to-poi-response-transaction-status.json");
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleToPOIResponse deserialized = terminalApiGson.fromJson(mockJson, SaleToPOIResponse.class);
+ assertNotNull(deserialized.getMessageHeader());
+ assertEquals(
+ MessageCategoryType.TRANSACTION_STATUS,
+ deserialized.getMessageHeader().getMessageCategory());
+ assertNotNull(deserialized.getTransactionStatusResponse());
+ assertEquals(
+ ResultType.SUCCESS, deserialized.getTransactionStatusResponse().getResponse().getResult());
+ assertNotNull(deserialized.getTransactionStatusResponse().getMessageReference());
+ assertEquals(
+ MessageCategoryType.PAYMENT,
+ deserialized.getTransactionStatusResponse().getMessageReference().getMessageCategory());
+ assertNull(deserialized.getDiagnosisResponse());
+ assertNull(deserialized.getPaymentResponse());
+
+ String serializedJson = terminalApiGson.toJson(deserialized);
+ JsonObject expectedJsonObject = JsonParser.parseString(mockJson).getAsJsonObject();
+ JsonObject serializedJsonObject = JsonParser.parseString(serializedJson).getAsJsonObject();
+ assertEquals(expectedJsonObject, serializedJsonObject);
+ }
+
+ @Test
+ public void testDeserializationWithMissingRequiredField() throws IOException {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ // JSON missing MessageHeader (required field)
+ String jsonMissingHeader =
+ NexoTestUtils.readResource(
+ "mocks/terminal-api/sale-to-poi-response-missing-message-header.json");
+ SaleToPOIResponse result = terminalApiGson.fromJson(jsonMissingHeader, SaleToPOIResponse.class);
+ assertNotNull(result, "Deserialization should succeed even with missing required field");
+ assertNull(result.getMessageHeader(), "MessageHeader should be null when missing from JSON");
+ assertNotNull(result.getDiagnosisResponse(), "DiagnosisResponse should be present");
+ }
+
+ @Test
+ public void testSerializationWithNullRequiredField() {
+ Gson terminalApiGson = TerminalAPIGsonBuilder.create();
+
+ SaleToPOIResponse response = new SaleToPOIResponse();
+ response.setDiagnosisResponse(new DiagnosisResponse());
+ // Not setting MessageHeader (required field)
+
+ String serialized = terminalApiGson.toJson(response);
+ assertNotNull(serialized, "Serialization should succeed even with null required field");
+
+ SaleToPOIResponse deserialized = terminalApiGson.fromJson(serialized, SaleToPOIResponse.class);
+ assertNotNull(deserialized);
+ assertNull(
+ deserialized.getMessageHeader(), "MessageHeader should remain null after round-trip");
+ assertNotNull(
+ deserialized.getDiagnosisResponse(),
+ "DiagnosisResponse should remain present after round-trip");
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/abort-request-missing-abort-reason.json b/src/test/resources/mocks/terminal-api/abort-request-missing-abort-reason.json
new file mode 100644
index 000000000..8a8181e84
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/abort-request-missing-abort-reason.json
@@ -0,0 +1,8 @@
+{
+ "MessageReference": {
+ "MessageCategory": "Payment",
+ "ServiceID": "service-id",
+ "SaleID": "sale-id",
+ "POIID": "poi-id"
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/abort-request-missing-message-reference.json b/src/test/resources/mocks/terminal-api/abort-request-missing-message-reference.json
new file mode 100644
index 000000000..18c0f170c
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/abort-request-missing-message-reference.json
@@ -0,0 +1,7 @@
+{
+ "AbortReason": "cancelled-by-shopper",
+ "DisplayOutput": {
+ "Device": "CustomerDisplay",
+ "InfoQualify": "Error"
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-kek.json b/src/test/resources/mocks/terminal-api/authenticated-data-kek.json
new file mode 100644
index 000000000..fe75fb71a
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-kek.json
@@ -0,0 +1,25 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "KEKIdentifier": {
+ "KeyIdentifier": "kid-1",
+ "KeyVersion": "kv1",
+ "DerivationIdentifier": "ZGVyaXZhdGlvbg=="
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "des-ede3-cbc"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ==",
+ "Version": "v4"
+ }
+ ],
+ "MACAlgorithm": {
+ "Algorithm": "id-sha256"
+ },
+ "EncapsulatedContent": {
+ "Content": "cGF5bG9hZA==",
+ "ContentType": "id-data"
+ },
+ "Version": "v0",
+ "MAC": "bWFjLXZhbHVl"
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-key-transport.json b/src/test/resources/mocks/terminal-api/authenticated-data-key-transport.json
new file mode 100644
index 000000000..8ab201a66
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-key-transport.json
@@ -0,0 +1,28 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "RecipientIdentifier": {
+ "IssuerAndSerialNumber": {
+ "Issuer": {
+ "RelativeDistinguishedName": []
+ },
+ "SerialNumber": 123
+ }
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "rsaEncryption"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ==",
+ "Version": "v0"
+ }
+ ],
+ "MACAlgorithm": {
+ "Algorithm": "id-sha256"
+ },
+ "EncapsulatedContent": {
+ "Content": "cGF5bG9hZA==",
+ "ContentType": "id-data"
+ },
+ "Version": "v0",
+ "MAC": "bWFjLXZhbHVl"
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-missing-encapsulated-content.json b/src/test/resources/mocks/terminal-api/authenticated-data-missing-encapsulated-content.json
new file mode 100644
index 000000000..2cb488d2b
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-missing-encapsulated-content.json
@@ -0,0 +1,18 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "KEKIdentifier": {
+ "KeyIdentifier": "kid-1",
+ "KeyVersion": "kv1"
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "des-ede3-cbc"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ=="
+ }
+ ],
+ "MACAlgorithm": {
+ "Algorithm": "id-sha256"
+ },
+ "MAC": "bWFjLXZhbHVl"
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac-algorithm.json b/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac-algorithm.json
new file mode 100644
index 000000000..059de2589
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac-algorithm.json
@@ -0,0 +1,19 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "KEKIdentifier": {
+ "KeyIdentifier": "kid-1",
+ "KeyVersion": "kv1"
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "des-ede3-cbc"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ=="
+ }
+ ],
+ "EncapsulatedContent": {
+ "Content": "cGF5bG9hZA==",
+ "ContentType": "id-data"
+ },
+ "MAC": "bWFjLXZhbHVl"
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac.json b/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac.json
new file mode 100644
index 000000000..90dfad397
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-missing-mac.json
@@ -0,0 +1,21 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "KEKIdentifier": {
+ "KeyIdentifier": "kid-1",
+ "KeyVersion": "kv1"
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "des-ede3-cbc"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ=="
+ }
+ ],
+ "MACAlgorithm": {
+ "Algorithm": "id-sha256"
+ },
+ "EncapsulatedContent": {
+ "Content": "cGF5bG9hZA==",
+ "ContentType": "id-data"
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-missing-version.json b/src/test/resources/mocks/terminal-api/authenticated-data-missing-version.json
new file mode 100644
index 000000000..eaba242f4
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-missing-version.json
@@ -0,0 +1,22 @@
+{
+ "keyTransportOrKEK": [
+ {
+ "KEKIdentifier": {
+ "KeyIdentifier": "kid-1",
+ "KeyVersion": "kv1"
+ },
+ "KeyEncryptionAlgorithm": {
+ "Algorithm": "des-ede3-cbc"
+ },
+ "EncryptedKey": "ZW5jcnlwdGVkLWtleQ=="
+ }
+ ],
+ "MACAlgorithm": {
+ "Algorithm": "id-sha256"
+ },
+ "EncapsulatedContent": {
+ "Content": "cGF5bG9hZA==",
+ "ContentType": "id-data"
+ },
+ "MAC": "bWFjLXZhbHVl"
+}
diff --git a/src/test/resources/mocks/terminal-api/authenticated-data-wrong-case.json b/src/test/resources/mocks/terminal-api/authenticated-data-wrong-case.json
new file mode 100644
index 000000000..ec5a55e67
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/authenticated-data-wrong-case.json
@@ -0,0 +1,7 @@
+{
+ "macalgorithm": {"Algorithm": "id-sha256"},
+ "encapsulatedcontent": {"Content": "cGF5bG9hZA==", "ContentType": "id-data"},
+ "version": "v0",
+ "mac": "bWFjLXZhbHVl",
+ "KeyTransportOrKEK": []
+}
diff --git a/src/test/resources/mocks/terminal-api/event-notification-missing-event-to-notify.json b/src/test/resources/mocks/terminal-api/event-notification-missing-event-to-notify.json
new file mode 100644
index 000000000..a8aa44f6b
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/event-notification-missing-event-to-notify.json
@@ -0,0 +1,4 @@
+{
+ "TimeStamp": "2026-03-27T10:30:00Z",
+ "EventDetails": "Maintenance window started"
+}
diff --git a/src/test/resources/mocks/terminal-api/event-notification-missing-timestamp.json b/src/test/resources/mocks/terminal-api/event-notification-missing-timestamp.json
new file mode 100644
index 000000000..34495b91b
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/event-notification-missing-timestamp.json
@@ -0,0 +1,4 @@
+{
+ "EventToNotify": "BeginMaintenance",
+ "EventDetails": "Maintenance window started"
+}
diff --git a/src/test/resources/mocks/terminal-api/event-notification.json b/src/test/resources/mocks/terminal-api/event-notification.json
new file mode 100644
index 000000000..ceb244307
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/event-notification.json
@@ -0,0 +1,19 @@
+{
+ "TimeStamp": "2026-03-27T10:30:00Z",
+ "EventToNotify": "BeginMaintenance",
+ "EventDetails": "Maintenance window started",
+ "MaintenanceRequiredFlag": true,
+ "CustomerLanguage": "en",
+ "DisplayOutput": {
+ "Device": "CustomerDisplay",
+ "InfoQualify": "Display",
+ "OutputContent": {
+ "OutputFormat": "Text",
+ "OutputText": [
+ {
+ "Text": "System maintenance in progress"
+ }
+ ]
+ }
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-terminal-data-empty-capabilities.json b/src/test/resources/mocks/terminal-api/sale-terminal-data-empty-capabilities.json
new file mode 100644
index 000000000..6fdd25ba5
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-terminal-data-empty-capabilities.json
@@ -0,0 +1,4 @@
+{
+ "SaleCapabilities": [],
+ "TerminalEnvironment": "Attended"
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-terminal-data.json b/src/test/resources/mocks/terminal-api/sale-terminal-data.json
new file mode 100644
index 000000000..dbf710c2a
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-terminal-data.json
@@ -0,0 +1,8 @@
+{
+ "SaleCapabilities": ["CashierStatus", "CashierDisplay", "CustomerDisplay"],
+ "SaleProfile": {
+ "GenericProfile": "Extended"
+ },
+ "TerminalEnvironment": "Attended",
+ "TotalsGroupID": "group-1"
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-to-poi-response-diagnosis.json b/src/test/resources/mocks/terminal-api/sale-to-poi-response-diagnosis.json
new file mode 100644
index 000000000..13b1cf0a2
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-to-poi-response-diagnosis.json
@@ -0,0 +1,20 @@
+{
+ "MessageHeader": {
+ "ProtocolVersion": "3.0",
+ "SaleID": "POSSystemID12345",
+ "MessageClass": "Service",
+ "MessageCategory": "Diagnosis",
+ "ServiceID": "0207111617",
+ "POIID": "V400m-324688179",
+ "MessageType": "Response"
+ },
+ "DiagnosisResponse": {
+ "Response": {
+ "Result": "Success"
+ },
+ "LoggedSaleID": ["sale-1", "sale-2"],
+ "POIStatus": {
+ "GlobalStatus": "OK"
+ }
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-to-poi-response-display.json b/src/test/resources/mocks/terminal-api/sale-to-poi-response-display.json
new file mode 100644
index 000000000..d743ee12b
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-to-poi-response-display.json
@@ -0,0 +1,22 @@
+{
+ "MessageHeader": {
+ "ProtocolVersion": "3.0",
+ "SaleID": "POSSystemID12345",
+ "MessageClass": "Device",
+ "MessageCategory": "Display",
+ "ServiceID": "0207111617",
+ "POIID": "V400m-324688179",
+ "MessageType": "Response"
+ },
+ "DisplayResponse": {
+ "OutputResult": [
+ {
+ "Device": "CashierDisplay",
+ "InfoQualify": "Status",
+ "Response": {
+ "Result": "Success"
+ }
+ }
+ ]
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-to-poi-response-missing-message-header.json b/src/test/resources/mocks/terminal-api/sale-to-poi-response-missing-message-header.json
new file mode 100644
index 000000000..964d9d9fa
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-to-poi-response-missing-message-header.json
@@ -0,0 +1,7 @@
+{
+ "DiagnosisResponse": {
+ "Response": {
+ "Result": "Success"
+ }
+ }
+}
diff --git a/src/test/resources/mocks/terminal-api/sale-to-poi-response-transaction-status.json b/src/test/resources/mocks/terminal-api/sale-to-poi-response-transaction-status.json
new file mode 100644
index 000000000..e2211f9eb
--- /dev/null
+++ b/src/test/resources/mocks/terminal-api/sale-to-poi-response-transaction-status.json
@@ -0,0 +1,22 @@
+{
+ "MessageHeader": {
+ "ProtocolVersion": "3.0",
+ "SaleID": "POSSystemID12345",
+ "MessageClass": "Service",
+ "MessageCategory": "TransactionStatus",
+ "ServiceID": "0207111617",
+ "POIID": "V400m-324688179",
+ "MessageType": "Response"
+ },
+ "TransactionStatusResponse": {
+ "Response": {
+ "Result": "Success"
+ },
+ "MessageReference": {
+ "MessageCategory": "Payment",
+ "ServiceID": "service-id",
+ "SaleID": "sale-id",
+ "POIID": "poi-id"
+ }
+ }
+}