diff --git a/CHANGELOG.md b/CHANGELOG.md
index 431bf87..c8dd9d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
# Changelog
All notable changes to this project will be documented in this file.
+## [3.1.14]
+- Added `AcquirerTransactionData`
+
## [3.1.13]
- Added `terminal' field to `ChargeSubscriptionRequest` and `ReserveSubscriptionChargeRequest` in `MerchantAPI` to support charging subscription with specific terminal
diff --git a/build.gradle b/build.gradle
index 2ae8980..0790c35 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ plugins {
}
group = 'com.altapay'
-version = '3.1.13'
+version = '3.1.14'
repositories {
mavenCentral()
@@ -83,6 +83,8 @@ sonar {
property "sonar.coverage.exclusions", "**/*"
property "sonar.java.coveragePlugin", "none"
+ property "sonar.exclusions", "**/com/pensio/api/generated/**"
+
// Fail build if Quality Gate fails
property "sonar.qualitygate.wait", "true"
}
diff --git a/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionData.java b/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionData.java
new file mode 100644
index 0000000..2c5d6f6
--- /dev/null
+++ b/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionData.java
@@ -0,0 +1,73 @@
+
+package com.pensio.api.generated;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.annotation.Generated;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlType;
+
+
+/**
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
group;
+
+ /**
+ * Gets the value of the group property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the Jakarta XML Binding object.
+ * This is why there is not a set method for the group property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getGroup().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link AcquirerTransactionDataGroup }
+ *
+ *
+ */
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ public List getGroup() {
+ if (group == null) {
+ group = new ArrayList<>();
+ }
+ return this.group;
+ }
+
+}
diff --git a/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionDataGroup.java b/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionDataGroup.java
new file mode 100644
index 0000000..535480d
--- /dev/null
+++ b/build/generated/sources/xjc/java/com/pensio/api/generated/AcquirerTransactionDataGroup.java
@@ -0,0 +1,104 @@
+
+package com.pensio.api.generated;
+
+import java.util.ArrayList;
+import java.util.List;
+import jakarta.annotation.Generated;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlAttribute;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for AcquirerTransactionDataGroup complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType name="AcquirerTransactionDataGroup">
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="Entry" type="{}AcquirerTransactionDataEntry" maxOccurs="unbounded" minOccurs="0"/>
+ * </sequence>
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "AcquirerTransactionDataGroup", propOrder = {
+ "entry"
+})
+@Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+public class AcquirerTransactionDataGroup {
+
+ @XmlElement(name = "Entry")
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ protected List entry;
+ @XmlAttribute(name = "name", required = true)
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ protected String name;
+
+ /**
+ * Gets the value of the entry property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the Jakarta XML Binding object.
+ * This is why there is not a set method for the entry property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getEntry().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link AcquirerTransactionDataEntry }
+ *
+ *
+ */
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ public List getEntry() {
+ if (entry == null) {
+ entry = new ArrayList<>();
+ }
+ return this.entry;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ @Generated(value = "com.sun.tools.xjc.Driver", comments = "JAXB RI v3.0.2", date = "2026-05-21T09:05:56+02:00")
+ public void setName(String value) {
+ this.name = value;
+ }
+
+}
diff --git a/readme.md b/readme.md
index f9a118c..0359798 100644
--- a/readme.md
+++ b/readme.md
@@ -49,7 +49,7 @@ For integrating Java projects with the AltaPay gateway.
com.altapay
sdk-java
- 3.1.13
+ 3.1.14
### Gradle
diff --git a/src/main/java/com/pensio/api/PensioMerchantAPI.java b/src/main/java/com/pensio/api/PensioMerchantAPI.java
index d8da2f1..db0e207 100644
--- a/src/main/java/com/pensio/api/PensioMerchantAPI.java
+++ b/src/main/java/com/pensio/api/PensioMerchantAPI.java
@@ -665,6 +665,17 @@ private void addPaymentInfo(PaymentRequest> paymentRequest, HashMap> g
+ : paymentRequest.getAcquirerTransactionData().getAll().entrySet()) {
+ for (Map.Entry kv : g.getValue().entrySet()) {
+ addParam(params,
+ "acquirerTransactionData[" + g.getKey() + "][" + kv.getKey() + "]",
+ kv.getValue());
+ }
+ }
+ }
}
private void addAuthType(PaymentRequest> request, HashMap params)
diff --git a/src/main/java/com/pensio/api/request/AcquirerTransactionData.java b/src/main/java/com/pensio/api/request/AcquirerTransactionData.java
new file mode 100644
index 0000000..ba14405
--- /dev/null
+++ b/src/main/java/com/pensio/api/request/AcquirerTransactionData.java
@@ -0,0 +1,21 @@
+package com.pensio.api.request;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class AcquirerTransactionData {
+ private final Map> groups = new LinkedHashMap<>();
+
+ public AcquirerTransactionData add(String group, String key, String value) {
+ groups.computeIfAbsent(group, g -> new LinkedHashMap<>()).put(key, value);
+ return this;
+ }
+
+ public Map> getAll() {
+ return groups;
+ }
+
+ public boolean isEmpty() {
+ return groups.isEmpty();
+ }
+}
diff --git a/src/main/java/com/pensio/api/request/PassCard.java b/src/main/java/com/pensio/api/request/PassCard.java
new file mode 100644
index 0000000..cd5fc00
--- /dev/null
+++ b/src/main/java/com/pensio/api/request/PassCard.java
@@ -0,0 +1,9 @@
+package com.pensio.api.request;
+
+public final class PassCard {
+ private PassCard() {}
+
+ public static final String GROUP = "passcard";
+ public static final String CREDITCODE = "creditcode";
+ public static final String PAYMENTOCCURRENCE = "paymentoccurrence";
+}
diff --git a/src/main/java/com/pensio/api/request/PaymentRequest.java b/src/main/java/com/pensio/api/request/PaymentRequest.java
index 7c6b157..096b6bc 100644
--- a/src/main/java/com/pensio/api/request/PaymentRequest.java
+++ b/src/main/java/com/pensio/api/request/PaymentRequest.java
@@ -27,6 +27,7 @@ public class PaymentRequest>
protected CustomerInfo recipientInfo;
private final PaymentInfos paymentInfos;
private final List orderLines;
+ private final AcquirerTransactionData acquirerTransactionData;
/**
*
@@ -38,6 +39,7 @@ public class PaymentRequest>
{
paymentInfos = new PaymentInfos();
orderLines = new ArrayList<>();
+ acquirerTransactionData = new AcquirerTransactionData();
}
public PaymentRequest()
@@ -199,6 +201,11 @@ public PaymentInfos getPaymentInfos()
return paymentInfos;
}
+ public AcquirerTransactionData getAcquirerTransactionData()
+ {
+ return acquirerTransactionData;
+ }
+
@SuppressWarnings("unchecked")
public T addPaymentInfo(String key, String value)
{
diff --git a/src/main/resources/xsd/APIResponse.xsd b/src/main/resources/xsd/APIResponse.xsd
index 9484c98..c1d01c5 100644
--- a/src/main/resources/xsd/APIResponse.xsd
+++ b/src/main/resources/xsd/APIResponse.xsd
@@ -597,6 +597,7 @@
+
@@ -827,6 +828,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/com/pensio/api/MerchantApi_AcquirerTransactionDataEmissionTests.java b/src/test/java/com/pensio/api/MerchantApi_AcquirerTransactionDataEmissionTests.java
new file mode 100644
index 0000000..f4643fe
--- /dev/null
+++ b/src/test/java/com/pensio/api/MerchantApi_AcquirerTransactionDataEmissionTests.java
@@ -0,0 +1,57 @@
+package com.pensio.api;
+
+import com.pensio.Amount;
+import com.pensio.Currency;
+import com.pensio.api.generated.APIResponse;
+import com.pensio.api.request.PassCard;
+import com.pensio.api.request.PaymentReservationRequest;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+class MerchantApi_AcquirerTransactionDataEmissionTests {
+
+ @Test
+ void reservationEmitsAcquirerTransactionDataParams() throws PensioAPIException {
+ final var api = new PensioMerchantAPI("http://base", "user", "pass") {
+ @Override
+ protected APIResponse getAPIResponse(String method, HttpMethod httpMethod, Map requestVars) {
+ assertEquals("reservation", method);
+ assertEquals(HttpMethod.POST, httpMethod);
+ assertEquals("32", requestVars.get("acquirerTransactionData[passcard][creditcode]"));
+ assertEquals("001", requestVars.get("acquirerTransactionData[passcard][paymentoccurrence]"));
+ return null;
+ }
+ };
+
+ PaymentReservationRequest request = new PaymentReservationRequest("order123", "Terminal", Amount.get(100, Currency.DKK));
+ request.getAcquirerTransactionData()
+ .add(PassCard.GROUP, PassCard.CREDITCODE, "32")
+ .add(PassCard.GROUP, PassCard.PAYMENTOCCURRENCE, "001");
+
+ api.reservation(request);
+ }
+
+ @Test
+ void reservationOmitsAcquirerTransactionDataParamsWhenEmpty() throws PensioAPIException {
+ final var api = new PensioMerchantAPI("http://base", "user", "pass") {
+ @Override
+ protected APIResponse getAPIResponse(String method, HttpMethod httpMethod, Map requestVars) {
+ assertEquals("reservation", method);
+ assertEquals(HttpMethod.POST, httpMethod);
+ assertFalse(
+ requestVars.keySet().stream().anyMatch(k -> k.startsWith("acquirerTransactionData[")),
+ "no key starting with acquirerTransactionData[ should be emitted when empty"
+ );
+ return null;
+ }
+ };
+
+ PaymentReservationRequest request = new PaymentReservationRequest("order123", "Terminal", Amount.get(100, Currency.DKK));
+
+ api.reservation(request);
+ }
+}
diff --git a/src/test/java/com/pensio/api/PensioAPIResponseParserTest.java b/src/test/java/com/pensio/api/PensioAPIResponseParserTest.java
new file mode 100644
index 0000000..b444b33
--- /dev/null
+++ b/src/test/java/com/pensio/api/PensioAPIResponseParserTest.java
@@ -0,0 +1,63 @@
+package com.pensio.api;
+
+import com.pensio.api.generated.APIResponse;
+import com.pensio.api.generated.AcquirerTransactionData;
+import com.pensio.api.generated.AcquirerTransactionDataEntry;
+import com.pensio.api.generated.AcquirerTransactionDataGroup;
+import com.pensio.api.generated.Transaction;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class PensioAPIResponseParserTest {
+
+ @Test
+ void parsesAcquirerTransactionDataOnTransaction() throws Exception {
+ String xml =
+ "" +
+ "" +
+ " " +
+ " 2026-05-14T11:50:45+02:00" +
+ " API/reservation" +
+ " 0" +
+ " Success" +
+ " " +
+ " " +
+ " Success" +
+ " " +
+ " " +
+ " 1" +
+ " " +
+ " " +
+ " 32" +
+ " 001" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
+
+ PensioMerchantAPI api = new PensioMerchantAPI("url", "username", "password");
+ APIResponse parsed = api.parsePostBackXMLParameter(xml);
+
+ Transaction t = parsed.getBody().getTransactions().getTransaction().get(0);
+
+ AcquirerTransactionData atd = t.getAcquirerTransactionData();
+ assertNotNull(atd);
+ assertEquals(1, atd.getGroup().size());
+
+ AcquirerTransactionDataGroup group = atd.getGroup().get(0);
+ assertEquals("passcard", group.getName());
+ assertEquals(2, group.getEntry().size());
+
+ AcquirerTransactionDataEntry e0 = group.getEntry().get(0);
+ assertEquals("creditcode", e0.getKey());
+ assertEquals("32", e0.getValue());
+
+ AcquirerTransactionDataEntry e1 = group.getEntry().get(1);
+ assertEquals("paymentoccurrence", e1.getKey());
+ assertEquals("001", e1.getValue());
+ }
+}
diff --git a/src/test/java/com/pensio/api/request/AcquirerTransactionDataTest.java b/src/test/java/com/pensio/api/request/AcquirerTransactionDataTest.java
new file mode 100644
index 0000000..d179289
--- /dev/null
+++ b/src/test/java/com/pensio/api/request/AcquirerTransactionDataTest.java
@@ -0,0 +1,41 @@
+package com.pensio.api.request;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AcquirerTransactionDataTest {
+
+ @Test
+ void emptyByDefault() {
+ AcquirerTransactionData d = new AcquirerTransactionData();
+ assertTrue(d.isEmpty());
+ assertTrue(d.getAll().isEmpty());
+ }
+
+ @Test
+ void addChainsAndStoresByGroup() {
+ AcquirerTransactionData d = new AcquirerTransactionData()
+ .add(PassCard.GROUP, PassCard.CREDITCODE, "32")
+ .add(PassCard.GROUP, PassCard.PAYMENTOCCURRENCE, "001");
+
+ assertFalse(d.isEmpty());
+ Map passcard = d.getAll().get(PassCard.GROUP);
+ assertEquals(2, passcard.size());
+ assertEquals("32", passcard.get(PassCard.CREDITCODE));
+ assertEquals("001", passcard.get(PassCard.PAYMENTOCCURRENCE));
+ }
+
+ @Test
+ void addOverwritesSameKey() {
+ AcquirerTransactionData d = new AcquirerTransactionData()
+ .add("g", "k", "v1")
+ .add("g", "k", "v2");
+
+ assertEquals("v2", d.getAll().get("g").get("k"));
+ }
+}