form = assertInstanceOf(Map.class, response.get("form"));
+ assertEquals(1, form.size());
+ String value = assertInstanceOf(String.class, form.get("value"));
+ assertEquals("Hello World", value);
+ }, null);
+ }
+ }
+}
diff --git a/src/it/java/com/worldline/acquiring/sdk/java/it/ProcessPaymentTest.java b/src/it/java/com/worldline/acquiring/sdk/java/it/ProcessPaymentTest.java
new file mode 100644
index 0000000..c72c73d
--- /dev/null
+++ b/src/it/java/com/worldline/acquiring/sdk/java/it/ProcessPaymentTest.java
@@ -0,0 +1,45 @@
+package com.worldline.acquiring.sdk.java.it;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.Client;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.payments.GetPaymentStatusParams;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.payments.PaymentsClient;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentResource;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentResponse;
+
+class ProcessPaymentTest extends ItTest {
+
+ /**
+ * Smoke test for process payment.
+ */
+ @Test
+ void test() throws URISyntaxException, IOException {
+ try (Client client = getClient()) {
+ ApiPaymentRequest body = getProcessPaymentRequest();
+
+ PaymentsClient paymentsClient = client
+ .v1()
+ .acquirer(getAcquirerId())
+ .merchant(getMerchantId())
+ .payments();
+
+ ApiPaymentResponse response = paymentsClient.processPayment(body);
+
+ assertProcessPaymentResponse(body, response);
+
+ String paymentId = response.getPaymentId();
+
+ GetPaymentStatusParams query = new GetPaymentStatusParams();
+ query.setReturnOperations(true);
+
+ ApiPaymentResource status = paymentsClient.getPaymentStatus(paymentId, query);
+
+ assertPaymentStatusResponse(paymentId, status);
+ }
+ }
+}
diff --git a/src/it/java/com/worldline/acquiring/sdk/java/it/RequestDccRateTest.java b/src/it/java/com/worldline/acquiring/sdk/java/it/RequestDccRateTest.java
new file mode 100644
index 0000000..26fff35
--- /dev/null
+++ b/src/it/java/com/worldline/acquiring/sdk/java/it/RequestDccRateTest.java
@@ -0,0 +1,31 @@
+package com.worldline.acquiring.sdk.java.it;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.Client;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDCCRateRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDccRateResponse;
+
+class RequestDccRateTest extends ItTest {
+
+ /**
+ * Smoke test for request DCC rate.
+ */
+ @Test
+ void test() throws URISyntaxException, IOException {
+ try (Client client = getClient()) {
+ GetDCCRateRequest body = getDCCRateRequest();
+
+ GetDccRateResponse response = client
+ .v1()
+ .acquirer(getAcquirerId())
+ .merchant(getMerchantId())
+ .dynamicCurrencyConversion()
+ .requestDccRate(body);
+ assertDccRateResponse(body, response);
+ }
+ }
+}
diff --git a/src/it/java/com/worldline/acquiring/sdk/java/it/SDKProxyTest.java b/src/it/java/com/worldline/acquiring/sdk/java/it/SDKProxyTest.java
new file mode 100644
index 0000000..b046ccf
--- /dev/null
+++ b/src/it/java/com/worldline/acquiring/sdk/java/it/SDKProxyTest.java
@@ -0,0 +1,34 @@
+package com.worldline.acquiring.sdk.java.it;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.Client;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.dynamiccurrencyconversion.DynamicCurrencyConversionClient;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDCCRateRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDccRateResponse;
+
+class SDKProxyTest extends ItTest {
+
+ /**
+ * Smoke test for using a proxy configured through SDK properties.
+ */
+ @Test
+ void test() throws URISyntaxException, IOException {
+ try (Client client = getClientWithProxy()) {
+ DynamicCurrencyConversionClient dynamicCurrencyConversionClient = client
+ .v1()
+ .acquirer(getAcquirerId())
+ .merchant(getMerchantId())
+ .dynamicCurrencyConversion();
+
+ GetDCCRateRequest body = getDCCRateRequest();
+
+ GetDccRateResponse response = dynamicCurrencyConversionClient.requestDccRate(body);
+
+ assertDccRateResponse(body, response);
+ }
+ }
+}
diff --git a/src/it/java/com/worldline/acquiring/sdk/java/it/SystemProxyTest.java b/src/it/java/com/worldline/acquiring/sdk/java/it/SystemProxyTest.java
new file mode 100644
index 0000000..77768ff
--- /dev/null
+++ b/src/it/java/com/worldline/acquiring/sdk/java/it/SystemProxyTest.java
@@ -0,0 +1,146 @@
+package com.worldline.acquiring.sdk.java.it;
+
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.Client;
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+import com.worldline.acquiring.sdk.java.Factory;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDCCRateRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDccRateResponse;
+
+class SystemProxyTest extends ItTest {
+
+ private boolean setHttpProxyHost;
+ private boolean setHttpProxyPort;
+ private boolean setHttpsProxyHost;
+ private boolean setHttpsProxyPort;
+ private boolean setHttpProxyUser;
+ private boolean setHttpProxyPass;
+
+ @BeforeEach
+ void setup() throws URISyntaxException {
+ if (setupProxyHost("http")) {
+ setHttpProxyHost = true;
+ setHttpProxyPort = true;
+ }
+ if (setupProxyHost("https")) {
+ setHttpsProxyHost = true;
+ setHttpsProxyPort = true;
+ }
+
+ if (System.getProperty("http.proxyUser") == null) {
+ String proxyUsername = System.getProperty("acquiring.api.proxy.username");
+ if (proxyUsername != null) {
+ System.setProperty("http.proxyUser", proxyUsername);
+ setHttpProxyUser = true;
+ }
+ }
+ if (System.getProperty("http.proxyPass") == null) {
+ String proxyPassword = System.getProperty("acquiring.api.proxy.password");
+ if (proxyPassword != null) {
+ System.setProperty("http.proxyPass", proxyPassword);
+ setHttpProxyPass = true;
+ }
+ }
+ }
+
+ private boolean setupProxyHost(String prefix) throws URISyntaxException {
+ String proxyHostProperty = prefix + ".proxyHost";
+ String proxyPortProperty = prefix + ".proxyPort";
+
+ String proxyHost = System.getProperty(proxyPortProperty);
+ String proxyPort = System.getProperty(proxyPortProperty);
+
+ if (proxyHost == null && proxyPort == null) {
+ String proxyURIString = System.getProperty("acquiring.api.proxy.uri");
+ assumeFalse(proxyURIString == null,
+ "Either system properties '" + proxyHostProperty + "' and '" + proxyPortProperty + "' must be set,"
+ + " or system property acquiring.api.proxy.uri must be set");
+
+ URI proxyURI = new URI(proxyURIString);
+ System.setProperty(proxyHostProperty, proxyURI.getHost());
+ System.setProperty(proxyPortProperty, Integer.toString(proxyURI.getPort()));
+
+ return true;
+ }
+ if (proxyHost == null || proxyPort == null) {
+ throw new IllegalStateException("Either system properties '" + proxyHostProperty + "' and '" + proxyPortProperty + "' must both be set,"
+ + " or neither must be set");
+ }
+
+ // did not setup the proxy host
+ return false;
+ }
+
+ @AfterEach
+ void cleanup() {
+ if (setHttpProxyHost) {
+ System.clearProperty("http.proxyHost");
+ }
+ if (setHttpProxyPort) {
+ System.clearProperty("http.proxyPort");
+ }
+ if (setHttpsProxyHost) {
+ System.clearProperty("https.proxyHost");
+ }
+ if (setHttpsProxyPort) {
+ System.clearProperty("https.proxyPort");
+ }
+ if (setHttpProxyUser) {
+ System.clearProperty("http.proxyUser");
+ }
+ if (setHttpProxyPass) {
+ System.clearProperty("http.proxyPass");
+ }
+ }
+
+ /**
+ * Smoke test for using a proxy configured through system properties.
+ */
+ @Test
+ void test() throws URISyntaxException, IOException {
+
+ final boolean[] authenticationCalled = { false };
+
+ final String username = System.getProperty("http.proxyUser");
+ final String password = System.getProperty("http.proxyPass");
+
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ authenticationCalled[0] = true;
+
+ return new PasswordAuthentication(username, password.toCharArray());
+ }
+ });
+
+ CommunicatorConfiguration configuration = getCommunicatorConfiguration()
+ .withProxyConfiguration(null);
+
+ try (Client client = Factory.createClient(configuration)) {
+ GetDCCRateRequest body = getDCCRateRequest();
+
+ GetDccRateResponse response = client
+ .v1()
+ .acquirer(getAcquirerId())
+ .merchant(getMerchantId())
+ .dynamicCurrencyConversion()
+ .requestDccRate(body);
+
+ assertDccRateResponse(body, response);
+ }
+
+ // Authentication may or may not be required, depending on the proxy configuration.
+ // Therefore, no assertions can be made about authenticationCalled[0]
+ }
+}
diff --git a/src/it/resources/itconfiguration.properties b/src/it/resources/itconfiguration.properties
new file mode 100644
index 0000000..c27f9da
--- /dev/null
+++ b/src/it/resources/itconfiguration.properties
@@ -0,0 +1,6 @@
+# Worldline Acquiring platform connection settings
+acquiring.api.endpoint.host=api.preprod.acquiring.worldline-solutions.com
+acquiring.api.authorizationType=OAUTH2
+acquiring.api.connectTimeout=5000
+acquiring.api.socketTimeout=300000
+acquiring.api.integrator=Worldline
diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml
new file mode 100644
index 0000000..8b13a23
--- /dev/null
+++ b/src/main/assembly/bin.xml
@@ -0,0 +1,40 @@
+
+
+
+ bin
+
+ zip
+
+
+ false
+
+
+
+
+ README.md
+ LICENSE.txt
+
+
+
+
+
+
+ lib
+ true
+ true
+ runtime
+
+
+ lib
+ true
+ compile
+
+ com.google.code.gson:gson:*
+ org.apache.httpcomponents:httpclient:*
+
+
+
+
diff --git a/src/main/assembly/src.xml b/src/main/assembly/src.xml
new file mode 100644
index 0000000..a358dc1
--- /dev/null
+++ b/src/main/assembly/src.xml
@@ -0,0 +1,27 @@
+
+
+
+ src
+
+ zip
+
+
+ true
+
+
+
+
+ pom.xml
+ README.md
+ LICENSE.txt
+ checkstyle.xml
+
+
+
+ src
+
+
+
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/Client.java b/src/main/generated/com/worldline/acquiring/sdk/java/Client.java
new file mode 100644
index 0000000..a17def1
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/Client.java
@@ -0,0 +1,85 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import com.worldline.acquiring.sdk.java.communication.PooledConnection;
+import com.worldline.acquiring.sdk.java.logging.BodyObfuscator;
+import com.worldline.acquiring.sdk.java.logging.CommunicatorLogger;
+import com.worldline.acquiring.sdk.java.logging.HeaderObfuscator;
+import com.worldline.acquiring.sdk.java.logging.LoggingCapable;
+import com.worldline.acquiring.sdk.java.logging.ObfuscationCapable;
+import com.worldline.acquiring.sdk.java.v1.V1Client;
+
+/**
+ * Worldline Acquiring platform client.
+ *
+ * Thread-safe.
+ */
+public class Client extends ApiResource implements Closeable, LoggingCapable, ObfuscationCapable {
+
+ public Client(Communicator communicator) {
+ super(communicator, null);
+ }
+
+ /**
+ * Utility method that delegates the call to this client's communicator.
+ *
+ * @see Communicator#closeIdleConnections(long, TimeUnit)
+ * @see PooledConnection#closeIdleConnections(long, TimeUnit)
+ */
+ public void closeIdleConnections(long idleTime, TimeUnit timeUnit) {
+ communicator.closeIdleConnections(idleTime, timeUnit);
+ }
+
+ /**
+ * Utility method that delegates the call to this client's communicator.
+ *
+ * @see Communicator#closeExpiredConnections()
+ * @see PooledConnection#closeExpiredConnections()
+ */
+ public void closeExpiredConnections() {
+ communicator.closeExpiredConnections();
+ }
+
+ @Override
+ public void setBodyObfuscator(BodyObfuscator bodyObfuscator) {
+ // delegate to the communicator
+ communicator.setBodyObfuscator(bodyObfuscator);
+ }
+
+ @Override
+ public void setHeaderObfuscator(HeaderObfuscator headerObfuscator) {
+ // delegate to the communicator
+ communicator.setHeaderObfuscator(headerObfuscator);
+ }
+
+ @Override
+ public void enableLogging(CommunicatorLogger communicatorLogger) {
+ // delegate to the communicator
+ communicator.enableLogging(communicatorLogger);
+ }
+
+ @Override
+ public void disableLogging() {
+ // delegate to the communicator
+ communicator.disableLogging();
+ }
+
+ /**
+ * Releases any system resources associated with this object.
+ */
+ @Override
+ public void close() throws IOException {
+ communicator.close();
+ }
+
+ public V1Client v1() {
+ return new V1Client(this, null);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/ApiException.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ApiException.java
new file mode 100644
index 0000000..75bee2b
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ApiException.java
@@ -0,0 +1,96 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+/**
+ * Represents an error response from the Worldline Acquiring platform.
+ */
+@SuppressWarnings("serial")
+public class ApiException extends RuntimeException {
+
+ private final int statusCode;
+ private final String responseBody;
+ private final String type;
+ private final String title;
+ private final Integer status;
+ private final String detail;
+ private final String instance;
+
+ public ApiException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ this("The Worldline Acquiring platform returned an error response", statusCode, responseBody, type, title, status, detail, instance);
+ }
+
+ public ApiException(String message, int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super(message);
+ this.statusCode = statusCode;
+ this.responseBody = responseBody;
+ this.type = type;
+ this.title = title;
+ this.status = status;
+ this.detail = detail;
+ this.instance = instance;
+ }
+
+ /**
+ * @return The HTTP status code that was returned by the Worldline Acquiring platform.
+ */
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * @return The raw response body that was returned by the Worldline Acquiring platform.
+ */
+ public String getResponseBody() {
+ return responseBody;
+ }
+
+ /**
+ * @return The {@code type} received from the Worldline Acquiring platform if available.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @return The {@code title} received from the Worldline Acquiring platform if available.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @return The {@code status} received from the Worldline Acquiring platform if available.
+ */
+ public Integer getStatus() {
+ return status;
+ }
+
+ /**
+ * @return The {@code detail} received from the Worldline Acquiring platform if available.
+ */
+ public String getDetail() {
+ return detail;
+ }
+
+ /**
+ * @return The {@code instance} received from the Worldline Acquiring platform if available.
+ */
+ public String getInstance() {
+ return instance;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(super.toString());
+ if (statusCode > 0) {
+ sb.append("; statusCode=").append(statusCode);
+ }
+ if (responseBody != null && responseBody.length() > 0) {
+ sb.append("; responseBody='").append(responseBody).append("'");
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/AuthorizationException.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/AuthorizationException.java
new file mode 100644
index 0000000..bbd764a
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/AuthorizationException.java
@@ -0,0 +1,20 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+/**
+ * Represents an error response from the Worldline Acquiring platform when API authorization failed.
+ */
+@SuppressWarnings("serial")
+public class AuthorizationException extends ApiException {
+
+ public AuthorizationException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super("The Worldline Acquiring platform returned an API authorization error response", statusCode, responseBody, type, title, status, detail, instance);
+ }
+
+ public AuthorizationException(String message, int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super(message, statusCode, responseBody, type, title, status, detail, instance);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/ExceptionFactory.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ExceptionFactory.java
new file mode 100644
index 0000000..98be110
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ExceptionFactory.java
@@ -0,0 +1,50 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+
+/**
+ * Factory for exceptions thrown by Worldline Acquiring platform API v1 resources.
+ */
+public class ExceptionFactory {
+
+ public RuntimeException createException(int statusCode, String responseBody, Object errorObject, CallContext context) {
+ if (errorObject instanceof ApiPaymentErrorResponse) {
+ ApiPaymentErrorResponse apiPaymentErrorResponse = (ApiPaymentErrorResponse) errorObject;
+ return createException(statusCode, responseBody, apiPaymentErrorResponse.getType(), apiPaymentErrorResponse.getTitle(), apiPaymentErrorResponse.getStatus(), apiPaymentErrorResponse.getDetail(), apiPaymentErrorResponse.getInstance(), context);
+ }
+ if (errorObject == null) {
+ return createException(statusCode, responseBody, null, null, null, null, null, context);
+ }
+ throw new IllegalArgumentException("unsupported error object type: " + errorObject.getClass().getName());
+ }
+
+ // keep the context here to not have to make breaking changes if we should need it in the future
+ @SuppressWarnings("unused")
+ private RuntimeException createException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance, CallContext context) {
+ switch (statusCode) {
+ case 400:
+ return new ValidationException(statusCode, responseBody, type, title, status, detail, instance);
+ case 403:
+ return new AuthorizationException(statusCode, responseBody, type, title, status, detail, instance);
+ case 404:
+ return new ReferenceException(statusCode, responseBody, type, title, status, detail, instance);
+ case 409:
+ return new ReferenceException(statusCode, responseBody, type, title, status, detail, instance);
+ case 410:
+ return new ReferenceException(statusCode, responseBody, type, title, status, detail, instance);
+ case 500:
+ return new PlatformException(statusCode, responseBody, type, title, status, detail, instance);
+ case 502:
+ return new PlatformException(statusCode, responseBody, type, title, status, detail, instance);
+ case 503:
+ return new PlatformException(statusCode, responseBody, type, title, status, detail, instance);
+ default:
+ return new ApiException(statusCode, responseBody, type, title, status, detail, instance);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/PlatformException.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/PlatformException.java
new file mode 100644
index 0000000..6e1bcf4
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/PlatformException.java
@@ -0,0 +1,20 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+/**
+ * Represents an error response from the Worldline Acquiring platform when something went wrong at the Worldline Acquiring platform or further downstream.
+ */
+@SuppressWarnings("serial")
+public class PlatformException extends ApiException {
+
+ public PlatformException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super("The Worldline Acquiring platform returned an error response", statusCode, responseBody, type, title, status, detail, instance);
+ }
+
+ public PlatformException(String message, int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super(message, statusCode, responseBody, type, title, status, detail, instance);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/ReferenceException.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ReferenceException.java
new file mode 100644
index 0000000..2da2d9e
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ReferenceException.java
@@ -0,0 +1,20 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+/**
+ * Represents an error response from the Worldline Acquiring platform when a non-existing or removed object is trying to be accessed.
+ */
+@SuppressWarnings("serial")
+public class ReferenceException extends ApiException {
+
+ public ReferenceException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super("The Worldline Acquiring platform returned a reference error response", statusCode, responseBody, type, title, status, detail, instance);
+ }
+
+ public ReferenceException(String message, int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super(message, statusCode, responseBody, type, title, status, detail, instance);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/V1Client.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/V1Client.java
new file mode 100644
index 0000000..6410b9f
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/V1Client.java
@@ -0,0 +1,43 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.v1.acquirer.AcquirerClient;
+import com.worldline.acquiring.sdk.java.v1.ping.PingClient;
+
+/**
+ * V1 client. Thread-safe.
+ */
+public class V1Client extends ApiResource {
+
+ public V1Client(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}
+ *
+ * @param acquirerId String
+ * @return AcquirerClient
+ */
+ public AcquirerClient acquirer(String acquirerId) {
+ Map subContext = new TreeMap<>();
+ subContext.put("acquirerId", acquirerId);
+ return new AcquirerClient(this, subContext);
+ }
+
+ /**
+ * Resource /services/v1/ping
+ *
+ * @return PingClient
+ */
+ public PingClient ping() {
+ return new PingClient(this, null);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/ValidationException.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ValidationException.java
new file mode 100644
index 0000000..34b0c84
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ValidationException.java
@@ -0,0 +1,20 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1;
+
+/**
+ * Represents an error response from the Worldline Acquiring platform when validation of requests failed.
+ */
+@SuppressWarnings("serial")
+public class ValidationException extends ApiException {
+
+ public ValidationException(int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super("The Worldline Acquiring platform returned an incorrect request error response", statusCode, responseBody, type, title, status, detail, instance);
+ }
+
+ public ValidationException(String message, int statusCode, String responseBody, String type, String title, Integer status, String detail, String instance) {
+ super(message, statusCode, responseBody, type, title, status, detail, instance);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/AcquirerClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/AcquirerClient.java
new file mode 100644
index 0000000..7cc8f71
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/AcquirerClient.java
@@ -0,0 +1,33 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.MerchantClient;
+
+/**
+ * Acquirer client. Thread-safe.
+ */
+public class AcquirerClient extends ApiResource {
+
+ public AcquirerClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}
+ *
+ * @param merchantId String
+ * @return MerchantClient
+ */
+ public MerchantClient merchant(String merchantId) {
+ Map subContext = new TreeMap<>();
+ subContext.put("merchantId", merchantId);
+ return new MerchantClient(this, subContext);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/MerchantClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/MerchantClient.java
new file mode 100644
index 0000000..52416f6
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/MerchantClient.java
@@ -0,0 +1,69 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant;
+
+import java.util.Map;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.accountverifications.AccountVerificationsClient;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.dynamiccurrencyconversion.DynamicCurrencyConversionClient;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.payments.PaymentsClient;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.refunds.RefundsClient;
+import com.worldline.acquiring.sdk.java.v1.acquirer.merchant.technicalreversals.TechnicalReversalsClient;
+
+/**
+ * Merchant client. Thread-safe.
+ */
+public class MerchantClient extends ApiResource {
+
+ public MerchantClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments
+ *
+ * @return PaymentsClient
+ */
+ public PaymentsClient payments() {
+ return new PaymentsClient(this, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds
+ *
+ * @return RefundsClient
+ */
+ public RefundsClient refunds() {
+ return new RefundsClient(this, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/account-verifications
+ *
+ * @return AccountVerificationsClient
+ */
+ public AccountVerificationsClient accountVerifications() {
+ return new AccountVerificationsClient(this, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/operations/{operationId}/reverse
+ *
+ * @return TechnicalReversalsClient
+ */
+ public TechnicalReversalsClient technicalReversals() {
+ return new TechnicalReversalsClient(this, null);
+ }
+
+ /**
+ * Resource /services/v1/{acquirerId}/{merchantId}/dcc-rates
+ *
+ * @return DynamicCurrencyConversionClient
+ */
+ public DynamicCurrencyConversionClient dynamicCurrencyConversion() {
+ return new DynamicCurrencyConversionClient(this, null);
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/accountverifications/AccountVerificationsClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/accountverifications/AccountVerificationsClient.java
new file mode 100644
index 0000000..e62bd57
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/accountverifications/AccountVerificationsClient.java
@@ -0,0 +1,84 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.accountverifications;
+
+import java.util.Map;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiAccountVerificationRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiAccountVerificationResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+
+/**
+ * AccountVerifications client. Thread-safe.
+ */
+public class AccountVerificationsClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public AccountVerificationsClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/account-verifications
+ * - Verify account
+ *
+ * @param body ApiAccountVerificationRequest
+ * @return ApiAccountVerificationResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiAccountVerificationResponse processAccountVerification(ApiAccountVerificationRequest body) {
+ return processAccountVerification(body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/account-verifications
+ * - Verify account
+ *
+ * @param body ApiAccountVerificationRequest
+ * @param context CallContext
+ * @return ApiAccountVerificationResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiAccountVerificationResponse processAccountVerification(ApiAccountVerificationRequest body, CallContext context) {
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/account-verifications", null);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiAccountVerificationResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/dynamiccurrencyconversion/DynamicCurrencyConversionClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/dynamiccurrencyconversion/DynamicCurrencyConversionClient.java
new file mode 100644
index 0000000..17b4d98
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/dynamiccurrencyconversion/DynamicCurrencyConversionClient.java
@@ -0,0 +1,84 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.dynamiccurrencyconversion;
+
+import java.util.Map;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDCCRateRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.GetDccRateResponse;
+
+/**
+ * DynamicCurrencyConversion client. Thread-safe.
+ */
+public class DynamicCurrencyConversionClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public DynamicCurrencyConversionClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /services/v1/{acquirerId}/{merchantId}/dcc-rates
+ * - Request DCC rate
+ *
+ * @param body GetDCCRateRequest
+ * @return GetDccRateResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public GetDccRateResponse requestDccRate(GetDCCRateRequest body) {
+ return requestDccRate(body, null);
+ }
+
+ /**
+ * Resource /services/v1/{acquirerId}/{merchantId}/dcc-rates
+ * - Request DCC rate
+ *
+ * @param body GetDCCRateRequest
+ * @param context CallContext
+ * @return GetDccRateResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public GetDccRateResponse requestDccRate(GetDCCRateRequest body, CallContext context) {
+ String uri = instantiateUri("/services/v1/{acquirerId}/{merchantId}/dcc-rates", null);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ GetDccRateResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/GetPaymentStatusParams.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/GetPaymentStatusParams.java
new file mode 100644
index 0000000..dc0b615
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/GetPaymentStatusParams.java
@@ -0,0 +1,45 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.payments;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.communication.ParamRequest;
+import com.worldline.acquiring.sdk.java.communication.RequestParam;
+
+/**
+ * Query parameters for
+ * Retrieve payment
+ */
+public class GetPaymentStatusParams implements ParamRequest {
+
+ private Boolean returnOperations;
+
+ /**
+ * If true, the response will contain the operations of the payment.
+ * False by default.
+ */
+ public Boolean getReturnOperations() {
+ return returnOperations;
+ }
+
+ /**
+ * If true, the response will contain the operations of the payment.
+ * False by default.
+ */
+ public void setReturnOperations(Boolean value) {
+ this.returnOperations = value;
+ }
+
+ @Override
+ public List toRequestParameters() {
+ List result = new ArrayList<>();
+ if (returnOperations != null) {
+ result.add(new RequestParam("returnOperations", returnOperations.toString()));
+ }
+ return result;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/PaymentsClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/PaymentsClient.java
new file mode 100644
index 0000000..419184c
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/payments/PaymentsClient.java
@@ -0,0 +1,373 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.payments;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiActionResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiActionResponseForRefund;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiCaptureRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiIncrementRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiIncrementResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentRefundRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentResource;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentReversalRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiReversalResponse;
+
+/**
+ * Payments client. Thread-safe.
+ */
+public class PaymentsClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public PaymentsClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments
+ * - Create payment
+ *
+ * @param body ApiPaymentRequest
+ * @return ApiPaymentResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiPaymentResponse processPayment(ApiPaymentRequest body) {
+ return processPayment(body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments
+ * - Create payment
+ *
+ * @param body ApiPaymentRequest
+ * @param context CallContext
+ * @return ApiPaymentResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiPaymentResponse processPayment(ApiPaymentRequest body, CallContext context) {
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments", null);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiPaymentResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}
+ * - Retrieve payment
+ *
+ * @param paymentId String
+ * @param query GetPaymentStatusParams
+ * @return ApiPaymentResource
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiPaymentResource getPaymentStatus(String paymentId, GetPaymentStatusParams query) {
+ return getPaymentStatus(paymentId, query, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}
+ * - Retrieve payment
+ *
+ * @param paymentId String
+ * @param query GetPaymentStatusParams
+ * @param context CallContext
+ * @return ApiPaymentResource
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiPaymentResource getPaymentStatus(String paymentId, GetPaymentStatusParams query, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("paymentId", paymentId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}", pathContext);
+ try {
+ return communicator.get(
+ uri,
+ null,
+ query,
+ ApiPaymentResource.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/captures
+ * - Capture payment
+ *
+ * @param paymentId String
+ * @param body ApiCaptureRequest
+ * @return ApiActionResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponse simpleCaptureOfPayment(String paymentId, ApiCaptureRequest body) {
+ return simpleCaptureOfPayment(paymentId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/captures
+ * - Capture payment
+ *
+ * @param paymentId String
+ * @param body ApiCaptureRequest
+ * @param context CallContext
+ * @return ApiActionResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponse simpleCaptureOfPayment(String paymentId, ApiCaptureRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("paymentId", paymentId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/captures", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiActionResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/authorization-reversals
+ * - Reverse authorization
+ *
+ * @param paymentId String
+ * @param body ApiPaymentReversalRequest
+ * @return ApiReversalResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiReversalResponse reverseAuthorization(String paymentId, ApiPaymentReversalRequest body) {
+ return reverseAuthorization(paymentId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/authorization-reversals
+ * - Reverse authorization
+ *
+ * @param paymentId String
+ * @param body ApiPaymentReversalRequest
+ * @param context CallContext
+ * @return ApiReversalResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiReversalResponse reverseAuthorization(String paymentId, ApiPaymentReversalRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("paymentId", paymentId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/authorization-reversals", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiReversalResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/increments
+ * - Increment authorization
+ *
+ * @param paymentId String
+ * @param body ApiIncrementRequest
+ * @return ApiIncrementResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiIncrementResponse incrementPayment(String paymentId, ApiIncrementRequest body) {
+ return incrementPayment(paymentId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/increments
+ * - Increment authorization
+ *
+ * @param paymentId String
+ * @param body ApiIncrementRequest
+ * @param context CallContext
+ * @return ApiIncrementResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiIncrementResponse incrementPayment(String paymentId, ApiIncrementRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("paymentId", paymentId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/increments", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiIncrementResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/refunds
+ * - Refund payment
+ *
+ * @param paymentId String
+ * @param body ApiPaymentRefundRequest
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund createRefund(String paymentId, ApiPaymentRefundRequest body) {
+ return createRefund(paymentId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/refunds
+ * - Refund payment
+ *
+ * @param paymentId String
+ * @param body ApiPaymentRefundRequest
+ * @param context CallContext
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund createRefund(String paymentId, ApiPaymentRefundRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("paymentId", paymentId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/payments/{paymentId}/refunds", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiActionResponseForRefund.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/GetRefundParams.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/GetRefundParams.java
new file mode 100644
index 0000000..1d4e8c8
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/GetRefundParams.java
@@ -0,0 +1,45 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.refunds;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.communication.ParamRequest;
+import com.worldline.acquiring.sdk.java.communication.RequestParam;
+
+/**
+ * Query parameters for
+ * Retrieve refund
+ */
+public class GetRefundParams implements ParamRequest {
+
+ private Boolean returnOperations;
+
+ /**
+ * If true, the response will contain the operations of the payment.
+ * False by default.
+ */
+ public Boolean getReturnOperations() {
+ return returnOperations;
+ }
+
+ /**
+ * If true, the response will contain the operations of the payment.
+ * False by default.
+ */
+ public void setReturnOperations(Boolean value) {
+ this.returnOperations = value;
+ }
+
+ @Override
+ public List toRequestParameters() {
+ List result = new ArrayList<>();
+ if (returnOperations != null) {
+ result.add(new RequestParam("returnOperations", returnOperations.toString()));
+ }
+ return result;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/RefundsClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/RefundsClient.java
new file mode 100644
index 0000000..893701a
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/refunds/RefundsClient.java
@@ -0,0 +1,256 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.refunds;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiActionResponseForRefund;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiCaptureRequestForRefund;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentReversalRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiRefundRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiRefundResource;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiRefundResponse;
+
+/**
+ * Refunds client. Thread-safe.
+ */
+public class RefundsClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public RefundsClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds
+ * - Create standalone refund
+ *
+ * @param body ApiRefundRequest
+ * @return ApiRefundResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiRefundResponse processStandaloneRefund(ApiRefundRequest body) {
+ return processStandaloneRefund(body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds
+ * - Create standalone refund
+ *
+ * @param body ApiRefundRequest
+ * @param context CallContext
+ * @return ApiRefundResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiRefundResponse processStandaloneRefund(ApiRefundRequest body, CallContext context) {
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/refunds", null);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiRefundResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}
+ * - Retrieve refund
+ *
+ * @param refundId String
+ * @param query GetRefundParams
+ * @return ApiRefundResource
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiRefundResource getRefund(String refundId, GetRefundParams query) {
+ return getRefund(refundId, query, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}
+ * - Retrieve refund
+ *
+ * @param refundId String
+ * @param query GetRefundParams
+ * @param context CallContext
+ * @return ApiRefundResource
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiRefundResource getRefund(String refundId, GetRefundParams query, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("refundId", refundId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}", pathContext);
+ try {
+ return communicator.get(
+ uri,
+ null,
+ query,
+ ApiRefundResource.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/captures
+ * - Capture refund
+ *
+ * @param refundId String
+ * @param body ApiCaptureRequestForRefund
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund captureRefund(String refundId, ApiCaptureRequestForRefund body) {
+ return captureRefund(refundId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/captures
+ * - Capture refund
+ *
+ * @param refundId String
+ * @param body ApiCaptureRequestForRefund
+ * @param context CallContext
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund captureRefund(String refundId, ApiCaptureRequestForRefund body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("refundId", refundId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/captures", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiActionResponseForRefund.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/authorization-reversals
+ * - Reverse refund authorization
+ *
+ * @param refundId String
+ * @param body ApiPaymentReversalRequest
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund reverseRefundAuthorization(String refundId, ApiPaymentReversalRequest body) {
+ return reverseRefundAuthorization(refundId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/authorization-reversals
+ * - Reverse refund authorization
+ *
+ * @param refundId String
+ * @param body ApiPaymentReversalRequest
+ * @param context CallContext
+ * @return ApiActionResponseForRefund
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiActionResponseForRefund reverseRefundAuthorization(String refundId, ApiPaymentReversalRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("refundId", refundId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/refunds/{refundId}/authorization-reversals", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiActionResponseForRefund.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/technicalreversals/TechnicalReversalsClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/technicalreversals/TechnicalReversalsClient.java
new file mode 100644
index 0000000..2195e59
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/acquirer/merchant/technicalreversals/TechnicalReversalsClient.java
@@ -0,0 +1,89 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.acquirer.merchant.technicalreversals;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiTechnicalReversalRequest;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiTechnicalReversalResponse;
+
+/**
+ * TechnicalReversals client. Thread-safe.
+ */
+public class TechnicalReversalsClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public TechnicalReversalsClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/operations/{operationId}/reverse
+ * - Technical reversal
+ *
+ * @param operationId String
+ * @param body ApiTechnicalReversalRequest
+ * @return ApiTechnicalReversalResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiTechnicalReversalResponse technicalReversal(String operationId, ApiTechnicalReversalRequest body) {
+ return technicalReversal(operationId, body, null);
+ }
+
+ /**
+ * Resource /processing/v1/{acquirerId}/{merchantId}/operations/{operationId}/reverse
+ * - Technical reversal
+ *
+ * @param operationId String
+ * @param body ApiTechnicalReversalRequest
+ * @param context CallContext
+ * @return ApiTechnicalReversalResponse
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public ApiTechnicalReversalResponse technicalReversal(String operationId, ApiTechnicalReversalRequest body, CallContext context) {
+ Map pathContext = new TreeMap<>();
+ pathContext.put("operationId", operationId);
+ String uri = instantiateUri("/processing/v1/{acquirerId}/{merchantId}/operations/{operationId}/reverse", pathContext);
+ try {
+ return communicator.post(
+ uri,
+ null,
+ null,
+ body,
+ ApiTechnicalReversalResponse.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AddressVerificationData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AddressVerificationData.java
new file mode 100644
index 0000000..ae69f26
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AddressVerificationData.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class AddressVerificationData {
+
+ private String cardholderAddress;
+
+ private String cardholderPostalCode;
+
+ /**
+ * Cardholder street address
+ */
+ public String getCardholderAddress() {
+ return cardholderAddress;
+ }
+
+ /**
+ * Cardholder street address
+ */
+ public void setCardholderAddress(String value) {
+ this.cardholderAddress = value;
+ }
+
+ /**
+ * Cardholder postal code, should be provided without spaces
+ */
+ public String getCardholderPostalCode() {
+ return cardholderPostalCode;
+ }
+
+ /**
+ * Cardholder postal code, should be provided without spaces
+ */
+ public void setCardholderPostalCode(String value) {
+ this.cardholderPostalCode = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AmountData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AmountData.java
new file mode 100644
index 0000000..5735a3c
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/AmountData.java
@@ -0,0 +1,60 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class AmountData {
+
+ private Long amount;
+
+ private String currencyCode;
+
+ private Integer numberOfDecimals;
+
+ /**
+ * Amount of transaction formatted according to card scheme
+ * specifications.
+ * E.g. 100 for 1.00 EUR. Either this or amount must be present.
+ */
+ public Long getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount of transaction formatted according to card scheme
+ * specifications.
+ * E.g. 100 for 1.00 EUR. Either this or amount must be present.
+ */
+ public void setAmount(Long value) {
+ this.amount = value;
+ }
+
+ /**
+ * Alpha-numeric ISO 4217 currency code for transaction, e.g. EUR
+ */
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ /**
+ * Alpha-numeric ISO 4217 currency code for transaction, e.g. EUR
+ */
+ public void setCurrencyCode(String value) {
+ this.currencyCode = value;
+ }
+
+ /**
+ * Number of decimals in the amount
+ */
+ public Integer getNumberOfDecimals() {
+ return numberOfDecimals;
+ }
+
+ /**
+ * Number of decimals in the amount
+ */
+ public void setNumberOfDecimals(Integer value) {
+ this.numberOfDecimals = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationRequest.java
new file mode 100644
index 0000000..8aa7f48
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationRequest.java
@@ -0,0 +1,102 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiAccountVerificationRequest {
+
+ private CardPaymentDataForVerification cardPaymentData;
+
+ private MerchantData merchant;
+
+ private String operationId;
+
+ private PaymentReferences references;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Card data
+ */
+ public CardPaymentDataForVerification getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ /**
+ * Card data
+ */
+ public void setCardPaymentData(CardPaymentDataForVerification value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public MerchantData getMerchant() {
+ return merchant;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public void setMerchant(MerchantData value) {
+ this.merchant = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Payment References
+ */
+ public PaymentReferences getReferences() {
+ return references;
+ }
+
+ /**
+ * Payment References
+ */
+ public void setReferences(PaymentReferences value) {
+ this.references = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationResponse.java
new file mode 100644
index 0000000..49353d1
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiAccountVerificationResponse.java
@@ -0,0 +1,138 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiAccountVerificationResponse {
+
+ private String authorizationCode;
+
+ private CardPaymentDataForResponse cardPaymentData;
+
+ private String operationId;
+
+ private ApiReferencesForResponses references;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ /**
+ * Authorization approval code
+ */
+ public String getAuthorizationCode() {
+ return authorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setAuthorizationCode(String value) {
+ this.authorizationCode = value;
+ }
+
+ public CardPaymentDataForResponse getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardPaymentDataForResponse value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponse.java
new file mode 100644
index 0000000..31048ed
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponse.java
@@ -0,0 +1,112 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiActionResponse {
+
+ private String operationId;
+
+ private ApiPaymentSummaryForResponse payment;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * A summary of the payment used for responses
+ */
+ public ApiPaymentSummaryForResponse getPayment() {
+ return payment;
+ }
+
+ /**
+ * A summary of the payment used for responses
+ */
+ public void setPayment(ApiPaymentSummaryForResponse value) {
+ this.payment = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponseForRefund.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponseForRefund.java
new file mode 100644
index 0000000..70e997c
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiActionResponseForRefund.java
@@ -0,0 +1,112 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiActionResponseForRefund {
+
+ private String operationId;
+
+ private ApiRefundSummaryForResponse refund;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * A summary of the refund used for responses
+ */
+ public ApiRefundSummaryForResponse getRefund() {
+ return refund;
+ }
+
+ /**
+ * A summary of the refund used for responses
+ */
+ public void setRefund(ApiRefundSummaryForResponse value) {
+ this.refund = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequest.java
new file mode 100644
index 0000000..93e93a2
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequest.java
@@ -0,0 +1,122 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiCaptureRequest {
+
+ private AmountData amount;
+
+ private Integer captureSequenceNumber;
+
+ private DccData dynamicCurrencyConversion;
+
+ private Boolean isFinal;
+
+ private String operationId;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Amount to capture. If not provided, the full amount will be captured.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount to capture. If not provided, the full amount will be captured.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * The index of the partial capture. Not needed for full capture.
+ */
+ public Integer getCaptureSequenceNumber() {
+ return captureSequenceNumber;
+ }
+
+ /**
+ * The index of the partial capture. Not needed for full capture.
+ */
+ public void setCaptureSequenceNumber(Integer value) {
+ this.captureSequenceNumber = value;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * Indicates whether this partial capture is the final one.
+ * Not needed for full capture.
+ */
+ public Boolean getIsFinal() {
+ return isFinal;
+ }
+
+ /**
+ * Indicates whether this partial capture is the final one.
+ * Not needed for full capture.
+ */
+ public void setIsFinal(Boolean value) {
+ this.isFinal = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequestForRefund.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequestForRefund.java
new file mode 100644
index 0000000..a0e5786
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiCaptureRequestForRefund.java
@@ -0,0 +1,54 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiCaptureRequestForRefund {
+
+ private String operationId;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementRequest.java
new file mode 100644
index 0000000..bbab19e
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementRequest.java
@@ -0,0 +1,88 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiIncrementRequest {
+
+ private DccData dynamicCurrencyConversion;
+
+ private AmountData incrementAmount;
+
+ private String operationId;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getIncrementAmount() {
+ return incrementAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setIncrementAmount(AmountData value) {
+ this.incrementAmount = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementResponse.java
new file mode 100644
index 0000000..4f1b02f
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiIncrementResponse.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiIncrementResponse extends ApiActionResponse {
+
+ private String authorizationCode;
+
+ private AmountData totalAuthorizedAmount;
+
+ /**
+ * Authorization approval code
+ */
+ public String getAuthorizationCode() {
+ return authorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setAuthorizationCode(String value) {
+ this.authorizationCode = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentErrorResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentErrorResponse.java
new file mode 100644
index 0000000..da1b460
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentErrorResponse.java
@@ -0,0 +1,102 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiPaymentErrorResponse {
+
+ private String detail;
+
+ private String instance;
+
+ private Integer status;
+
+ private String title;
+
+ private String type;
+
+ /**
+ * Any relevant details about the error.
+ * May include suggestions for handling it. Can be an empty string if no extra details are
+ * available.
+ */
+ public String getDetail() {
+ return detail;
+ }
+
+ /**
+ * Any relevant details about the error.
+ * May include suggestions for handling it. Can be an empty string if no extra details are
+ * available.
+ */
+ public void setDetail(String value) {
+ this.detail = value;
+ }
+
+ /**
+ * A URI reference that identifies the specific occurrence of the error.
+ * It may or may not yield further information if dereferenced.
+ */
+ public String getInstance() {
+ return instance;
+ }
+
+ /**
+ * A URI reference that identifies the specific occurrence of the error.
+ * It may or may not yield further information if dereferenced.
+ */
+ public void setInstance(String value) {
+ this.instance = value;
+ }
+
+ /**
+ * The HTTP status code of this error response.
+ * Included to aid those frameworks that have a hard time working with anything other than
+ * the body of an HTTP response.
+ */
+ public Integer getStatus() {
+ return status;
+ }
+
+ /**
+ * The HTTP status code of this error response.
+ * Included to aid those frameworks that have a hard time working with anything other than
+ * the body of an HTTP response.
+ */
+ public void setStatus(Integer value) {
+ this.status = value;
+ }
+
+ /**
+ * The human-readable version of the error.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * The human-readable version of the error.
+ */
+ public void setTitle(String value) {
+ this.title = value;
+ }
+
+ /**
+ * The type of the error.
+ * This is what you should match against when implementing error handling.
+ * It is in the form of a URL that identifies the error type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * The type of the error.
+ * This is what you should match against when implementing error handling.
+ * It is in the form of a URL that identifies the error type.
+ */
+ public void setType(String value) {
+ this.type = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRefundRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRefundRequest.java
new file mode 100644
index 0000000..dd51b06
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRefundRequest.java
@@ -0,0 +1,120 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiPaymentRefundRequest {
+
+ private AmountData amount;
+
+ private Boolean captureImmediately;
+
+ private DccData dynamicCurrencyConversion;
+
+ private String operationId;
+
+ private PaymentReferences references;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Amount to refund. If not provided, the full amount will be refunded.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount to refund. If not provided, the full amount will be refunded.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public Boolean getCaptureImmediately() {
+ return captureImmediately;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public void setCaptureImmediately(Boolean value) {
+ this.captureImmediately = value;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Payment References
+ */
+ public PaymentReferences getReferences() {
+ return references;
+ }
+
+ /**
+ * Payment References
+ */
+ public void setReferences(PaymentReferences value) {
+ this.references = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRequest.java
new file mode 100644
index 0000000..234566f
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentRequest.java
@@ -0,0 +1,152 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiPaymentRequest {
+
+ private AmountData amount;
+
+ private String authorizationType;
+
+ private CardPaymentData cardPaymentData;
+
+ private DccData dynamicCurrencyConversion;
+
+ private MerchantData merchant;
+
+ private String operationId;
+
+ private PaymentReferences references;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * The type of authorization
+ */
+ public String getAuthorizationType() {
+ return authorizationType;
+ }
+
+ /**
+ * The type of authorization
+ */
+ public void setAuthorizationType(String value) {
+ this.authorizationType = value;
+ }
+
+ /**
+ * Card data
+ */
+ public CardPaymentData getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ /**
+ * Card data
+ */
+ public void setCardPaymentData(CardPaymentData value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public MerchantData getMerchant() {
+ return merchant;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public void setMerchant(MerchantData value) {
+ this.merchant = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Payment References
+ */
+ public PaymentReferences getReferences() {
+ return references;
+ }
+
+ /**
+ * Payment References
+ */
+ public void setReferences(PaymentReferences value) {
+ this.references = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResource.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResource.java
new file mode 100644
index 0000000..d89dd9a
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResource.java
@@ -0,0 +1,149 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+public class ApiPaymentResource {
+
+ private CardPaymentDataForResource cardPaymentData;
+
+ private String initialAuthorizationCode;
+
+ private List operations;
+
+ private String paymentId;
+
+ private ApiReferencesForResponses references;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ private AmountData totalAuthorizedAmount;
+
+ public CardPaymentDataForResource getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardPaymentDataForResource value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public String getInitialAuthorizationCode() {
+ return initialAuthorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setInitialAuthorizationCode(String value) {
+ this.initialAuthorizationCode = value;
+ }
+
+ public List getOperations() {
+ return operations;
+ }
+
+ public void setOperations(List value) {
+ this.operations = value;
+ }
+
+ /**
+ * the ID of the payment
+ */
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ /**
+ * the ID of the payment
+ */
+ public void setPaymentId(String value) {
+ this.paymentId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResponse.java
new file mode 100644
index 0000000..4916bcd
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentResponse.java
@@ -0,0 +1,226 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiPaymentResponse {
+
+ private CardPaymentDataForResponse cardPaymentData;
+
+ private String initialAuthorizationCode;
+
+ private String operationId;
+
+ private String paymentId;
+
+ private ApiReferencesForResponses references;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ private AmountData totalAuthorizedAmount;
+
+ public CardPaymentDataForResponse getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardPaymentDataForResponse value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public String getInitialAuthorizationCode() {
+ return initialAuthorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setInitialAuthorizationCode(String value) {
+ this.initialAuthorizationCode = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * the ID of the payment
+ */
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ /**
+ * the ID of the payment
+ */
+ public void setPaymentId(String value) {
+ this.paymentId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentReversalRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentReversalRequest.java
new file mode 100644
index 0000000..b05c9c1
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentReversalRequest.java
@@ -0,0 +1,88 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiPaymentReversalRequest {
+
+ private DccData dynamicCurrencyConversion;
+
+ private String operationId;
+
+ private AmountData reversalAmount;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Amount to reverse. If not provided, the full amount will be reversed.
+ */
+ public AmountData getReversalAmount() {
+ return reversalAmount;
+ }
+
+ /**
+ * Amount to reverse. If not provided, the full amount will be reversed.
+ */
+ public void setReversalAmount(AmountData value) {
+ this.reversalAmount = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentSummaryForResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentSummaryForResponse.java
new file mode 100644
index 0000000..0e8586d
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiPaymentSummaryForResponse.java
@@ -0,0 +1,96 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiPaymentSummaryForResponse {
+
+ private String paymentId;
+
+ private ApiReferencesForResponses references;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ /**
+ * the ID of the payment
+ */
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ /**
+ * the ID of the payment
+ */
+ public void setPaymentId(String value) {
+ this.paymentId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReferencesForResponses.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReferencesForResponses.java
new file mode 100644
index 0000000..cdfc0ea
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReferencesForResponses.java
@@ -0,0 +1,58 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiReferencesForResponses {
+
+ private String paymentAccountReference;
+
+ private String retrievalReferenceNumber;
+
+ private String schemeTransactionId;
+
+ /**
+ * (PAR) Unique identifier associated with a specific cardholder PAN
+ */
+ public String getPaymentAccountReference() {
+ return paymentAccountReference;
+ }
+
+ /**
+ * (PAR) Unique identifier associated with a specific cardholder PAN
+ */
+ public void setPaymentAccountReference(String value) {
+ this.paymentAccountReference = value;
+ }
+
+ /**
+ * Retrieval reference number for transaction, must be AN(12) if provided
+ */
+ public String getRetrievalReferenceNumber() {
+ return retrievalReferenceNumber;
+ }
+
+ /**
+ * Retrieval reference number for transaction, must be AN(12) if provided
+ */
+ public void setRetrievalReferenceNumber(String value) {
+ this.retrievalReferenceNumber = value;
+ }
+
+ /**
+ * ID assigned by the scheme to identify a transaction through
+ * its whole lifecycle.
+ */
+ public String getSchemeTransactionId() {
+ return schemeTransactionId;
+ }
+
+ /**
+ * ID assigned by the scheme to identify a transaction through
+ * its whole lifecycle.
+ */
+ public void setSchemeTransactionId(String value) {
+ this.schemeTransactionId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundRequest.java
new file mode 100644
index 0000000..a61b753
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundRequest.java
@@ -0,0 +1,136 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiRefundRequest {
+
+ private AmountData amount;
+
+ private CardPaymentDataForRefund cardPaymentData;
+
+ private DccData dynamicCurrencyConversion;
+
+ private MerchantData merchant;
+
+ private String operationId;
+
+ private PaymentReferences references;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * Card data for refund
+ */
+ public CardPaymentDataForRefund getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ /**
+ * Card data for refund
+ */
+ public void setCardPaymentData(CardPaymentDataForRefund value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public DccData getDynamicCurrencyConversion() {
+ return dynamicCurrencyConversion;
+ }
+
+ /**
+ * Dynamic Currency Conversion (DCC) rate data from DCC lookup response.
+ * Mandatory for DCC transactions.
+ */
+ public void setDynamicCurrencyConversion(DccData value) {
+ this.dynamicCurrencyConversion = value;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public MerchantData getMerchant() {
+ return merchant;
+ }
+
+ /**
+ * Merchant Data
+ */
+ public void setMerchant(MerchantData value) {
+ this.merchant = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Payment References
+ */
+ public PaymentReferences getReferences() {
+ return references;
+ }
+
+ /**
+ * Payment References
+ */
+ public void setReferences(PaymentReferences value) {
+ this.references = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResource.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResource.java
new file mode 100644
index 0000000..159e654
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResource.java
@@ -0,0 +1,165 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+public class ApiRefundResource {
+
+ private CardPaymentDataForResource cardPaymentData;
+
+ private String initialAuthorizationCode;
+
+ private List operations;
+
+ private String referencedPaymentId;
+
+ private ApiReferencesForResponses references;
+
+ private String refundId;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ private AmountData totalAuthorizedAmount;
+
+ public CardPaymentDataForResource getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardPaymentDataForResource value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public String getInitialAuthorizationCode() {
+ return initialAuthorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setInitialAuthorizationCode(String value) {
+ this.initialAuthorizationCode = value;
+ }
+
+ public List getOperations() {
+ return operations;
+ }
+
+ public void setOperations(List value) {
+ this.operations = value;
+ }
+
+ /**
+ * The identifier of the payment referenced by this refund.
+ */
+ public String getReferencedPaymentId() {
+ return referencedPaymentId;
+ }
+
+ /**
+ * The identifier of the payment referenced by this refund.
+ */
+ public void setReferencedPaymentId(String value) {
+ this.referencedPaymentId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public String getRefundId() {
+ return refundId;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public void setRefundId(String value) {
+ this.refundId = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResponse.java
new file mode 100644
index 0000000..da81c67
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundResponse.java
@@ -0,0 +1,242 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiRefundResponse {
+
+ private String authorizationCode;
+
+ private CardPaymentDataForResource cardPaymentData;
+
+ private String operationId;
+
+ private String referencedPaymentId;
+
+ private ApiReferencesForResponses references;
+
+ private String refundId;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ private AmountData totalAuthorizedAmount;
+
+ /**
+ * Authorization approval code
+ */
+ public String getAuthorizationCode() {
+ return authorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setAuthorizationCode(String value) {
+ this.authorizationCode = value;
+ }
+
+ public CardPaymentDataForResource getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardPaymentDataForResource value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * The identifier of the payment referenced by this refund.
+ */
+ public String getReferencedPaymentId() {
+ return referencedPaymentId;
+ }
+
+ /**
+ * The identifier of the payment referenced by this refund.
+ */
+ public void setReferencedPaymentId(String value) {
+ this.referencedPaymentId = value;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public String getRefundId() {
+ return refundId;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public void setRefundId(String value) {
+ this.refundId = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundSummaryForResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundSummaryForResponse.java
new file mode 100644
index 0000000..7aeb219
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiRefundSummaryForResponse.java
@@ -0,0 +1,96 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiRefundSummaryForResponse {
+
+ private ApiReferencesForResponses references;
+
+ private String refundId;
+
+ private String retryAfter;
+
+ private String status;
+
+ private ZonedDateTime statusTimestamp;
+
+ /**
+ * A set of references returned in responses
+ */
+ public ApiReferencesForResponses getReferences() {
+ return references;
+ }
+
+ /**
+ * A set of references returned in responses
+ */
+ public void setReferences(ApiReferencesForResponses value) {
+ this.references = value;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public String getRefundId() {
+ return refundId;
+ }
+
+ /**
+ * the ID of the refund
+ */
+ public void setRefundId(String value) {
+ this.refundId = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the payment.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the payment can be retried later.
+ * PT0 means that the payment can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * The status of the payment, refund or credit transfer
+ */
+ public void setStatus(String value) {
+ this.status = value;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getStatusTimestamp() {
+ return statusTimestamp;
+ }
+
+ /**
+ * Timestamp of the status in format yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setStatusTimestamp(ZonedDateTime value) {
+ this.statusTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReversalResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReversalResponse.java
new file mode 100644
index 0000000..ceff238
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiReversalResponse.java
@@ -0,0 +1,24 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiReversalResponse extends ApiActionResponse {
+
+ private AmountData totalAuthorizedAmount;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getTotalAuthorizedAmount() {
+ return totalAuthorizedAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setTotalAuthorizedAmount(AmountData value) {
+ this.totalAuthorizedAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalRequest.java
new file mode 100644
index 0000000..7049e8b
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalRequest.java
@@ -0,0 +1,70 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class ApiTechnicalReversalRequest {
+
+ private String operationId;
+
+ private String reason;
+
+ private ZonedDateTime transactionTimestamp;
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Reason for reversal
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Reason for reversal
+ */
+ public void setReason(String value) {
+ this.reason = value;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * Timestamp of transaction in ISO 8601 format (YYYY-MM-DDThh:mm:ss+TZD)
+ * It can be expressed in merchant time zone (ex: 2023-10-10T08:00+02:00)
+ * or in UTC (ex: 2023-10-10T08:00Z)
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalResponse.java
new file mode 100644
index 0000000..d036b67
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ApiTechnicalReversalResponse.java
@@ -0,0 +1,96 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ApiTechnicalReversalResponse {
+
+ private String operationId;
+
+ private String responder;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public String getResponder() {
+ return responder;
+ }
+
+ /**
+ * The party that originated the response
+ */
+ public void setResponder(String value) {
+ this.responder = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardDataForDcc.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardDataForDcc.java
new file mode 100644
index 0000000..87afbd7
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardDataForDcc.java
@@ -0,0 +1,64 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardDataForDcc {
+
+ private String bin;
+
+ private String brand;
+
+ private String cardCountryCode;
+
+ /**
+ * Used to determine the currency of the card.
+ * The first 12 digits of the card number.
+ * The BIN number is on the first 6 or 8 digits.
+ * Some issuers are using subranges for different countries on digits
+ * 9-12.
+ */
+ public String getBin() {
+ return bin;
+ }
+
+ /**
+ * Used to determine the currency of the card.
+ * The first 12 digits of the card number.
+ * The BIN number is on the first 6 or 8 digits.
+ * Some issuers are using subranges for different countries on digits
+ * 9-12.
+ */
+ public void setBin(String value) {
+ this.bin = value;
+ }
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ /**
+ * The country code of the card
+ */
+ public String getCardCountryCode() {
+ return cardCountryCode;
+ }
+
+ /**
+ * The country code of the card
+ */
+ public void setCardCountryCode(String value) {
+ this.cardCountryCode = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardOnFileData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardOnFileData.java
new file mode 100644
index 0000000..d411d7d
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardOnFileData.java
@@ -0,0 +1,44 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardOnFileData {
+
+ private InitialCardOnFileData initialCardOnFileData;
+
+ private Boolean isInitialTransaction;
+
+ private SubsequentCardOnFileData subsequentCardOnFileData;
+
+ public InitialCardOnFileData getInitialCardOnFileData() {
+ return initialCardOnFileData;
+ }
+
+ public void setInitialCardOnFileData(InitialCardOnFileData value) {
+ this.initialCardOnFileData = value;
+ }
+
+ /**
+ * Indicate wether this is the initial Card on File transaction or not
+ */
+ public Boolean getIsInitialTransaction() {
+ return isInitialTransaction;
+ }
+
+ /**
+ * Indicate wether this is the initial Card on File transaction or not
+ */
+ public void setIsInitialTransaction(Boolean value) {
+ this.isInitialTransaction = value;
+ }
+
+ public SubsequentCardOnFileData getSubsequentCardOnFileData() {
+ return subsequentCardOnFileData;
+ }
+
+ public void setSubsequentCardOnFileData(SubsequentCardOnFileData value) {
+ this.subsequentCardOnFileData = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentData.java
new file mode 100644
index 0000000..ba15f6a
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentData.java
@@ -0,0 +1,176 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardPaymentData {
+
+ private Boolean allowPartialApproval;
+
+ private String brand;
+
+ private Boolean captureImmediately;
+
+ private PlainCardData cardData;
+
+ private String cardEntryMode;
+
+ private CardOnFileData cardOnFileData;
+
+ private String cardholderVerificationMethod;
+
+ private ECommerceData ecommerceData;
+
+ private NetworkTokenData networkTokenData;
+
+ private PointOfSaleData pointOfSaleData;
+
+ private String walletId;
+
+ /**
+ * Indicate wether you allow partial approval or not
+ */
+ public Boolean getAllowPartialApproval() {
+ return allowPartialApproval;
+ }
+
+ /**
+ * Indicate wether you allow partial approval or not
+ */
+ public void setAllowPartialApproval(Boolean value) {
+ this.allowPartialApproval = value;
+ }
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public Boolean getCaptureImmediately() {
+ return captureImmediately;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public void setCaptureImmediately(Boolean value) {
+ this.captureImmediately = value;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public PlainCardData getCardData() {
+ return cardData;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public void setCardData(PlainCardData value) {
+ this.cardData = value;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public String getCardEntryMode() {
+ return cardEntryMode;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public void setCardEntryMode(String value) {
+ this.cardEntryMode = value;
+ }
+
+ public CardOnFileData getCardOnFileData() {
+ return cardOnFileData;
+ }
+
+ public void setCardOnFileData(CardOnFileData value) {
+ this.cardOnFileData = value;
+ }
+
+ /**
+ * Cardholder verification method used in the transaction
+ */
+ public String getCardholderVerificationMethod() {
+ return cardholderVerificationMethod;
+ }
+
+ /**
+ * Cardholder verification method used in the transaction
+ */
+ public void setCardholderVerificationMethod(String value) {
+ this.cardholderVerificationMethod = value;
+ }
+
+ /**
+ * Request data for eCommerce and MOTO transactions
+ */
+ public ECommerceData getEcommerceData() {
+ return ecommerceData;
+ }
+
+ /**
+ * Request data for eCommerce and MOTO transactions
+ */
+ public void setEcommerceData(ECommerceData value) {
+ this.ecommerceData = value;
+ }
+
+ public NetworkTokenData getNetworkTokenData() {
+ return networkTokenData;
+ }
+
+ public void setNetworkTokenData(NetworkTokenData value) {
+ this.networkTokenData = value;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public PointOfSaleData getPointOfSaleData() {
+ return pointOfSaleData;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public void setPointOfSaleData(PointOfSaleData value) {
+ this.pointOfSaleData = value;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public String getWalletId() {
+ return walletId;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public void setWalletId(String value) {
+ this.walletId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForRefund.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForRefund.java
new file mode 100644
index 0000000..a30c157
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForRefund.java
@@ -0,0 +1,118 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardPaymentDataForRefund {
+
+ private String brand;
+
+ private Boolean captureImmediately;
+
+ private PlainCardData cardData;
+
+ private String cardEntryMode;
+
+ private NetworkTokenData networkTokenData;
+
+ private PointOfSaleData pointOfSaleData;
+
+ private String walletId;
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public Boolean getCaptureImmediately() {
+ return captureImmediately;
+ }
+
+ /**
+ * If true the transaction will be authorized and captured immediately
+ */
+ public void setCaptureImmediately(Boolean value) {
+ this.captureImmediately = value;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public PlainCardData getCardData() {
+ return cardData;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public void setCardData(PlainCardData value) {
+ this.cardData = value;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public String getCardEntryMode() {
+ return cardEntryMode;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public void setCardEntryMode(String value) {
+ this.cardEntryMode = value;
+ }
+
+ public NetworkTokenData getNetworkTokenData() {
+ return networkTokenData;
+ }
+
+ public void setNetworkTokenData(NetworkTokenData value) {
+ this.networkTokenData = value;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public PointOfSaleData getPointOfSaleData() {
+ return pointOfSaleData;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public void setPointOfSaleData(PointOfSaleData value) {
+ this.pointOfSaleData = value;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public String getWalletId() {
+ return walletId;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public void setWalletId(String value) {
+ this.walletId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResource.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResource.java
new file mode 100644
index 0000000..c7d3d24
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResource.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardPaymentDataForResource {
+
+ private String brand;
+
+ private PointOfSaleData pointOfSaleData;
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public PointOfSaleData getPointOfSaleData() {
+ return pointOfSaleData;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public void setPointOfSaleData(PointOfSaleData value) {
+ this.pointOfSaleData = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResponse.java
new file mode 100644
index 0000000..cac9252
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForResponse.java
@@ -0,0 +1,50 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardPaymentDataForResponse {
+
+ private String brand;
+
+ private ECommerceDataForResponse ecommerceData;
+
+ private PointOfSaleData pointOfSaleData;
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ public ECommerceDataForResponse getEcommerceData() {
+ return ecommerceData;
+ }
+
+ public void setEcommerceData(ECommerceDataForResponse value) {
+ this.ecommerceData = value;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public PointOfSaleData getPointOfSaleData() {
+ return pointOfSaleData;
+ }
+
+ /**
+ * Payment terminal request data
+ */
+ public void setPointOfSaleData(PointOfSaleData value) {
+ this.pointOfSaleData = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForVerification.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForVerification.java
new file mode 100644
index 0000000..381521f
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/CardPaymentDataForVerification.java
@@ -0,0 +1,128 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class CardPaymentDataForVerification {
+
+ private String brand;
+
+ private PlainCardData cardData;
+
+ private String cardEntryMode;
+
+ private CardOnFileData cardOnFileData;
+
+ private String cardholderVerificationMethod;
+
+ private ECommerceDataForAccountVerification ecommerceData;
+
+ private NetworkTokenData networkTokenData;
+
+ private String walletId;
+
+ /**
+ * The card brand
+ */
+ public String getBrand() {
+ return brand;
+ }
+
+ /**
+ * The card brand
+ */
+ public void setBrand(String value) {
+ this.brand = value;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public PlainCardData getCardData() {
+ return cardData;
+ }
+
+ /**
+ * Card data in plain text
+ */
+ public void setCardData(PlainCardData value) {
+ this.cardData = value;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public String getCardEntryMode() {
+ return cardEntryMode;
+ }
+
+ /**
+ * Card entry mode used in the transaction, defaults to ECOMMERCE
+ */
+ public void setCardEntryMode(String value) {
+ this.cardEntryMode = value;
+ }
+
+ public CardOnFileData getCardOnFileData() {
+ return cardOnFileData;
+ }
+
+ public void setCardOnFileData(CardOnFileData value) {
+ this.cardOnFileData = value;
+ }
+
+ /**
+ * Cardholder verification method used in the transaction
+ */
+ public String getCardholderVerificationMethod() {
+ return cardholderVerificationMethod;
+ }
+
+ /**
+ * Cardholder verification method used in the transaction
+ */
+ public void setCardholderVerificationMethod(String value) {
+ this.cardholderVerificationMethod = value;
+ }
+
+ /**
+ * Request data for eCommerce and MOTO transactions
+ */
+ public ECommerceDataForAccountVerification getEcommerceData() {
+ return ecommerceData;
+ }
+
+ /**
+ * Request data for eCommerce and MOTO transactions
+ */
+ public void setEcommerceData(ECommerceDataForAccountVerification value) {
+ this.ecommerceData = value;
+ }
+
+ public NetworkTokenData getNetworkTokenData() {
+ return networkTokenData;
+ }
+
+ public void setNetworkTokenData(NetworkTokenData value) {
+ this.networkTokenData = value;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public String getWalletId() {
+ return walletId;
+ }
+
+ /**
+ * Type of wallet, values are assigned by card schemes, e.g. 101
+ * for MasterPass in eCommerce, 102 for MasterPass NFC, 103 for Apple Pay,
+ * 216 for Google Pay and 217 for Samsung Pay
+ */
+ public void setWalletId(String value) {
+ this.walletId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccData.java
new file mode 100644
index 0000000..cd31db6
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccData.java
@@ -0,0 +1,80 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.math.BigDecimal;
+
+public class DccData {
+
+ private Long amount;
+
+ private BigDecimal conversionRate;
+
+ private String currencyCode;
+
+ private Integer numberOfDecimals;
+
+ /**
+ * Amount of transaction formatted according to card scheme
+ * specifications.
+ * E.g. 100 for 1.00 EUR. Either this or amount must be present.
+ */
+ public Long getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount of transaction formatted according to card scheme
+ * specifications.
+ * E.g. 100 for 1.00 EUR. Either this or amount must be present.
+ */
+ public void setAmount(Long value) {
+ this.amount = value;
+ }
+
+ /**
+ * Currency conversion rate in decimal notation.
+ * Either this or isoConversionRate must be present
+ */
+ public BigDecimal getConversionRate() {
+ return conversionRate;
+ }
+
+ /**
+ * Currency conversion rate in decimal notation.
+ * Either this or isoConversionRate must be present
+ */
+ public void setConversionRate(BigDecimal value) {
+ this.conversionRate = value;
+ }
+
+ /**
+ * Alpha-numeric ISO 4217 currency code for transaction, e.g. EUR
+ */
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ /**
+ * Alpha-numeric ISO 4217 currency code for transaction, e.g. EUR
+ */
+ public void setCurrencyCode(String value) {
+ this.currencyCode = value;
+ }
+
+ /**
+ * Number of decimals in the amount
+ */
+ public Integer getNumberOfDecimals() {
+ return numberOfDecimals;
+ }
+
+ /**
+ * Number of decimals in the amount
+ */
+ public void setNumberOfDecimals(Integer value) {
+ this.numberOfDecimals = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccProposal.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccProposal.java
new file mode 100644
index 0000000..d0c926d
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/DccProposal.java
@@ -0,0 +1,66 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class DccProposal {
+
+ private AmountData originalAmount;
+
+ private RateData rate;
+
+ private String rateReferenceId;
+
+ private AmountData resultingAmount;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getOriginalAmount() {
+ return originalAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setOriginalAmount(AmountData value) {
+ this.originalAmount = value;
+ }
+
+ public RateData getRate() {
+ return rate;
+ }
+
+ public void setRate(RateData value) {
+ this.rate = value;
+ }
+
+ /**
+ * The rate reference ID
+ */
+ public String getRateReferenceId() {
+ return rateReferenceId;
+ }
+
+ /**
+ * The rate reference ID
+ */
+ public void setRateReferenceId(String value) {
+ this.rateReferenceId = value;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getResultingAmount() {
+ return resultingAmount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setResultingAmount(AmountData value) {
+ this.resultingAmount = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceData.java
new file mode 100644
index 0000000..2b6969f
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceData.java
@@ -0,0 +1,60 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ECommerceData {
+
+ private AddressVerificationData addressVerificationData;
+
+ private String scaExemptionRequest;
+
+ private ThreeDSecure threeDSecure;
+
+ /**
+ * Address Verification System data
+ */
+ public AddressVerificationData getAddressVerificationData() {
+ return addressVerificationData;
+ }
+
+ /**
+ * Address Verification System data
+ */
+ public void setAddressVerificationData(AddressVerificationData value) {
+ this.addressVerificationData = value;
+ }
+
+ /**
+ * Strong customer authentication exemption request
+ */
+ public String getScaExemptionRequest() {
+ return scaExemptionRequest;
+ }
+
+ /**
+ * Strong customer authentication exemption request
+ */
+ public void setScaExemptionRequest(String value) {
+ this.scaExemptionRequest = value;
+ }
+
+ /**
+ * 3D Secure data.
+ * Please note that if AAV or CAVV or equivalent is
+ * missing, transaction should not be flagged as 3D Secure.
+ */
+ public ThreeDSecure getThreeDSecure() {
+ return threeDSecure;
+ }
+
+ /**
+ * 3D Secure data.
+ * Please note that if AAV or CAVV or equivalent is
+ * missing, transaction should not be flagged as 3D Secure.
+ */
+ public void setThreeDSecure(ThreeDSecure value) {
+ this.threeDSecure = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForAccountVerification.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForAccountVerification.java
new file mode 100644
index 0000000..3ca8e14
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForAccountVerification.java
@@ -0,0 +1,44 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ECommerceDataForAccountVerification {
+
+ private AddressVerificationData addressVerificationData;
+
+ private ThreeDSecure threeDSecure;
+
+ /**
+ * Address Verification System data
+ */
+ public AddressVerificationData getAddressVerificationData() {
+ return addressVerificationData;
+ }
+
+ /**
+ * Address Verification System data
+ */
+ public void setAddressVerificationData(AddressVerificationData value) {
+ this.addressVerificationData = value;
+ }
+
+ /**
+ * 3D Secure data.
+ * Please note that if AAV or CAVV or equivalent is
+ * missing, transaction should not be flagged as 3D Secure.
+ */
+ public ThreeDSecure getThreeDSecure() {
+ return threeDSecure;
+ }
+
+ /**
+ * 3D Secure data.
+ * Please note that if AAV or CAVV or equivalent is
+ * missing, transaction should not be flagged as 3D Secure.
+ */
+ public void setThreeDSecure(ThreeDSecure value) {
+ this.threeDSecure = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForResponse.java
new file mode 100644
index 0000000..b5085d5
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ECommerceDataForResponse.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ECommerceDataForResponse {
+
+ private String addressVerificationResult;
+
+ private String cardSecurityCodeResult;
+
+ /**
+ * Result of Address Verification Result
+ */
+ public String getAddressVerificationResult() {
+ return addressVerificationResult;
+ }
+
+ /**
+ * Result of Address Verification Result
+ */
+ public void setAddressVerificationResult(String value) {
+ this.addressVerificationResult = value;
+ }
+
+ /**
+ * Result of card security code check
+ */
+ public String getCardSecurityCodeResult() {
+ return cardSecurityCodeResult;
+ }
+
+ /**
+ * Result of card security code check
+ */
+ public void setCardSecurityCodeResult(String value) {
+ this.cardSecurityCodeResult = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDCCRateRequest.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDCCRateRequest.java
new file mode 100644
index 0000000..50d4a10
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDCCRateRequest.java
@@ -0,0 +1,90 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class GetDCCRateRequest {
+
+ private CardDataForDcc cardPaymentData;
+
+ private String operationId;
+
+ private PointOfSaleDataForDcc pointOfSaleData;
+
+ private String rateReferenceId;
+
+ private String targetCurrency;
+
+ private TransactionDataForDcc transaction;
+
+ public CardDataForDcc getCardPaymentData() {
+ return cardPaymentData;
+ }
+
+ public void setCardPaymentData(CardDataForDcc value) {
+ this.cardPaymentData = value;
+ }
+
+ /**
+ * A unique identifier of the operation, generated by the client.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A unique identifier of the operation, generated by the client.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ public PointOfSaleDataForDcc getPointOfSaleData() {
+ return pointOfSaleData;
+ }
+
+ public void setPointOfSaleData(PointOfSaleDataForDcc value) {
+ this.pointOfSaleData = value;
+ }
+
+ /**
+ * The reference of a previously used rate
+ * This can be used in case of refund if you want to use the same rate
+ * as the original transaction.
+ */
+ public String getRateReferenceId() {
+ return rateReferenceId;
+ }
+
+ /**
+ * The reference of a previously used rate
+ * This can be used in case of refund if you want to use the same rate
+ * as the original transaction.
+ */
+ public void setRateReferenceId(String value) {
+ this.rateReferenceId = value;
+ }
+
+ /**
+ * The currency to convert to
+ */
+ public String getTargetCurrency() {
+ return targetCurrency;
+ }
+
+ /**
+ * The currency to convert to
+ */
+ public void setTargetCurrency(String value) {
+ this.targetCurrency = value;
+ }
+
+ public TransactionDataForDcc getTransaction() {
+ return transaction;
+ }
+
+ public void setTransaction(TransactionDataForDcc value) {
+ this.transaction = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDccRateResponse.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDccRateResponse.java
new file mode 100644
index 0000000..130f29d
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/GetDccRateResponse.java
@@ -0,0 +1,66 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class GetDccRateResponse {
+
+ private String disclaimerDisplay;
+
+ private String disclaimerReceipt;
+
+ private DccProposal proposal;
+
+ private String result;
+
+ /**
+ * The disclaimer display
+ */
+ public String getDisclaimerDisplay() {
+ return disclaimerDisplay;
+ }
+
+ /**
+ * The disclaimer display
+ */
+ public void setDisclaimerDisplay(String value) {
+ this.disclaimerDisplay = value;
+ }
+
+ /**
+ * The disclaimer receipt
+ */
+ public String getDisclaimerReceipt() {
+ return disclaimerReceipt;
+ }
+
+ /**
+ * The disclaimer receipt
+ */
+ public void setDisclaimerReceipt(String value) {
+ this.disclaimerReceipt = value;
+ }
+
+ public DccProposal getProposal() {
+ return proposal;
+ }
+
+ public void setProposal(DccProposal value) {
+ this.proposal = value;
+ }
+
+ /**
+ * The result of the operation
+ */
+ public String getResult() {
+ return result;
+ }
+
+ /**
+ * The result of the operation
+ */
+ public void setResult(String value) {
+ this.result = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/InitialCardOnFileData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/InitialCardOnFileData.java
new file mode 100644
index 0000000..4c6f7a3
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/InitialCardOnFileData.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class InitialCardOnFileData {
+
+ private String futureUse;
+
+ private String transactionType;
+
+ /**
+ * Future use
+ */
+ public String getFutureUse() {
+ return futureUse;
+ }
+
+ /**
+ * Future use
+ */
+ public void setFutureUse(String value) {
+ this.futureUse = value;
+ }
+
+ /**
+ * Transaction type
+ */
+ public String getTransactionType() {
+ return transactionType;
+ }
+
+ /**
+ * Transaction type
+ */
+ public void setTransactionType(String value) {
+ this.transactionType = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/MerchantData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/MerchantData.java
new file mode 100644
index 0000000..cc71eb9
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/MerchantData.java
@@ -0,0 +1,120 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class MerchantData {
+
+ private String address;
+
+ private String city;
+
+ private String countryCode;
+
+ private Integer merchantCategoryCode;
+
+ private String name;
+
+ private String postalCode;
+
+ private String stateCode;
+
+ /**
+ * Street address
+ */
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ * Street address
+ */
+ public void setAddress(String value) {
+ this.address = value;
+ }
+
+ /**
+ * Address city
+ */
+ public String getCity() {
+ return city;
+ }
+
+ /**
+ * Address city
+ */
+ public void setCity(String value) {
+ this.city = value;
+ }
+
+ /**
+ * Address country code, ISO 3166 international standard
+ */
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ /**
+ * Address country code, ISO 3166 international standard
+ */
+ public void setCountryCode(String value) {
+ this.countryCode = value;
+ }
+
+ /**
+ * Merchant category code (MCC)
+ */
+ public Integer getMerchantCategoryCode() {
+ return merchantCategoryCode;
+ }
+
+ /**
+ * Merchant category code (MCC)
+ */
+ public void setMerchantCategoryCode(Integer value) {
+ this.merchantCategoryCode = value;
+ }
+
+ /**
+ * Merchant name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Merchant name
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ /**
+ * Address postal code
+ */
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ /**
+ * Address postal code
+ */
+ public void setPostalCode(String value) {
+ this.postalCode = value;
+ }
+
+ /**
+ * Address state code, only supplied if country is US or CA
+ */
+ public String getStateCode() {
+ return stateCode;
+ }
+
+ /**
+ * Address state code, only supplied if country is US or CA
+ */
+ public void setStateCode(String value) {
+ this.stateCode = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/NetworkTokenData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/NetworkTokenData.java
new file mode 100644
index 0000000..4b9a3da
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/NetworkTokenData.java
@@ -0,0 +1,46 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class NetworkTokenData {
+
+ private String cryptogram;
+
+ private String eci;
+
+ /**
+ * Network token cryptogram
+ */
+ public String getCryptogram() {
+ return cryptogram;
+ }
+
+ /**
+ * Network token cryptogram
+ */
+ public void setCryptogram(String value) {
+ this.cryptogram = value;
+ }
+
+ /**
+ * Electronic Commerce Indicator
+ * Value returned by the 3D Secure process that indicates the level of
+ * authentication.
+ * Contains different values depending on the brand.
+ */
+ public String getEci() {
+ return eci;
+ }
+
+ /**
+ * Electronic Commerce Indicator
+ * Value returned by the 3D Secure process that indicates the level of
+ * authentication.
+ * Contains different values depending on the brand.
+ */
+ public void setEci(String value) {
+ this.eci = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PaymentReferences.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PaymentReferences.java
new file mode 100644
index 0000000..f59d8a8
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PaymentReferences.java
@@ -0,0 +1,62 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class PaymentReferences {
+
+ private String dynamicDescriptor;
+
+ private String merchantReference;
+
+ private String retrievalReferenceNumber;
+
+ /**
+ * Dynamic descriptor gives you the ability to control the descriptor
+ * on the credit card statement of the customer.
+ */
+ public String getDynamicDescriptor() {
+ return dynamicDescriptor;
+ }
+
+ /**
+ * Dynamic descriptor gives you the ability to control the descriptor
+ * on the credit card statement of the customer.
+ */
+ public void setDynamicDescriptor(String value) {
+ this.dynamicDescriptor = value;
+ }
+
+ /**
+ * Reference for the transaction to allow the merchant to reconcile their payments in our report files.
+ * It is advised to submit a unique value per transaction.
+ * The value provided here is returned in the baseTrxType/addlMercData element of the MRX file.
+ */
+ public String getMerchantReference() {
+ return merchantReference;
+ }
+
+ /**
+ * Reference for the transaction to allow the merchant to reconcile their payments in our report files.
+ * It is advised to submit a unique value per transaction.
+ * The value provided here is returned in the baseTrxType/addlMercData element of the MRX file.
+ */
+ public void setMerchantReference(String value) {
+ this.merchantReference = value;
+ }
+
+ /**
+ * Retrieval reference number for transaction, must be AN(12) if provided
+ */
+ public String getRetrievalReferenceNumber() {
+ return retrievalReferenceNumber;
+ }
+
+ /**
+ * Retrieval reference number for transaction, must be AN(12) if provided
+ */
+ public void setRetrievalReferenceNumber(String value) {
+ this.retrievalReferenceNumber = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PlainCardData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PlainCardData.java
new file mode 100644
index 0000000..4c09d84
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PlainCardData.java
@@ -0,0 +1,60 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class PlainCardData {
+
+ private String cardNumber;
+
+ private String cardSecurityCode;
+
+ private String expiryDate;
+
+ /**
+ * Card number (PAN, network token or DPAN).
+ */
+ public String getCardNumber() {
+ return cardNumber;
+ }
+
+ /**
+ * Card number (PAN, network token or DPAN).
+ */
+ public void setCardNumber(String value) {
+ this.cardNumber = value;
+ }
+
+ /**
+ * The security code indicated on the card
+ * Based on the card brand, it can be 3 or 4 digits long
+ * and have different names: CVV2, CVC2, CVN2, CID, CVC, CAV2, etc.
+ */
+ public String getCardSecurityCode() {
+ return cardSecurityCode;
+ }
+
+ /**
+ * The security code indicated on the card
+ * Based on the card brand, it can be 3 or 4 digits long
+ * and have different names: CVV2, CVC2, CVN2, CID, CVC, CAV2, etc.
+ */
+ public void setCardSecurityCode(String value) {
+ this.cardSecurityCode = value;
+ }
+
+ /**
+ * Card or token expiry date in format MMYYYY
+ */
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ /**
+ * Card or token expiry date in format MMYYYY
+ */
+ public void setExpiryDate(String value) {
+ this.expiryDate = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleData.java
new file mode 100644
index 0000000..fc1c5a0
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleData.java
@@ -0,0 +1,24 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class PointOfSaleData {
+
+ private String terminalId;
+
+ /**
+ * Terminal ID ANS(8)
+ */
+ public String getTerminalId() {
+ return terminalId;
+ }
+
+ /**
+ * Terminal ID ANS(8)
+ */
+ public void setTerminalId(String value) {
+ this.terminalId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleDataForDcc.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleDataForDcc.java
new file mode 100644
index 0000000..7211f31
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/PointOfSaleDataForDcc.java
@@ -0,0 +1,40 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class PointOfSaleDataForDcc {
+
+ private String terminalCountryCode;
+
+ private String terminalId;
+
+ /**
+ * Country code of the terminal
+ */
+ public String getTerminalCountryCode() {
+ return terminalCountryCode;
+ }
+
+ /**
+ * Country code of the terminal
+ */
+ public void setTerminalCountryCode(String value) {
+ this.terminalCountryCode = value;
+ }
+
+ /**
+ * The terminal ID
+ */
+ public String getTerminalId() {
+ return terminalId;
+ }
+
+ /**
+ * The terminal ID
+ */
+ public void setTerminalId(String value) {
+ this.terminalId = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/RateData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/RateData.java
new file mode 100644
index 0000000..6a0b6ab
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/RateData.java
@@ -0,0 +1,97 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.math.BigDecimal;
+import java.time.ZonedDateTime;
+
+public class RateData {
+
+ private BigDecimal exchangeRate;
+
+ private BigDecimal invertedExchangeRate;
+
+ private BigDecimal markUp;
+
+ private String markUpBasis;
+
+ private ZonedDateTime quotationDateTime;
+
+ /**
+ * The exchange rate
+ */
+ public BigDecimal getExchangeRate() {
+ return exchangeRate;
+ }
+
+ /**
+ * The exchange rate
+ */
+ public void setExchangeRate(BigDecimal value) {
+ this.exchangeRate = value;
+ }
+
+ /**
+ * The inverted exchange rate
+ */
+ public BigDecimal getInvertedExchangeRate() {
+ return invertedExchangeRate;
+ }
+
+ /**
+ * The inverted exchange rate
+ */
+ public void setInvertedExchangeRate(BigDecimal value) {
+ this.invertedExchangeRate = value;
+ }
+
+ /**
+ * The mark up applied on the rate (in percentage).
+ */
+ public BigDecimal getMarkUp() {
+ return markUp;
+ }
+
+ /**
+ * The mark up applied on the rate (in percentage).
+ */
+ public void setMarkUp(BigDecimal value) {
+ this.markUp = value;
+ }
+
+ /**
+ * The source of the rate the markup is based upon.
+ * If the cardholder and the merchant are based in Europe, the
+ * mark up is calculated based on the
+ * rates provided by the European Central Bank.
+ */
+ public String getMarkUpBasis() {
+ return markUpBasis;
+ }
+
+ /**
+ * The source of the rate the markup is based upon.
+ * If the cardholder and the merchant are based in Europe, the
+ * mark up is calculated based on the
+ * rates provided by the European Central Bank.
+ */
+ public void setMarkUpBasis(String value) {
+ this.markUpBasis = value;
+ }
+
+ /**
+ * The date and time of the quotation
+ */
+ public ZonedDateTime getQuotationDateTime() {
+ return quotationDateTime;
+ }
+
+ /**
+ * The date and time of the quotation
+ */
+ public void setQuotationDateTime(ZonedDateTime value) {
+ this.quotationDateTime = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperation.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperation.java
new file mode 100644
index 0000000..067d44c
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperation.java
@@ -0,0 +1,170 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class SubOperation {
+
+ private AmountData amount;
+
+ private String authorizationCode;
+
+ private String operationId;
+
+ private ZonedDateTime operationTimestamp;
+
+ private String operationType;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ private String retryAfter;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public String getAuthorizationCode() {
+ return authorizationCode;
+ }
+
+ /**
+ * Authorization approval code
+ */
+ public void setAuthorizationCode(String value) {
+ this.authorizationCode = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Timestamp of the operation in merchant time zone in format
+ * yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getOperationTimestamp() {
+ return operationTimestamp;
+ }
+
+ /**
+ * Timestamp of the operation in merchant time zone in format
+ * yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setOperationTimestamp(ZonedDateTime value) {
+ this.operationTimestamp = value;
+ }
+
+ /**
+ * The kind of operation
+ */
+ public String getOperationType() {
+ return operationType;
+ }
+
+ /**
+ * The kind of operation
+ */
+ public void setOperationType(String value) {
+ this.operationType = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the operation.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the operation can be retried later.
+ * PT0 means that the operation can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the operation.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the operation can be retried later.
+ * PT0 means that the operation can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperationForRefund.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperationForRefund.java
new file mode 100644
index 0000000..3f8d71c
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubOperationForRefund.java
@@ -0,0 +1,154 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class SubOperationForRefund {
+
+ private AmountData amount;
+
+ private String operationId;
+
+ private ZonedDateTime operationTimestamp;
+
+ private String operationType;
+
+ private String responseCode;
+
+ private String responseCodeCategory;
+
+ private String responseCodeDescription;
+
+ private String retryAfter;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public String getOperationId() {
+ return operationId;
+ }
+
+ /**
+ * A globally unique identifier of the operation, generated by you.
+ * We advise you to submit a UUID or an identifier composed of an arbitrary string
+ * and a UUID/URL-safe Base64 UUID (RFC 4648 §5).
+ * It's used to detect duplicate requests or to reference an operation in
+ * technical reversals.
+ */
+ public void setOperationId(String value) {
+ this.operationId = value;
+ }
+
+ /**
+ * Timestamp of the operation in merchant time zone in format
+ * yyyy-MM-ddTHH:mm:ssZ
+ */
+ public ZonedDateTime getOperationTimestamp() {
+ return operationTimestamp;
+ }
+
+ /**
+ * Timestamp of the operation in merchant time zone in format
+ * yyyy-MM-ddTHH:mm:ssZ
+ */
+ public void setOperationTimestamp(ZonedDateTime value) {
+ this.operationTimestamp = value;
+ }
+
+ /**
+ * The kind of operation
+ */
+ public String getOperationType() {
+ return operationType;
+ }
+
+ /**
+ * The kind of operation
+ */
+ public void setOperationType(String value) {
+ this.operationType = value;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public String getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Numeric response code, e.g. 0000, 0005
+ */
+ public void setResponseCode(String value) {
+ this.responseCode = value;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public String getResponseCodeCategory() {
+ return responseCodeCategory;
+ }
+
+ /**
+ * Category of response code.
+ */
+ public void setResponseCodeCategory(String value) {
+ this.responseCodeCategory = value;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public String getResponseCodeDescription() {
+ return responseCodeDescription;
+ }
+
+ /**
+ * Description of the response code
+ */
+ public void setResponseCodeDescription(String value) {
+ this.responseCodeDescription = value;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the operation.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the operation can be retried later.
+ * PT0 means that the operation can be retried immediately.
+ */
+ public String getRetryAfter() {
+ return retryAfter;
+ }
+
+ /**
+ * The duration to wait after the initial submission before retrying the operation.
+ * Expressed using ISO 8601 duration format, ex: PT2H for 2 hours.
+ * This field is only present when the operation can be retried later.
+ * PT0 means that the operation can be retried immediately.
+ */
+ public void setRetryAfter(String value) {
+ this.retryAfter = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubsequentCardOnFileData.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubsequentCardOnFileData.java
new file mode 100644
index 0000000..be3d5ed
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/SubsequentCardOnFileData.java
@@ -0,0 +1,56 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class SubsequentCardOnFileData {
+
+ private String cardOnFileInitiator;
+
+ private String initialSchemeTransactionId;
+
+ private String transactionType;
+
+ /**
+ * Card on file initiator
+ */
+ public String getCardOnFileInitiator() {
+ return cardOnFileInitiator;
+ }
+
+ /**
+ * Card on file initiator
+ */
+ public void setCardOnFileInitiator(String value) {
+ this.cardOnFileInitiator = value;
+ }
+
+ /**
+ * Scheme transaction ID of initial transaction
+ */
+ public String getInitialSchemeTransactionId() {
+ return initialSchemeTransactionId;
+ }
+
+ /**
+ * Scheme transaction ID of initial transaction
+ */
+ public void setInitialSchemeTransactionId(String value) {
+ this.initialSchemeTransactionId = value;
+ }
+
+ /**
+ * Transaction type
+ */
+ public String getTransactionType() {
+ return transactionType;
+ }
+
+ /**
+ * Transaction type
+ */
+ public void setTransactionType(String value) {
+ this.transactionType = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ThreeDSecure.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ThreeDSecure.java
new file mode 100644
index 0000000..a6ea195
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/ThreeDSecure.java
@@ -0,0 +1,96 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+public class ThreeDSecure {
+
+ private String authenticationValue;
+
+ private String directoryServerTransactionId;
+
+ private String eci;
+
+ private String threeDSecureType;
+
+ private String version;
+
+ /**
+ * MasterCard AAV in original base64 encoding or Visa, DinersClub,
+ * UnionPay or JCB CAVV in either hexadecimal or base64 encoding
+ */
+ public String getAuthenticationValue() {
+ return authenticationValue;
+ }
+
+ /**
+ * MasterCard AAV in original base64 encoding or Visa, DinersClub,
+ * UnionPay or JCB CAVV in either hexadecimal or base64 encoding
+ */
+ public void setAuthenticationValue(String value) {
+ this.authenticationValue = value;
+ }
+
+ /**
+ * 3D Secure 2.x directory server transaction ID
+ */
+ public String getDirectoryServerTransactionId() {
+ return directoryServerTransactionId;
+ }
+
+ /**
+ * 3D Secure 2.x directory server transaction ID
+ */
+ public void setDirectoryServerTransactionId(String value) {
+ this.directoryServerTransactionId = value;
+ }
+
+ /**
+ * Electronic Commerce Indicator
+ * Value returned by the 3D Secure process that indicates the level of
+ * authentication.
+ * Contains different values depending on the brand.
+ */
+ public String getEci() {
+ return eci;
+ }
+
+ /**
+ * Electronic Commerce Indicator
+ * Value returned by the 3D Secure process that indicates the level of
+ * authentication.
+ * Contains different values depending on the brand.
+ */
+ public void setEci(String value) {
+ this.eci = value;
+ }
+
+ /**
+ * 3D Secure type used in the transaction
+ */
+ public String getThreeDSecureType() {
+ return threeDSecureType;
+ }
+
+ /**
+ * 3D Secure type used in the transaction
+ */
+ public void setThreeDSecureType(String value) {
+ this.threeDSecureType = value;
+ }
+
+ /**
+ * 3D Secure version
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * 3D Secure version
+ */
+ public void setVersion(String value) {
+ this.version = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/TransactionDataForDcc.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/TransactionDataForDcc.java
new file mode 100644
index 0000000..91a4e14
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/domain/TransactionDataForDcc.java
@@ -0,0 +1,58 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.domain;
+
+import java.time.ZonedDateTime;
+
+public class TransactionDataForDcc {
+
+ private AmountData amount;
+
+ private ZonedDateTime transactionTimestamp;
+
+ private String transactionType;
+
+ /**
+ * Amount for the operation.
+ */
+ public AmountData getAmount() {
+ return amount;
+ }
+
+ /**
+ * Amount for the operation.
+ */
+ public void setAmount(AmountData value) {
+ this.amount = value;
+ }
+
+ /**
+ * The date and time of the transaction
+ */
+ public ZonedDateTime getTransactionTimestamp() {
+ return transactionTimestamp;
+ }
+
+ /**
+ * The date and time of the transaction
+ */
+ public void setTransactionTimestamp(ZonedDateTime value) {
+ this.transactionTimestamp = value;
+ }
+
+ /**
+ * The transaction type
+ */
+ public String getTransactionType() {
+ return transactionType;
+ }
+
+ /**
+ * The transaction type
+ */
+ public void setTransactionType(String value) {
+ this.transactionType = value;
+ }
+}
diff --git a/src/main/generated/com/worldline/acquiring/sdk/java/v1/ping/PingClient.java b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ping/PingClient.java
new file mode 100644
index 0000000..ff3b03b
--- /dev/null
+++ b/src/main/generated/com/worldline/acquiring/sdk/java/v1/ping/PingClient.java
@@ -0,0 +1,77 @@
+/*
+ * This file was automatically generated.
+ */
+
+package com.worldline.acquiring.sdk.java.v1.ping;
+
+import java.util.Map;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.v1.ApiException;
+import com.worldline.acquiring.sdk.java.v1.AuthorizationException;
+import com.worldline.acquiring.sdk.java.v1.ExceptionFactory;
+import com.worldline.acquiring.sdk.java.v1.PlatformException;
+import com.worldline.acquiring.sdk.java.v1.ReferenceException;
+import com.worldline.acquiring.sdk.java.v1.ValidationException;
+import com.worldline.acquiring.sdk.java.v1.domain.ApiPaymentErrorResponse;
+
+/**
+ * Ping client. Thread-safe.
+ */
+public class PingClient extends ApiResource {
+
+ private static final ExceptionFactory EXCEPTION_FACTORY = new ExceptionFactory();
+
+ public PingClient(ApiResource parent, Map pathContext) {
+ super(parent, pathContext);
+ }
+
+ /**
+ * Resource /services/v1/ping
+ * - Check API connection
+ *
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public void ping() {
+ ping(null);
+ }
+
+ /**
+ * Resource /services/v1/ping
+ * - Check API connection
+ *
+ * @param context CallContext
+ * @throws ValidationException if the request was not correct and couldn't be processed (HTTP status code 400)
+ * @throws AuthorizationException if the request was not allowed (HTTP status code 403)
+ * @throws ReferenceException if an object was attempted to be referenced that doesn't exist or has been removed,
+ * or there was a conflict (HTTP status code 404, 409 or 410)
+ * @throws PlatformException if something went wrong at the Worldline Acquiring platform,
+ * the Worldline Acquiring platform was unable to process a message from a downstream partner/acquirer,
+ * or the service that you're trying to reach is temporary unavailable (HTTP status code 500, 502 or 503)
+ * @throws ApiException if the Worldline Acquiring platform returned any other error
+ */
+ public void ping(CallContext context) {
+ String uri = instantiateUri("/services/v1/ping", null);
+ try {
+ communicator.get(
+ uri,
+ null,
+ null,
+ void.class,
+ context);
+ } catch (ResponseException e) {
+ final Class> errorType = ApiPaymentErrorResponse.class;
+ final Object errorObject = communicator.getMarshaller().unmarshal(e.getBody(), errorType);
+ throw EXCEPTION_FACTORY.createException(e.getStatusCode(), e.getBody(), errorObject, context);
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/ApiResource.java b/src/main/java/com/worldline/acquiring/sdk/java/ApiResource.java
new file mode 100644
index 0000000..07dd670
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/ApiResource.java
@@ -0,0 +1,54 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.util.Map;
+
+/**
+ * Base class of all Worldline Acquiring platform API resources.
+ */
+public abstract class ApiResource {
+
+ private final ApiResource parent;
+ protected final Communicator communicator;
+ private final Map pathContext;
+
+ protected ApiResource(ApiResource parent, Map pathContext) {
+ if (parent == null) {
+ throw new IllegalArgumentException("parent is required");
+ }
+ this.parent = parent;
+ this.communicator = parent.communicator;
+ this.pathContext = pathContext;
+ }
+
+ protected ApiResource(Communicator communicator, Map pathContext) {
+ if (communicator == null) {
+ throw new IllegalArgumentException("communicator is required");
+ }
+ this.parent = null;
+ this.communicator = communicator;
+ this.pathContext = pathContext;
+ }
+
+ protected String instantiateUri(String uri, Map pathContext) {
+ uri = replaceAll(uri, pathContext);
+ uri = instantiateUri(uri);
+ return uri;
+ }
+
+ private String instantiateUri(String uri) {
+ uri = replaceAll(uri, pathContext);
+ if (parent != null) {
+ uri = parent.instantiateUri(uri);
+ }
+ return uri;
+ }
+
+ private static String replaceAll(String uri, Map pathContext) {
+ if (pathContext != null) {
+ for (Map.Entry entry : pathContext.entrySet()) {
+ uri = uri.replace(String.format("{%s}", entry.getKey()), entry.getValue());
+ }
+ }
+ return uri;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/BodyHandler.java b/src/main/java/com/worldline/acquiring/sdk/java/BodyHandler.java
new file mode 100644
index 0000000..8785f8a
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/BodyHandler.java
@@ -0,0 +1,19 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.communication.ResponseHeader;
+
+/**
+ * An interface for handling binary responses.
+ */
+public interface BodyHandler {
+
+ /**
+ * @param bodyStream The raw response body as an input stream. Note that it will be closed outside of this method.
+ * @param headers The headers that were returned by the Worldline Acquiring platform. Never {@code null}.
+ */
+ void handleBody(InputStream bodyStream, List headers) throws IOException;
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/BodyHandlerException.java b/src/main/java/com/worldline/acquiring/sdk/java/BodyHandlerException.java
new file mode 100644
index 0000000..9d3a71d
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/BodyHandlerException.java
@@ -0,0 +1,24 @@
+package com.worldline.acquiring.sdk.java;
+
+/**
+ * Thrown when an error occurred when calling a {@link BodyHandler}.
+ */
+@SuppressWarnings("serial")
+public class BodyHandlerException extends RuntimeException {
+
+ public BodyHandlerException() {
+ super();
+ }
+
+ public BodyHandlerException(String message) {
+ super(message);
+ }
+
+ public BodyHandlerException(Throwable cause) {
+ super(cause);
+ }
+
+ public BodyHandlerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/CallContext.java b/src/main/java/com/worldline/acquiring/sdk/java/CallContext.java
new file mode 100644
index 0000000..911b35c
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/CallContext.java
@@ -0,0 +1,9 @@
+package com.worldline.acquiring.sdk.java;
+
+/**
+ * A call context can be used to send extra information with a request, and to receive extra information from a response.
+ *
+ * Please note that this class is not thread-safe. Each request should get its own call context instance.
+ */
+public class CallContext {
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/Communicator.java b/src/main/java/com/worldline/acquiring/sdk/java/Communicator.java
new file mode 100644
index 0000000..9959362
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/Communicator.java
@@ -0,0 +1,655 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.utils.URIBuilder;
+
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.communication.CommunicationException;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.MetadataProvider;
+import com.worldline.acquiring.sdk.java.communication.MultipartFormDataObject;
+import com.worldline.acquiring.sdk.java.communication.MultipartFormDataRequest;
+import com.worldline.acquiring.sdk.java.communication.NotFoundException;
+import com.worldline.acquiring.sdk.java.communication.ParamRequest;
+import com.worldline.acquiring.sdk.java.communication.PooledConnection;
+import com.worldline.acquiring.sdk.java.communication.RequestHeader;
+import com.worldline.acquiring.sdk.java.communication.RequestParam;
+import com.worldline.acquiring.sdk.java.communication.ResponseException;
+import com.worldline.acquiring.sdk.java.communication.ResponseHeader;
+import com.worldline.acquiring.sdk.java.json.Marshaller;
+import com.worldline.acquiring.sdk.java.logging.BodyObfuscator;
+import com.worldline.acquiring.sdk.java.logging.CommunicatorLogger;
+import com.worldline.acquiring.sdk.java.logging.HeaderObfuscator;
+import com.worldline.acquiring.sdk.java.logging.LoggingCapable;
+import com.worldline.acquiring.sdk.java.logging.ObfuscationCapable;
+
+/**
+ * Used to communicate with the Worldline Acquiring platform web services.
+ *
+ * It contains all the logic to transform a request object to a HTTP request and
+ * a HTTP response to a response object.
+ *
+ * Thread-safe.
+ */
+public class Communicator implements Closeable, LoggingCapable, ObfuscationCapable {
+
+ private static final String CONTENT_TYPE_HEADER = "Content-Type";
+ private static final String CONTENT_TYPE_JSON = "application/json";
+ private static final String CONTENT_TYPE_PROBLEM_JSON = "application/problem+json";
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ private final URI apiEndpoint;
+
+ private final Connection connection;
+
+ private final Authenticator authenticator;
+
+ private final MetadataProvider metadataProvider;
+
+ private final Marshaller marshaller;
+
+ public Communicator(URI apiEndpoint,
+ Connection connection,
+ Authenticator authenticator,
+ MetadataProvider metadataProvider,
+ Marshaller marshaller) {
+
+ if (apiEndpoint == null) {
+ throw new IllegalArgumentException("apiEndpoint is required");
+ }
+ if (apiEndpoint.getPath() != null && !apiEndpoint.getPath().isEmpty()) {
+ throw new IllegalArgumentException("apiEndpoint should not contain a path");
+ }
+ if (apiEndpoint.getUserInfo() != null
+ || apiEndpoint.getQuery() != null
+ || apiEndpoint.getFragment() != null) {
+
+ throw new IllegalArgumentException("apiEndpoint should not contain user info, query or fragment");
+ }
+
+ if (connection == null) {
+ throw new IllegalArgumentException("connection is required");
+ }
+ if (authenticator == null) {
+ throw new IllegalArgumentException("authenticator is required");
+ }
+ if (metadataProvider == null) {
+ throw new IllegalArgumentException("metadataProvider is required");
+ }
+ if (marshaller == null) {
+ throw new IllegalArgumentException("marshaller is required");
+ }
+
+ this.apiEndpoint = apiEndpoint;
+ this.connection = connection;
+ this.authenticator = authenticator;
+ this.metadataProvider = metadataProvider;
+ this.marshaller = marshaller;
+ }
+
+ /**
+ * Releases any system resources associated with this object.
+ */
+ @Override
+ public void close() throws IOException {
+ connection.close();
+ }
+
+ /**
+ * Corresponds to the HTTP GET method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param responseType The type of response to return.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public O get(String relativePath, List requestHeaders, ParamRequest requestParameters,
+ Class responseType, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ addGenericHeaders("GET", uri, requestHeaders, context);
+
+ return connection.get(uri, requestHeaders,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ /**
+ * Corresponds to the HTTP GET method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param bodyHandler The handler for the response body.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public void get(String relativePath, List requestHeaders, ParamRequest requestParameters,
+ BodyHandler bodyHandler, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ addGenericHeaders("GET", uri, requestHeaders, context);
+
+ connection.get(uri, requestHeaders, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ /**
+ * Corresponds to the HTTP DELETE method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param responseType The type of response to return.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public O delete(String relativePath, List requestHeaders, ParamRequest requestParameters,
+ Class responseType, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ addGenericHeaders("DELETE", uri, requestHeaders, context);
+
+ return connection.delete(uri, requestHeaders,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ /**
+ * Corresponds to the HTTP DELETE method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param bodyHandler The handler for the response body.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public void delete(String relativePath, List requestHeaders, ParamRequest requestParameters,
+ BodyHandler bodyHandler, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ addGenericHeaders("DELETE", uri, requestHeaders, context);
+
+ connection.delete(uri, requestHeaders, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ /**
+ * Corresponds to the HTTP POST method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param requestBody The optional request body to send.
+ * @param responseType The type of response to return.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public O post(String relativePath, List requestHeaders, ParamRequest requestParameters, Object requestBody,
+ Class responseType, CallContext context) {
+
+ if (requestBody instanceof MultipartFormDataObject) {
+ return post(relativePath, requestHeaders, requestParameters, (MultipartFormDataObject) requestBody, responseType, context);
+ }
+ if (requestBody instanceof MultipartFormDataRequest) {
+ MultipartFormDataObject multipart = ((MultipartFormDataRequest) requestBody).toMultipartFormDataObject();
+ return post(relativePath, requestHeaders, requestParameters, multipart, responseType, context);
+ }
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ String requestJson = null;
+ if (requestBody != null) {
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON));
+ requestJson = marshaller.marshal(requestBody);
+ }
+
+ addGenericHeaders("POST", uri, requestHeaders, context);
+
+ return connection.post(uri, requestHeaders, requestJson,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ private O post(String relativePath, List requestHeaders, ParamRequest requestParameters, MultipartFormDataObject multipart,
+ Class responseType, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, multipart.getContentType()));
+
+ addGenericHeaders("POST", uri, requestHeaders, context);
+
+ return connection.post(uri, requestHeaders, multipart,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ /**
+ * Corresponds to the HTTP POST method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param requestBody The optional request body to send.
+ * @param bodyHandler The handler for the response body.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public void post(String relativePath, List requestHeaders, ParamRequest requestParameters, Object requestBody,
+ BodyHandler bodyHandler, CallContext context) {
+
+ if (requestBody instanceof MultipartFormDataObject) {
+ post(relativePath, requestHeaders, requestParameters, (MultipartFormDataObject) requestBody, bodyHandler, context);
+ return;
+ }
+ if (requestBody instanceof MultipartFormDataRequest) {
+ MultipartFormDataObject multipart = ((MultipartFormDataRequest) requestBody).toMultipartFormDataObject();
+ post(relativePath, requestHeaders, requestParameters, multipart, bodyHandler, context);
+ return;
+ }
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ String requestJson = null;
+ if (requestBody != null) {
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON));
+ requestJson = marshaller.marshal(requestBody);
+ }
+
+ addGenericHeaders("POST", uri, requestHeaders, context);
+
+ connection.post(uri, requestHeaders, requestJson, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ private void post(String relativePath, List requestHeaders, ParamRequest requestParameters, MultipartFormDataObject multipart,
+ BodyHandler bodyHandler, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, multipart.getContentType()));
+
+ addGenericHeaders("POST", uri, requestHeaders, context);
+
+ connection.post(uri, requestHeaders, multipart, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ /**
+ * Corresponds to the HTTP PUT method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param requestBody The optional request body to send.
+ * @param responseType The type of response to return.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public O put(String relativePath, List requestHeaders, ParamRequest requestParameters, Object requestBody,
+ Class responseType, CallContext context) {
+
+ if (requestBody instanceof MultipartFormDataObject) {
+ return put(relativePath, requestHeaders, requestParameters, (MultipartFormDataObject) requestBody, responseType, context);
+ }
+ if (requestBody instanceof MultipartFormDataRequest) {
+ MultipartFormDataObject multipart = ((MultipartFormDataRequest) requestBody).toMultipartFormDataObject();
+ return put(relativePath, requestHeaders, requestParameters, multipart, responseType, context);
+ }
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ String requestJson = null;
+ if (requestBody != null) {
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON));
+ requestJson = marshaller.marshal(requestBody);
+ }
+
+ addGenericHeaders("PUT", uri, requestHeaders, context);
+
+ return connection.put(uri, requestHeaders, requestJson,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ private O put(String relativePath, List requestHeaders, ParamRequest requestParameters, MultipartFormDataObject multipart,
+ Class responseType, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, multipart.getContentType()));
+
+ addGenericHeaders("PUT", uri, requestHeaders, context);
+
+ return connection.put(uri, requestHeaders, multipart,
+ (statusCode, bodyStream, headers) -> processResponse(statusCode, bodyStream, headers, responseType, relativePath, context));
+ }
+
+ /**
+ * Corresponds to the HTTP PUT method.
+ *
+ * @param relativePath The path to call, relative to the base URI.
+ * @param requestHeaders An optional list of request headers.
+ * @param requestParameters An optional set of request parameters.
+ * @param requestBody The optional request body to send.
+ * @param bodyHandler The handler for the response body.
+ * @param context The optional call context to use.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ * @throws ResponseException when an error response was received from the Worldline Acquiring platform
+ */
+ public void put(String relativePath, List requestHeaders, ParamRequest requestParameters, Object requestBody,
+ BodyHandler bodyHandler, CallContext context) {
+
+ if (requestBody instanceof MultipartFormDataObject) {
+ put(relativePath, requestHeaders, requestParameters, (MultipartFormDataObject) requestBody, bodyHandler, context);
+ return;
+ }
+ if (requestBody instanceof MultipartFormDataRequest) {
+ MultipartFormDataObject multipart = ((MultipartFormDataRequest) requestBody).toMultipartFormDataObject();
+ put(relativePath, requestHeaders, requestParameters, multipart, bodyHandler, context);
+ return;
+ }
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ String requestJson = null;
+ if (requestBody != null) {
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON));
+ requestJson = marshaller.marshal(requestBody);
+ }
+
+ addGenericHeaders("PUT", uri, requestHeaders, context);
+
+ connection.put(uri, requestHeaders, requestJson, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ private void put(String relativePath, List requestHeaders, ParamRequest requestParameters, MultipartFormDataObject multipart,
+ BodyHandler bodyHandler, CallContext context) {
+
+ URI uri = toAbsoluteURI(relativePath, requestParameters);
+
+ if (requestHeaders == null) {
+ requestHeaders = new ArrayList<>();
+ }
+
+ requestHeaders.add(new RequestHeader(CONTENT_TYPE_HEADER, multipart.getContentType()));
+
+ addGenericHeaders("PUT", uri, requestHeaders, context);
+
+ connection.put(uri, requestHeaders, multipart, (statusCode, bodyStream, headers) -> {
+ processResponse(statusCode, bodyStream, headers, bodyHandler, relativePath, context);
+ return null;
+ });
+ }
+
+ /**
+ * @return The {@link Marshaller} object associated with this communicator. Never {@code null}.
+ */
+ public Marshaller getMarshaller() {
+ return marshaller;
+ }
+
+ private URI toAbsoluteURI(String relativePath, ParamRequest requestParameters) {
+ List requestParameterList = requestParameters == null ? null : requestParameters.toRequestParameters();
+ return toAbsoluteURI(relativePath, requestParameterList);
+ }
+
+ protected URI toAbsoluteURI(String relativePath, List requestParameters) {
+ String absolutePath;
+ if (relativePath.startsWith("/")) {
+ absolutePath = relativePath;
+ } else {
+ absolutePath = "/" + relativePath;
+ }
+
+ URIBuilder uriBuilder = new URIBuilder()
+ .setScheme(apiEndpoint.getScheme())
+ .setHost(apiEndpoint.getHost())
+ .setPort(apiEndpoint.getPort())
+ .setPath(absolutePath);
+
+ if (requestParameters != null) {
+ for (RequestParam nvp : requestParameters) {
+ uriBuilder.addParameter(nvp.getName(), nvp.getValue());
+ }
+ }
+
+ try {
+ return uriBuilder.build();
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Unable to construct URI", e);
+ }
+ }
+
+ /**
+ * Adds the necessary headers to the given list of headers. This includes the authorization header, which uses
+ * other headers, so when you need to override this method, make sure to call {@code super.addGenericHeaders}
+ * at the end of your overridden method.
+ */
+ @SuppressWarnings("unused")
+ protected void addGenericHeaders(String httpMethod, URI uri, List requestHeaders, CallContext context) {
+ requestHeaders.addAll(metadataProvider.getServerMetadataHeaders());
+
+ requestHeaders.add(new RequestHeader("Date", getHeaderDateString()));
+
+ // no context specific headers at this time
+
+ String authorization = authenticator.getAuthorization(httpMethod, uri, requestHeaders);
+ requestHeaders.add(new RequestHeader("Authorization", authorization));
+ requestHeaders.add(new RequestHeader("Trace-Id", UUID.randomUUID().toString()));
+ }
+
+ /**
+ * Returns the date in the preferred format for the HTTP date header (RFC1123).
+ */
+ protected static String getHeaderDateString() {
+ return ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
+ }
+
+ protected O processResponse(int statusCode, InputStream bodyStream, List headers, Class responseType,
+ String requestPath, CallContext context) {
+
+ if (context != null) {
+ updateContext(headers, context);
+ }
+ throwExceptionIfNecessary(statusCode, bodyStream, headers, requestPath);
+
+ return marshaller.unmarshal(bodyStream, responseType);
+ }
+
+ protected void processResponse(int statusCode, InputStream bodyStream, List headers, BodyHandler bodyHandler,
+ String requestPath, CallContext context) {
+
+ if (context != null) {
+ updateContext(headers, context);
+ }
+ throwExceptionIfNecessary(statusCode, bodyStream, headers, requestPath);
+
+ try {
+ bodyHandler.handleBody(bodyStream, headers);
+ } catch (IOException e) {
+ throw new BodyHandlerException(e);
+ }
+ }
+
+ /**
+ * Updates the given call context based on the contents of the given response.
+ */
+ @SuppressWarnings("unused")
+ protected static void updateContext(List headers, CallContext context) {
+ // no context specific headers at this time
+ }
+
+ /**
+ * Checks the status code and headers for errors and throws an exception if necessary.
+ */
+ protected static void throwExceptionIfNecessary(int statusCode, InputStream bodyStream, List headers, String requestPath) {
+ // status codes in the 100 or 300 range are not expected
+ if (statusCode < 200 || statusCode >= 300) {
+ String body = toString(bodyStream);
+
+ if (body != null && !body.isEmpty() && !isJson(headers)) {
+ ResponseException cause = new ResponseException(statusCode, body, headers);
+ if (statusCode == HttpStatus.SC_NOT_FOUND) {
+ throw new NotFoundException("The requested resource was not found; invalid path: " + requestPath, cause);
+ }
+ throw new CommunicationException(cause);
+ }
+ throw new ResponseException(statusCode, body, headers);
+ }
+ }
+
+ private static String toString(InputStream bodyStream) {
+ try (Reader reader = new InputStreamReader(bodyStream, CHARSET)) {
+ StringBuilder body = new StringBuilder();
+ char[] buffer = new char[4096];
+ int len;
+ while ((len = reader.read(buffer)) != -1) {
+ body.append(buffer, 0, len);
+ }
+ return body.toString();
+ } catch (IOException e) {
+ throw new CommunicationException(e);
+ }
+ }
+
+ private static boolean isJson(List headers) {
+ String contentType = ResponseHeader.getHeaderValue(headers, CONTENT_TYPE_HEADER);
+ return contentType == null || isJson(contentType);
+ }
+
+ private static boolean isJson(String contentType) {
+ if (CONTENT_TYPE_JSON.equalsIgnoreCase(contentType) || CONTENT_TYPE_PROBLEM_JSON.equalsIgnoreCase(contentType)) {
+ return true;
+ }
+ String lowerCaseContentType = contentType.toLowerCase();
+ return lowerCaseContentType.startsWith(CONTENT_TYPE_JSON) || lowerCaseContentType.startsWith(CONTENT_TYPE_PROBLEM_JSON);
+ }
+
+ /**
+ * Utility method that delegates the call to this communicator's connection if that's an instance of
+ * {@link PooledConnection}. If not this method does nothing.
+ *
+ * @see PooledConnection#closeIdleConnections(long, TimeUnit)
+ */
+ public void closeIdleConnections(long idleTime, TimeUnit timeUnit) {
+ if (connection instanceof PooledConnection) {
+ ((PooledConnection) connection).closeIdleConnections(idleTime, timeUnit);
+ }
+ }
+
+ /**
+ * Utility method that delegates the call to this communicator's connection if that's an instance of
+ * {@link PooledConnection}. If not this method does nothing.
+ *
+ * @see PooledConnection#closeExpiredConnections()
+ */
+ public void closeExpiredConnections() {
+ if (connection instanceof PooledConnection) {
+ ((PooledConnection) connection).closeExpiredConnections();
+ }
+ }
+
+ @Override
+ public void setBodyObfuscator(BodyObfuscator bodyObfuscator) {
+ connection.setBodyObfuscator(bodyObfuscator);
+ }
+
+ @Override
+ public void setHeaderObfuscator(HeaderObfuscator headerObfuscator) {
+ connection.setHeaderObfuscator(headerObfuscator);
+ }
+
+ @Override
+ public void enableLogging(CommunicatorLogger communicatorLogger) {
+ connection.enableLogging(communicatorLogger);
+ }
+
+ @Override
+ public void disableLogging() {
+ connection.disableLogging();
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorBuilder.java
new file mode 100644
index 0000000..08865b3
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorBuilder.java
@@ -0,0 +1,79 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.net.URI;
+
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.MetadataProvider;
+import com.worldline.acquiring.sdk.java.json.Marshaller;
+
+/**
+ * Builder for a {@link Communicator} object.
+ */
+public class CommunicatorBuilder {
+
+ private URI apiEndpoint;
+
+ private Connection connection;
+
+ private MetadataProvider metadataProvider;
+
+ private Authenticator authenticator;
+
+ private Marshaller marshaller;
+
+ /**
+ * Sets the Worldline Acquiring platform API endpoint URI to use.
+ */
+ public CommunicatorBuilder withAPIEndpoint(URI apiEndpoint) {
+ this.apiEndpoint = apiEndpoint;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Connection} to use.
+ */
+ public CommunicatorBuilder withConnection(Connection connection) {
+ this.connection = connection;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Authenticator} to use.
+ */
+ public CommunicatorBuilder withAuthenticator(Authenticator authenticator) {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ /**
+ * Sets the {@link MetadataProvider} to use.
+ */
+ public CommunicatorBuilder withMetadataProvider(MetadataProvider metadataProvider) {
+ this.metadataProvider = metadataProvider;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Marshaller} to use.
+ */
+ public CommunicatorBuilder withMarshaller(Marshaller marshaller) {
+ this.marshaller = marshaller;
+ return this;
+ }
+
+ /**
+ * Creates a fully initialized {@link Communicator} object.
+ *
+ * @throws IllegalArgumentException if not all required components are set
+ */
+ public Communicator build() {
+ return new Communicator(
+ apiEndpoint,
+ connection,
+ authenticator,
+ metadataProvider,
+ marshaller
+ );
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorConfiguration.java b/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorConfiguration.java
new file mode 100644
index 0000000..d1fcb57
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/CommunicatorConfiguration.java
@@ -0,0 +1,366 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import com.worldline.acquiring.sdk.java.authentication.AuthorizationType;
+import com.worldline.acquiring.sdk.java.domain.ShoppingCartExtension;
+
+/**
+ * Configuration for the communicator.
+ */
+public class CommunicatorConfiguration {
+
+ /** The default number of maximum connections. */
+ public static final int DEFAULT_MAX_CONNECTIONS = 10;
+
+ /** The default HTTPS protocols. */
+ public static final Set DEFAULT_HTTPS_PROTOCOLS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList("TLSv1.1", "TLSv1.2")));
+
+ private static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile("\\s*,\\s*");
+
+ private URI apiEndpoint;
+
+ private int connectTimeout;
+
+ private int socketTimeout;
+
+ private int maxConnections = DEFAULT_MAX_CONNECTIONS;
+
+ private boolean connectionReuse = true;
+
+ private AuthorizationType authorizationType;
+
+ private String authorizationId;
+
+ private String authorizationSecret;
+
+ private String oauth2TokenUri;
+
+ private ProxyConfiguration proxyConfiguration;
+
+ private Set httpsProtocols = new LinkedHashSet<>(DEFAULT_HTTPS_PROTOCOLS);
+
+ private String integrator;
+
+ private ShoppingCartExtension shoppingCartExtension;
+
+ public CommunicatorConfiguration() {
+ }
+
+ public CommunicatorConfiguration(Properties properties) {
+ if (properties != null) {
+ apiEndpoint = getApiEndpoint(properties);
+ authorizationType = AuthorizationType.valueOf(properties.getProperty("acquiring.api.authorizationType"));
+ oauth2TokenUri = properties.getProperty("acquiring.api.oauth2.tokenUri");
+ connectTimeout = Integer.parseInt(properties.getProperty("acquiring.api.connectTimeout"));
+ socketTimeout = Integer.parseInt(properties.getProperty("acquiring.api.socketTimeout"));
+ maxConnections = getProperty(properties, "acquiring.api.maxConnections", DEFAULT_MAX_CONNECTIONS);
+
+ String proxyURI = properties.getProperty("acquiring.api.proxy.uri");
+ String proxyUser = properties.getProperty("acquiring.api.proxy.username");
+ String proxyPass = properties.getProperty("acquiring.api.proxy.password");
+ if (proxyURI != null) {
+ proxyConfiguration = new ProxyConfiguration(URI.create(proxyURI), proxyUser, proxyPass);
+ }
+
+ String httpsProtocolString = properties.getProperty("acquiring.api.https.protocols");
+ if (httpsProtocolString != null) {
+ httpsProtocols.clear();
+ httpsProtocols.addAll(Arrays.asList(COMMA_SEPARATOR_PATTERN.split(httpsProtocolString.trim())));
+ }
+
+ connectionReuse = Boolean.parseBoolean(properties.getProperty("acquiring.api.connectionReuse", "true"));
+
+ integrator = properties.getProperty("acquiring.api.integrator");
+ shoppingCartExtension = getShoppingCartExtension(properties);
+ }
+ }
+
+ private static int getProperty(Properties properties, String key, int defaultValue) {
+ String propertyValue = properties.getProperty(key, null);
+ return propertyValue != null ? Integer.parseInt(propertyValue) : defaultValue;
+ }
+
+ private static URI getApiEndpoint(Properties properties) {
+ String host = properties.getProperty("acquiring.api.endpoint.host");
+ String scheme = properties.getProperty("acquiring.api.endpoint.scheme");
+ String port = properties.getProperty("acquiring.api.endpoint.port");
+
+ return createURI(scheme != null ? scheme : "https", host, port != null ? Integer.parseInt(port) : -1);
+ }
+
+ private static URI createURI(String scheme, String host, int port) {
+ try {
+ return new URI(scheme, null, host, port, null, null, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Unable to construct API endpoint URI", e);
+ }
+ }
+
+ private static ShoppingCartExtension getShoppingCartExtension(Properties properties) {
+ String creator = properties.getProperty("acquiring.api.shoppingCartExtension.creator");
+ String name = properties.getProperty("acquiring.api.shoppingCartExtension.name");
+ String version = properties.getProperty("acquiring.api.shoppingCartExtension.version");
+ String extensionId = properties.getProperty("acquiring.api.shoppingCartExtension.extensionId");
+
+ if (creator == null && name == null && version == null && extensionId == null) {
+ return null;
+ } else if (extensionId == null) {
+ return new ShoppingCartExtension(creator, name, version);
+ } else {
+ return new ShoppingCartExtension(creator, name, version, extensionId);
+ }
+ }
+
+ /**
+ * Returns the Worldline Acquiring platform API endpoint URI.
+ */
+ public URI getApiEndpoint() {
+ return apiEndpoint;
+ }
+
+ public void setApiEndpoint(URI apiEndpoint) {
+ if (apiEndpoint != null && apiEndpoint.getPath() != null && !apiEndpoint.getPath().isEmpty()) {
+ throw new IllegalArgumentException("apiEndpoint should not contain a path");
+ }
+ if (apiEndpoint != null && (
+ apiEndpoint.getUserInfo() != null
+ || apiEndpoint.getQuery() != null
+ || apiEndpoint.getFragment() != null
+ )) {
+ throw new IllegalArgumentException("apiEndpoint should not contain user info, query or fragment");
+ }
+ this.apiEndpoint = apiEndpoint;
+ }
+
+ public CommunicatorConfiguration withApiEndpoint(URI apiEndpoint) {
+ setApiEndpoint(apiEndpoint);
+ return this;
+ }
+
+ /**
+ * Returns an id used for authorization. The meaning of this id is different for each authorization type.
+ * For instance, for {@link AuthorizationType#OAUTH2} this is the client id.
+ */
+ public String getAuthorizationId() {
+ return authorizationId;
+ }
+
+ public void setAuthorizationId(String authorizationId) {
+ this.authorizationId = authorizationId;
+ }
+
+ public CommunicatorConfiguration withAuthorizationId(String authorizationId) {
+ setAuthorizationId(authorizationId);
+ return this;
+ }
+
+ /**
+ * Returns a secret used for authorization. The meaning of this secret is different for each authorization type.
+ * For instance, for {@link AuthorizationType#OAUTH2} this is the client secret.
+ */
+ public String getAuthorizationSecret() {
+ return authorizationSecret;
+ }
+
+ public void setAuthorizationSecret(String authorizationSecret) {
+ this.authorizationSecret = authorizationSecret;
+ }
+
+ public CommunicatorConfiguration withAuthorizationSecret(String authorizationSecret) {
+ setAuthorizationSecret(authorizationSecret);
+ return this;
+ }
+
+ /**
+ * The OAuth2 client id.
+ *
+ * This method is an alias for {@link #getAuthorizationId()}.
+ */
+ public String getOAuth2ClientId() {
+ return getAuthorizationId();
+ }
+
+ /**
+ * This method is an alias for {@link #setAuthorizationId(String)}.
+ */
+ public void setOAuth2ClientId(String oauth2ClientId) {
+ setAuthorizationId(oauth2ClientId);
+ }
+
+ /**
+ * This method is an alias for {@link #withAuthorizationId(String)}.
+ */
+ public CommunicatorConfiguration withOAuth2ClientId(String oauth2ClientId) {
+ return withAuthorizationId(oauth2ClientId);
+ }
+
+ /**
+ * The OAuth2 client secret.
+ *
+ * This method is an alias for {@link #getAuthorizationSecret()}.
+ */
+ public String getOAuth2ClientSecret() {
+ return getAuthorizationSecret();
+ }
+
+ /**
+ * This method is an alias for {@link #setAuthorizationSecret(String)}.
+ */
+ public void setOAuth2ClientSecret(String oauth2ClientSecret) {
+ setAuthorizationSecret(oauth2ClientSecret);
+ }
+
+ /**
+ * This method is an alias for {@link #withAuthorizationSecret(String)}.
+ */
+ public CommunicatorConfiguration withOAuth2ClientSecret(String oauth2ClientSecret) {
+ return withAuthorizationSecret(oauth2ClientSecret);
+ }
+
+ public String getOAuth2TokenUri() {
+ return oauth2TokenUri;
+ }
+
+ public void setOAuth2TokenUri(String oauth2TokenUri) {
+ this.oauth2TokenUri = oauth2TokenUri;
+ }
+
+ public CommunicatorConfiguration withOAuth2TokenUri(String oauth2TokenUri) {
+ setOAuth2TokenUri(oauth2TokenUri);
+ return this;
+ }
+
+ public AuthorizationType getAuthorizationType() {
+ return authorizationType;
+ }
+
+ public void setAuthorizationType(AuthorizationType authorizationType) {
+ this.authorizationType = authorizationType;
+ }
+
+ public CommunicatorConfiguration withAuthorizationType(AuthorizationType authorizationType) {
+ setAuthorizationType(authorizationType);
+ return this;
+ }
+
+ public int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ public void setConnectTimeout(int connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ public CommunicatorConfiguration withConnectTimeout(int connectTimeout) {
+ setConnectTimeout(connectTimeout);
+ return this;
+ }
+
+ public int getSocketTimeout() {
+ return socketTimeout;
+ }
+
+ public void setSocketTimeout(int socketTimeout) {
+ this.socketTimeout = socketTimeout;
+ }
+
+ public CommunicatorConfiguration withSocketTimeout(int socketTimeout) {
+ setSocketTimeout(socketTimeout);
+ return this;
+ }
+
+ public int getMaxConnections() {
+ return maxConnections;
+ }
+
+ public void setMaxConnections(int maxConnections) {
+ this.maxConnections = maxConnections;
+ }
+
+ public CommunicatorConfiguration withMaxConnections(int maxConnections) {
+ setMaxConnections(maxConnections);
+ return this;
+ }
+
+ public boolean isConnectionReuse() {
+ return connectionReuse;
+ }
+
+ public void setConnectionReuse(boolean connectionReuse) {
+ this.connectionReuse = connectionReuse;
+ }
+
+ public CommunicatorConfiguration withConnectionReuse(boolean connectionReuse) {
+ this.connectionReuse = connectionReuse;
+ return this;
+ }
+
+ public ProxyConfiguration getProxyConfiguration() {
+ return proxyConfiguration;
+ }
+
+ public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
+ this.proxyConfiguration = proxyConfiguration;
+ }
+
+ public CommunicatorConfiguration withProxyConfiguration(ProxyConfiguration proxyConfiguration) {
+ setProxyConfiguration(proxyConfiguration);
+ return this;
+ }
+
+ public Set getHttpsProtocols() {
+ if (httpsProtocols == null) {
+ httpsProtocols = new LinkedHashSet<>();
+ }
+ return httpsProtocols;
+ }
+
+ public void setHttpsProtocols(Set httpsProtocols) {
+ this.httpsProtocols = httpsProtocols;
+ }
+
+ public CommunicatorConfiguration withHttpsProtocols(Collection httpsProtocols) {
+ getHttpsProtocols().clear();
+ getHttpsProtocols().addAll(httpsProtocols);
+ return this;
+ }
+
+ public CommunicatorConfiguration withHttpsProtocols(String... httpsProtocols) {
+ return withHttpsProtocols(Arrays.asList(httpsProtocols));
+ }
+
+ public String getIntegrator() {
+ return integrator;
+ }
+
+ public void setIntegrator(String integrator) {
+ this.integrator = integrator;
+ }
+
+ public CommunicatorConfiguration withIntegrator(String integrator) {
+ setIntegrator(integrator);
+ return this;
+ }
+
+ public ShoppingCartExtension getShoppingCartExtension() {
+ return shoppingCartExtension;
+ }
+
+ public void setShoppingCartExtension(ShoppingCartExtension shoppingCartExtension) {
+ this.shoppingCartExtension = shoppingCartExtension;
+ }
+
+ public CommunicatorConfiguration withShoppingCartExtension(ShoppingCartExtension shoppingCartExtension) {
+ setShoppingCartExtension(shoppingCartExtension);
+ return this;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/Factory.java b/src/main/java/com/worldline/acquiring/sdk/java/Factory.java
new file mode 100644
index 0000000..d2e517b
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/Factory.java
@@ -0,0 +1,143 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Properties;
+
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.authentication.AuthorizationType;
+import com.worldline.acquiring.sdk.java.authentication.OAuth2Authenticator;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.DefaultConnectionBuilder;
+import com.worldline.acquiring.sdk.java.communication.MetadataProvider;
+import com.worldline.acquiring.sdk.java.communication.MetadataProviderBuilder;
+import com.worldline.acquiring.sdk.java.json.DefaultMarshaller;
+
+/**
+ * Worldline Acquiring platform factory for several SDK components.
+ */
+public final class Factory {
+
+ private Factory() {
+ }
+
+ /**
+ * Creates a {@link CommunicatorConfiguration} based on the configuration
+ * values in {@code configurationFileUri} and {@code authorizationId} and
+ * {@code authorizationSecret}.
+ */
+ public static CommunicatorConfiguration createConfiguration(URI configurationFileUri, String authorizationId, String authorizationSecret) {
+ Properties properties = new Properties();
+ try (InputStream configurationFileInputStream = configurationFileUri.toURL().openStream()) {
+ properties.load(configurationFileInputStream);
+ return new CommunicatorConfiguration(properties)
+ .withAuthorizationId(authorizationId)
+ .withAuthorizationSecret(authorizationSecret);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to load properties", e);
+ }
+ }
+
+ /**
+ * Creates a {@link CommunicatorBuilder} based on the configuration values in
+ * {@code configurationFileUri}, {@code authorizationId} and {@code authorizationSecret}.
+ */
+ public static CommunicatorBuilder createCommunicatorBuilder(URI configurationFileUri, String authorizationId, String authorizationSecret) {
+ CommunicatorConfiguration configuration = createConfiguration(configurationFileUri, authorizationId, authorizationSecret);
+ return createCommunicatorBuilder(configuration);
+ }
+
+ /**
+ * Creates a {@link CommunicatorBuilder} based on the passed configuration.
+ */
+ @SuppressWarnings("resource")
+ public static CommunicatorBuilder createCommunicatorBuilder(CommunicatorConfiguration configuration) {
+
+ MetadataProvider metadataProvider = new MetadataProviderBuilder(configuration.getIntegrator())
+ .withShoppingCartExtension(configuration.getShoppingCartExtension())
+ .build();
+
+ return new CommunicatorBuilder()
+ .withAPIEndpoint(configuration.getApiEndpoint())
+ .withConnection(new DefaultConnectionBuilder(configuration.getConnectTimeout(), configuration.getSocketTimeout())
+ .withMaxConnections(configuration.getMaxConnections())
+ .withConnectionReuse(configuration.isConnectionReuse())
+ .withProxyConfiguration(configuration.getProxyConfiguration())
+ .withHttpsProtocols(configuration.getHttpsProtocols())
+ .build()
+ )
+ .withMetadataProvider(metadataProvider)
+ .withAuthenticator(getAuthenticator(configuration))
+ .withMarshaller(DefaultMarshaller.INSTANCE);
+ }
+
+ /**
+ * Creates an {@link Authenticator} based on the passed configuration.
+ */
+ private static Authenticator getAuthenticator(CommunicatorConfiguration configuration) {
+ if (configuration.getAuthorizationType() == AuthorizationType.OAUTH2) {
+ return new OAuth2Authenticator(configuration);
+ }
+ throw new IllegalStateException("Unknown authorizationType " + configuration.getAuthorizationType());
+ }
+
+ /**
+ * Creates a {@link Communicator} based on the configuration values in
+ * {@code configurationFileUri}, {@code authorizationId} and {@code authorizationSecret}.
+ */
+ public static Communicator createCommunicator(URI configurationFileUri, String authorizationId, String authorizationSecret) {
+ CommunicatorConfiguration configuration = createConfiguration(configurationFileUri, authorizationId, authorizationSecret);
+ return createCommunicator(configuration);
+ }
+
+ /**
+ * Creates a {@link Communicator} based on the passed configuration.
+ */
+ public static Communicator createCommunicator(CommunicatorConfiguration configuration) {
+ CommunicatorBuilder communicatorBuilder = createCommunicatorBuilder(configuration);
+ return communicatorBuilder.build();
+ }
+
+ /**
+ * Creates a {@link Communicator} based on the passed parameters.
+ */
+ public static Communicator createCommunicator(URI apiEndpoint, Connection connection, Authenticator authenticator, MetadataProvider metadataProvider) {
+ return new Communicator(apiEndpoint, connection, authenticator, metadataProvider, DefaultMarshaller.INSTANCE);
+ }
+
+ /**
+ * Creates a {@link Client} based on the configuration values in
+ * {@code configurationFileUri}, {@code authorizationId} and {@code authorizationSecret}.
+ */
+ @SuppressWarnings("resource")
+ public static Client createClient(URI configurationFileUri, String authorizationId, String authorizationSecret) {
+ Communicator communicator = createCommunicator(configurationFileUri, authorizationId, authorizationSecret);
+ return createClient(communicator);
+ }
+
+ /**
+ * Creates a {@link Client} based on the passed configuration.
+ */
+ @SuppressWarnings("resource")
+ public static Client createClient(CommunicatorConfiguration configuration) {
+ Communicator communicator = createCommunicator(configuration);
+ return createClient(communicator);
+ }
+
+ /**
+ * Creates a {@link Client} based on the passed parameters.
+ */
+ @SuppressWarnings("resource")
+ public static Client createClient(URI apiEndpoint, Connection connection, Authenticator authenticator, MetadataProvider metadataProvider) {
+ Communicator communicator = createCommunicator(apiEndpoint, connection, authenticator, metadataProvider);
+ return createClient(communicator);
+ }
+
+ /**
+ * Creates a {@link Client} based on the passed {@link Communicator}.
+ */
+ public static Client createClient(Communicator communicator) {
+ return new Client(communicator);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/ProxyConfiguration.java b/src/main/java/com/worldline/acquiring/sdk/java/ProxyConfiguration.java
new file mode 100644
index 0000000..3f6a633
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/ProxyConfiguration.java
@@ -0,0 +1,134 @@
+package com.worldline.acquiring.sdk.java;
+
+import java.net.URI;
+
+/**
+ * HTTP proxy configuration.
+ */
+public class ProxyConfiguration {
+
+ private String scheme;
+
+ private String host;
+
+ private int port;
+
+ private String username;
+
+ private String password;
+
+ public ProxyConfiguration(String host, int port) {
+ this("http", host, port);
+ }
+
+ public ProxyConfiguration(String host, int port, String username, String password) {
+ this("http", host, port, username, password);
+ }
+
+ public ProxyConfiguration(String scheme, String host, int port) {
+ this(scheme, host, port, null, null);
+ }
+
+ public ProxyConfiguration(String scheme, String host, int port, String username, String password) {
+ if (scheme == null || scheme.trim().isEmpty()) {
+ throw new IllegalArgumentException("scheme is required");
+ }
+ if (host == null || host.trim().isEmpty()) {
+ throw new IllegalArgumentException("host is required");
+ }
+ if (port <= 0 || port > 65535) {
+ throw new IllegalArgumentException("port is invalid");
+ }
+ this.scheme = scheme;
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ }
+
+ public ProxyConfiguration(URI address) {
+ this(address, null, null);
+ }
+
+ public ProxyConfiguration(URI address, String username, String password) {
+ this(address.getScheme(), address.getHost(), getPort(address), username, password);
+ }
+
+ private static int getPort(URI address) {
+ int port = address.getPort();
+ if (port != -1) {
+ return port;
+ } else if ("http".equalsIgnoreCase(address.getScheme())) {
+ return 80;
+ } else if ("https".equalsIgnoreCase(address.getScheme())) {
+ return 443;
+ } else {
+ throw new IllegalArgumentException("unsupported scheme: " + address.getScheme());
+ }
+ }
+
+ public String getScheme() {
+ return scheme;
+ }
+
+ public void setScheme(String scheme) {
+ this.scheme = scheme;
+ }
+
+ public ProxyConfiguration withScheme(String scheme) {
+ this.scheme = scheme;
+ return this;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public ProxyConfiguration withHost(String host) {
+ this.host = host;
+ return this;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public ProxyConfiguration withPort(int port) {
+ this.port = port;
+ return this;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public ProxyConfiguration withUsername(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public ProxyConfiguration withPassword(String password) {
+ this.password = password;
+ return this;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/Authenticator.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/Authenticator.java
new file mode 100644
index 0000000..ccfd2ea
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/Authenticator.java
@@ -0,0 +1,23 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+import java.net.URI;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.communication.RequestHeader;
+
+/**
+ * Used to authenticate requests to the Worldline Acquiring platform. Thread-safe.
+ */
+public interface Authenticator {
+
+ /**
+ * Returns a value that can be used for the "Authorization" header.
+ *
+ * @param httpMethod The HTTP method.
+ * @param resourceUri The {@link URI} of the resource.
+ * @param requestHeaders
+ * A list of {@link RequestHeader}s. This list may not be
+ * modified and may not contain headers with the same name.
+ */
+ String getAuthorization(String httpMethod, URI resourceUri, List requestHeaders);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/AuthorizationType.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/AuthorizationType.java
new file mode 100644
index 0000000..faea9fc
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/AuthorizationType.java
@@ -0,0 +1,6 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+public enum AuthorizationType {
+
+ OAUTH2,
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessToken.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessToken.java
new file mode 100644
index 0000000..5404e18
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessToken.java
@@ -0,0 +1,20 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+class OAuth2AccessToken {
+
+ private final String accessToken;
+ private final long expirationTime;
+
+ OAuth2AccessToken(String accessToken, long expirationTime) {
+ this.accessToken = accessToken;
+ this.expirationTime = expirationTime;
+ }
+
+ String getAccessToken() {
+ return accessToken;
+ }
+
+ long getExpirationTime() {
+ return expirationTime;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessTokenResponse.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessTokenResponse.java
new file mode 100644
index 0000000..fef6331
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AccessTokenResponse.java
@@ -0,0 +1,49 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+import com.google.gson.annotations.SerializedName;
+
+class OAuth2AccessTokenResponse {
+
+ // Using Gson specific annotations is fine here, because this class will only be handled by DefaultMarshaller
+
+ @SerializedName("access_token")
+ private String accessToken;
+ @SerializedName("expires_in")
+ private Long expiresIn;
+ @SerializedName("error")
+ private String error;
+ @SerializedName("error_description")
+ private String errorDescription;
+
+ String getAccessToken() {
+ return accessToken;
+ }
+
+ void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ Long getExpiresIn() {
+ return expiresIn;
+ }
+
+ void setExpiresIn(Long expiresIn) {
+ this.expiresIn = expiresIn;
+ }
+
+ String getError() {
+ return error;
+ }
+
+ void setError(String error) {
+ this.error = error;
+ }
+
+ String getErrorDescription() {
+ return errorDescription;
+ }
+
+ void setErrorDescription(String errorDescription) {
+ this.errorDescription = errorDescription;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Authenticator.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Authenticator.java
new file mode 100644
index 0000000..5799eb1
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Authenticator.java
@@ -0,0 +1,160 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.http.HttpStatus;
+
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+import com.worldline.acquiring.sdk.java.ProxyConfiguration;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.DefaultConnectionBuilder;
+import com.worldline.acquiring.sdk.java.communication.RequestHeader;
+import com.worldline.acquiring.sdk.java.communication.ResponseHandler;
+import com.worldline.acquiring.sdk.java.json.DefaultMarshaller;
+import com.worldline.acquiring.sdk.java.json.Marshaller;
+
+/**
+ * OAuth2 {@link Authenticator} implementation.
+ */
+public class OAuth2Authenticator implements Authenticator {
+
+ private static final Marshaller MARSHALLER = DefaultMarshaller.INSTANCE;
+ private static final String CONTENT_TYPE_KEY = "Content-Type";
+ private static final String CONTENT_TYPE_VALUE = "application/x-www-form-urlencoded";
+ private static final int MAX_CONNECTIONS = 1;
+
+ private final String clientId;
+ private final String clientSecret;
+ private final URI oauth2TokenUri;
+ private final int connectTimeout;
+ private final int socketTimeout;
+ private final ProxyConfiguration proxyConfiguration;
+ private final Set httpsProtocols;
+ private ConcurrentMap accessTokens = new ConcurrentHashMap<>();
+
+ /**
+ * Constructs a new OAuth2Authenticator instance using the provided CommunicatorConfiguration.
+ *
+ * @param communicatorConfiguration The configuration object containing the OAuth2 client id, client secret and token URI,
+ * connect timeout, and socket timeout. None of these can be {@code null} or empty, and
+ * the timeout values must be positive.
+ * @throws IllegalArgumentException If the OAuth2 client id, client secret or token URI from the communicator configuration
+ * is {@code null} or empty, or if the connect timeout or socket timeout is non-positive.
+ */
+ public OAuth2Authenticator(CommunicatorConfiguration communicatorConfiguration) {
+ if (communicatorConfiguration.getOAuth2ClientId() == null || communicatorConfiguration.getOAuth2ClientId().trim().isEmpty()) {
+ throw new IllegalArgumentException("clientId is required");
+ }
+ if (communicatorConfiguration.getOAuth2ClientSecret() == null || communicatorConfiguration.getOAuth2ClientSecret().trim().isEmpty()) {
+ throw new IllegalArgumentException("clientSecret is required");
+ }
+ if (communicatorConfiguration.getOAuth2TokenUri() == null || communicatorConfiguration.getOAuth2TokenUri().trim().isEmpty()) {
+ throw new IllegalArgumentException("tokenUri is required");
+ }
+ if (communicatorConfiguration.getConnectTimeout() <= 0) {
+ throw new IllegalArgumentException("connectTimeout must be positive");
+ }
+ if (communicatorConfiguration.getSocketTimeout() <= 0) {
+ throw new IllegalArgumentException("socketTimeout must be positive");
+ }
+ this.clientId = communicatorConfiguration.getOAuth2ClientId();
+ this.clientSecret = communicatorConfiguration.getOAuth2ClientSecret();
+ this.oauth2TokenUri = URI.create(communicatorConfiguration.getOAuth2TokenUri());
+ this.connectTimeout = communicatorConfiguration.getConnectTimeout();
+ this.socketTimeout = communicatorConfiguration.getSocketTimeout();
+ this.proxyConfiguration = communicatorConfiguration.getProxyConfiguration();
+ this.httpsProtocols = communicatorConfiguration.getHttpsProtocols();
+ }
+
+ @Override
+ public String getAuthorization(String httpMethod, URI resourceUri, List requestHeaders) {
+ OAuth2AccessToken accessToken = accessTokens.compute(TokenType.of(resourceUri.getPath()), (tokenType, existingToken) -> {
+ try {
+ if (isAccessTokenNullOrExpired(existingToken)) {
+ return getAccessToken(tokenType);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ return existingToken;
+ });
+
+ return "Bearer " + accessToken.getAccessToken();
+ }
+
+ private boolean isAccessTokenNullOrExpired(OAuth2AccessToken accessToken) {
+ return accessToken == null || accessToken.getExpirationTime() < System.currentTimeMillis();
+ }
+
+ private OAuth2AccessToken getAccessToken(TokenType tokenType) throws IOException {
+ List requestHeaders = Collections.singletonList(new RequestHeader(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE));
+ String requestBody = getAccessTokenRequestBody(clientId, clientSecret, tokenType.scopes);
+
+ try (Connection connection = new DefaultConnectionBuilder(connectTimeout, socketTimeout)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withProxyConfiguration(proxyConfiguration)
+ .withHttpsProtocols(httpsProtocols)
+ .build()) {
+
+ return connection.post(oauth2TokenUri, requestHeaders, requestBody, getAccessTokenResponseHandler());
+ }
+ }
+
+ private String getAccessTokenRequestBody(String clientId, String clientSecret, String scopes) {
+ return String.format("grant_type=client_credentials&client_id=%s&client_secret=%s&scope=%s", clientId, clientSecret, scopes);
+ }
+
+ private ResponseHandler getAccessTokenResponseHandler() {
+ long startTime = System.currentTimeMillis();
+
+ return (statusCode, bodyStream, headers) -> {
+ OAuth2AccessTokenResponse accessTokenResponse = MARSHALLER.unmarshal(bodyStream, OAuth2AccessTokenResponse.class);
+
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new OAuth2Exception(
+ String.format("There was an error while retrieving the OAuth2 access token: %s - %s",
+ accessTokenResponse.getError(),
+ accessTokenResponse.getErrorDescription()
+ )
+ );
+ }
+
+ long accessTokenExpiresMillis = accessTokenResponse.getExpiresIn() * 1000L;
+ long accessTokenExpirationTime = startTime + accessTokenExpiresMillis;
+
+ return new OAuth2AccessToken(accessTokenResponse.getAccessToken(), accessTokenExpirationTime);
+ };
+ }
+
+ enum TokenType {
+ // Only a limited amount of scopes may be sent in one request.
+ // While at the moment all scopes fit in one request, keep this code so we can easily add more token types if necessary.
+ // The empty path will ensure that all paths will match, as each full path ends with an empty string.
+ DEFAULT("", "processing_payment", "processing_refund", "processing_credittransfer", "processing_accountverification",
+ "processing_operation_reverse", "processing_dcc_rate", "services_ping"),
+ ;
+
+ private final String path;
+ private final String scopes;
+
+ TokenType(String pathMatch, String... scopes) {
+ this.path = pathMatch;
+ this.scopes = String.join(" ", scopes);
+ }
+
+ static TokenType of(String fullPath) {
+ return Arrays.stream(values())
+ .filter(tokenType -> fullPath.endsWith(tokenType.path) || fullPath.contains(tokenType.path + "/"))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(String.format("Scope could not be found for path %s", fullPath)));
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Exception.java b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Exception.java
new file mode 100644
index 0000000..7aab96c
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/authentication/OAuth2Exception.java
@@ -0,0 +1,12 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+/**
+ * Indicates an exception regarding the authorization with the Worldline OAuth2 Authorization Server.
+ */
+@SuppressWarnings("serial")
+public class OAuth2Exception extends RuntimeException {
+
+ public OAuth2Exception(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/CommunicationException.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/CommunicationException.java
new file mode 100644
index 0000000..fbdb5da
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/CommunicationException.java
@@ -0,0 +1,12 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+/**
+ * Indicates an exception regarding the communication with the Worldline Acquiring platform such as a connection exception.
+ */
+@SuppressWarnings("serial")
+public class CommunicationException extends RuntimeException {
+
+ public CommunicationException(Exception e) {
+ super(e);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/Connection.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/Connection.java
new file mode 100644
index 0000000..999055b
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/Connection.java
@@ -0,0 +1,94 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.logging.LoggingCapable;
+import com.worldline.acquiring.sdk.java.logging.ObfuscationCapable;
+
+/**
+ * Represents a connection to the Worldline Acquiring platform server. Thread-safe.
+ */
+public interface Connection extends Closeable, LoggingCapable, ObfuscationCapable {
+
+ /**
+ * Releases any system resources associated with this object.
+ * Should be called when this object is about to go out of scope.
+ */
+ @Override
+ void close() throws IOException;
+
+ /**
+ * Send a GET request to the Worldline Acquiring platform.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R get(URI uri, List requestHeaders, ResponseHandler responseHandler);
+
+ /**
+ * Send a DELETE request to the Worldline Acquiring platform.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R delete(URI uri, List requestHeaders, ResponseHandler responseHandler);
+
+ /**
+ * Send a POST request to the Worldline Acquiring platform.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param body The optional body to send.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R post(URI uri, List requestHeaders, String body, ResponseHandler responseHandler);
+
+ /**
+ * Send a multipart/form-data POST request to the Worldline Acquiring platform.
+ *
+ * The content type of the request will be be part of the given request header list.
+ * If the connection creates its own content type, it should be {@link MultipartFormDataObject#getContentType() multipart.getContentType()}.
+ * Otherwise, authentication failures will occur.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param multipart The multipart/form-data request to send.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R post(URI uri, List requestHeaders, MultipartFormDataObject multipart, ResponseHandler responseHandler);
+
+ /**
+ * Send a PUT request to the Worldline Acquiring platform.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param body The optional body to send.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R put(URI uri, List requestHeaders, String body, ResponseHandler responseHandler);
+
+ /**
+ * Send a multipart/form-data PUT request to the Worldline Acquiring platform.
+ *
+ * The content type of the request will be be part of the given request header list.
+ * If the connection creates its own content type, it should be {@link MultipartFormDataObject#getContentType() multipart.getContentType()}.
+ * Otherwise, authentication failures will occur.
+ *
+ * @param uri The URI to call, including any necessary query parameters.
+ * @param requestHeaders An optional list of request headers.
+ * @param multipart The multipart/form-data request to send.
+ * @param responseHandler A handler for the response.
+ * @throws CommunicationException when an exception occurred communicating with the Worldline Acquiring platform
+ */
+ R put(URI uri, List requestHeaders, MultipartFormDataObject multipart, ResponseHandler responseHandler);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnection.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnection.java
new file mode 100644
index 0000000..464418e
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnection.java
@@ -0,0 +1,689 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.RequestLine;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.routing.HttpRoutePlanner;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.entity.mime.content.ContentBody;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
+import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
+import org.apache.http.impl.conn.DefaultSchemePortResolver;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
+import org.apache.http.impl.io.EmptyInputStream;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.ssl.SSLContexts;
+
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+import com.worldline.acquiring.sdk.java.ProxyConfiguration;
+import com.worldline.acquiring.sdk.java.domain.UploadableFile;
+import com.worldline.acquiring.sdk.java.logging.BodyObfuscator;
+import com.worldline.acquiring.sdk.java.logging.CommunicatorLogger;
+import com.worldline.acquiring.sdk.java.logging.HeaderObfuscator;
+import com.worldline.acquiring.sdk.java.logging.LogMessageBuilder;
+import com.worldline.acquiring.sdk.java.logging.RequestLogMessageBuilder;
+import com.worldline.acquiring.sdk.java.logging.ResponseLogMessageBuilder;
+
+/**
+ * {@link Connection} implementation based on {@link HttpClient}.
+ */
+public class DefaultConnection implements PooledConnection {
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ private static final String REQUEST_ID_ATTRIBUTE = DefaultConnection.class.getName() + ".requestId";
+ private static final String START_TIMME_ATTRIBUTE = DefaultConnection.class.getName() + ".startTme";
+
+ // CloseableHttpClient is marked to be thread safe
+ protected final CloseableHttpClient httpClient;
+
+ // PoolingHttpClientConnectionManager, the implementation used, is marked to be thread safe
+ private final HttpClientConnectionManager connectionManager;
+
+ // RequestConfig is marked to be immutable
+ protected final RequestConfig requestConfig;
+
+ private final AtomicReference bodyObfuscator = new AtomicReference<>(BodyObfuscator.defaultObfuscator());
+ private final AtomicReference headerObfuscator = new AtomicReference<>(HeaderObfuscator.defaultObfuscator());
+
+ private final AtomicReference communicatorLogger = new AtomicReference<>();
+
+ /**
+ * Creates a new connection with the given timeouts, the default number of maximum connections, no proxy and the default HTTPS protocols.
+ *
+ * @see CommunicatorConfiguration#DEFAULT_MAX_CONNECTIONS
+ * @see CommunicatorConfiguration#DEFAULT_HTTPS_PROTOCOLS
+ */
+ public DefaultConnection(int connectTimeout, int socketTimeout) {
+ this(
+ connectTimeout,
+ socketTimeout,
+ CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS,
+ true,
+ null,
+ createSSLConnectionSocketFactory(CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS)
+ );
+ }
+
+ protected DefaultConnection(DefaultConnectionBuilder builder) {
+ this(
+ builder.connectTimeout,
+ builder.socketTimeout,
+ builder.maxConnections,
+ builder.connectionReuse,
+ builder.proxyConfiguration,
+ builder.sslConnectionSocketFactory
+ );
+ }
+
+ private DefaultConnection(int connectTimeout, int socketTimeout, int maxConnections, boolean connectionReuse,
+ ProxyConfiguration proxyConfiguration, SSLConnectionSocketFactory sslConnectionSocketFactory) {
+
+ if (sslConnectionSocketFactory == null) {
+ throw new IllegalArgumentException("sslConnectionSocketFactory is required");
+ }
+ requestConfig = createRequestConfig(connectTimeout, socketTimeout);
+ connectionManager = createHttpClientConnectionManager(maxConnections, sslConnectionSocketFactory);
+ httpClient = createHttpClient(proxyConfiguration, connectionReuse);
+ }
+
+ static SSLConnectionSocketFactory createSSLConnectionSocketFactory(Set httpsProtocols) {
+ SSLContext sslContext = SSLContexts.createDefault();
+ HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
+
+ Set supportedProtocols = httpsProtocols != null && !httpsProtocols.isEmpty() ? httpsProtocols : CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS;
+
+ return new SSLConnectionSocketFactory(sslContext, supportedProtocols.toArray(new String[0]), null, hostnameVerifier);
+ }
+
+ private static RequestConfig createRequestConfig(int connectTimeout, int socketTimeout) {
+ return RequestConfig.custom()
+ .setSocketTimeout(socketTimeout)
+ .setConnectTimeout(connectTimeout)
+ .build();
+ }
+
+ private static HttpClientConnectionManager createHttpClientConnectionManager(int maxConnections, SSLConnectionSocketFactory sslConnectionSocketFactory) {
+ Registry socketFactoryRegistry = RegistryBuilder.create()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", sslConnectionSocketFactory)
+ .build();
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+ connectionManager.setDefaultMaxPerRoute(maxConnections);
+ connectionManager.setMaxTotal(maxConnections + 20);
+ return connectionManager;
+ }
+
+ private CloseableHttpClient createHttpClient(ProxyConfiguration proxyConfiguration, boolean connectionReuse) {
+
+ HttpClientBuilder builder = HttpClients.custom()
+ .setConnectionManager(connectionManager);
+
+ HttpRoutePlanner routePlanner;
+ CredentialsProvider credentialsProvider;
+
+ if (proxyConfiguration != null) {
+ HttpHost proxy = new HttpHost(proxyConfiguration.getHost(), proxyConfiguration.getPort(), proxyConfiguration.getScheme());
+ routePlanner = new DefaultProxyRoutePlanner(proxy, DefaultSchemePortResolver.INSTANCE);
+ credentialsProvider = new BasicCredentialsProvider();
+
+ if (proxyConfiguration.getUsername() != null) {
+ AuthScope authscope = new AuthScope(proxyConfiguration.getHost(), proxyConfiguration.getPort());
+ Credentials credentials = new UsernamePasswordCredentials(proxyConfiguration.getUsername(), proxyConfiguration.getPassword());
+
+ credentialsProvider.setCredentials(authscope, credentials);
+
+ // enable preemptive authentication
+ HttpRequestInterceptor proxyAuthenticationInterceptor = (request, context) -> {
+ Header header = request.getFirstHeader(AUTH.PROXY_AUTH_RESP);
+ if (header == null) {
+ header = new BasicScheme((Charset) null).authenticate(credentials, request, context);
+ if (!AUTH.PROXY_AUTH_RESP.equals(header.getName())) {
+ header = new BasicHeader(AUTH.PROXY_AUTH_RESP, header.getValue());
+ }
+ request.setHeader(header);
+ }
+ };
+ builder = builder.addInterceptorLast(proxyAuthenticationInterceptor);
+ }
+
+ } else {
+ // add support for system properties
+ routePlanner = new SystemDefaultRoutePlanner(DefaultSchemePortResolver.INSTANCE, ProxySelector.getDefault());
+ credentialsProvider = new SystemDefaultCredentialsProvider();
+ }
+
+ // add logging - last for requests, first for responses
+ LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
+ builder = builder.addInterceptorLast((HttpRequestInterceptor) loggingInterceptor);
+ builder = builder.addInterceptorFirst((HttpResponseInterceptor) loggingInterceptor);
+
+ if (!connectionReuse) {
+ builder = builder.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE);
+ }
+
+ return builder
+ .setRoutePlanner(routePlanner)
+ .setDefaultCredentialsProvider(credentialsProvider)
+ .build();
+ }
+
+ @Override
+ public void close() throws IOException {
+ httpClient.close();
+ }
+
+ @Override
+ public R get(URI uri, List requestHeaders, ResponseHandler responseHandler) {
+ HttpGet httpGet = new HttpGet(uri);
+ httpGet.setConfig(requestConfig);
+ addHeaders(httpGet, requestHeaders);
+ return executeRequest(httpGet, responseHandler);
+ }
+
+ @Override
+ public R delete(URI uri, List requestHeaders, ResponseHandler responseHandler) {
+ HttpDelete httpDelete = new HttpDelete(uri);
+ httpDelete.setConfig(requestConfig);
+ addHeaders(httpDelete, requestHeaders);
+ return executeRequest(httpDelete, responseHandler);
+ }
+
+ @Override
+ public R post(URI uri, List requestHeaders, String body, ResponseHandler responseHandler) {
+ HttpEntity requestEntity = createRequestEntity(body);
+ return post(uri, requestHeaders, requestEntity, responseHandler);
+ }
+
+ @Override
+ public R post(URI uri, List requestHeaders, MultipartFormDataObject multipart, ResponseHandler responseHandler) {
+ HttpEntity requestEntity = createRequestEntity(multipart);
+ return post(uri, requestHeaders, requestEntity, responseHandler);
+ }
+
+ private R post(URI uri, List requestHeaders, HttpEntity requestEntity, ResponseHandler responseHandler) {
+ HttpPost httpPost = new HttpPost(uri);
+ httpPost.setConfig(requestConfig);
+ addHeaders(httpPost, requestHeaders);
+ if (requestEntity != null) {
+ httpPost.setEntity(requestEntity);
+ }
+ return executeRequest(httpPost, responseHandler);
+ }
+
+ @Override
+ public R put(URI uri, List requestHeaders, String body, ResponseHandler responseHandler) {
+ HttpEntity requestEntity = createRequestEntity(body);
+ return put(uri, requestHeaders, requestEntity, responseHandler);
+ }
+
+ @Override
+ public R put(URI uri, List requestHeaders, MultipartFormDataObject multipart, ResponseHandler responseHandler) {
+ HttpEntity requestEntity = createRequestEntity(multipart);
+ return put(uri, requestHeaders, requestEntity, responseHandler);
+ }
+
+ private R put(URI uri, List requestHeaders, HttpEntity requestEntity, ResponseHandler responseHandler) {
+ HttpPut httpPut = new HttpPut(uri);
+ httpPut.setConfig(requestConfig);
+ addHeaders(httpPut, requestHeaders);
+ if (requestEntity != null) {
+ httpPut.setEntity(requestEntity);
+ }
+ return executeRequest(httpPut, responseHandler);
+ }
+
+ private static HttpEntity createRequestEntity(String body) {
+ return body != null ? new JsonEntity(body, CHARSET) : null;
+ }
+
+ private static HttpEntity createRequestEntity(MultipartFormDataObject multipart) {
+ return new MultipartFormDataEntity(multipart);
+ }
+
+ @SuppressWarnings("resource")
+ protected R executeRequest(HttpUriRequest request, ResponseHandler responseHandler) {
+ String requestId = UUID.randomUUID().toString();
+ long startTime = System.currentTimeMillis();
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
+ context.setAttribute(START_TIMME_ATTRIBUTE, startTime);
+
+ boolean logRuntimeExceptions = true;
+
+ try {
+ CloseableHttpResponse httpResponse = httpClient.execute(request, context);
+ HttpEntity entity = httpResponse.getEntity();
+ InputStream bodyStream = EmptyInputStream.INSTANCE;
+ try {
+ int statusCode = httpResponse.getStatusLine().getStatusCode();
+ List headers = getHeaders(httpResponse);
+
+ bodyStream = entity == null ? null : entity.getContent();
+ if (bodyStream == null) {
+ bodyStream = EmptyInputStream.INSTANCE;
+ }
+
+ // do not log runtime exceptions that originate from the response handler, as those are not communication errors
+ logRuntimeExceptions = false;
+
+ return responseHandler.handleResponse(statusCode, bodyStream, headers);
+
+ } finally {
+ /*
+ * Ensure that the content stream is closed so the connection can be reused.
+ * Do not close the httpResponse because that will prevent the connection from being reused.
+ * Note that the body stream will always be set; closing the EmptyInputStream instance will do nothing.
+ */
+ if (bodyStream != null) {
+ bodyStream.close();
+ }
+ }
+ } catch (IOException e) {
+ logError(requestId, e, startTime, communicatorLogger.get());
+ throw new CommunicationException(e);
+ } catch (CommunicationException e) {
+ logError(requestId, e, startTime, communicatorLogger.get());
+ throw e;
+ } catch (RuntimeException e) {
+ if (logRuntimeExceptions) {
+ logError(requestId, e, startTime, communicatorLogger.get());
+ }
+ throw e;
+ }
+ }
+
+ protected void addHeaders(HttpRequestBase httpRequestBase, List requestHeaders) {
+ if (requestHeaders != null) {
+ for (RequestHeader requestHeader : requestHeaders) {
+ httpRequestBase.addHeader(new BasicHeader(requestHeader.getName(), requestHeader.getValue()));
+ }
+ }
+ }
+
+ protected List getHeaders(HttpResponse httpResponse) {
+ Header[] headers = httpResponse.getAllHeaders();
+ List result = new ArrayList<>(headers.length);
+ for (Header header : headers) {
+ result.add(new ResponseHeader(header.getName(), header.getValue()));
+ }
+ return result;
+ }
+
+ @Override
+ public void closeIdleConnections(long idleTime, TimeUnit timeUnit) {
+ connectionManager.closeIdleConnections(idleTime, timeUnit);
+ }
+
+ @Override
+ public void closeExpiredConnections() {
+ connectionManager.closeExpiredConnections();
+ }
+
+ @Override
+ public void setBodyObfuscator(BodyObfuscator bodyObfuscator) {
+ if (bodyObfuscator == null) {
+ throw new IllegalArgumentException("bodyObfuscator is required");
+ }
+ this.bodyObfuscator.set(bodyObfuscator);
+ }
+
+ @Override
+ public void setHeaderObfuscator(HeaderObfuscator headerObfuscator) {
+ if (headerObfuscator == null) {
+ throw new IllegalArgumentException("headerObfuscator is required");
+ }
+ this.headerObfuscator.set(headerObfuscator);
+ }
+
+ @Override
+ public void enableLogging(CommunicatorLogger communicatorLogger) {
+ if (communicatorLogger == null) {
+ throw new IllegalArgumentException("communicatorLogger is required");
+ }
+ this.communicatorLogger.set(communicatorLogger);
+ }
+
+ @Override
+ public void disableLogging() {
+ this.communicatorLogger.set(null);
+ }
+
+ // logging code
+
+ private void logRequest(HttpRequest request, String requestId, CommunicatorLogger logger) {
+ try {
+ RequestLine requestLine = request.getRequestLine();
+ String method = requestLine.getMethod();
+ String uri = requestLine.getUri();
+
+ RequestLogMessageBuilder logMessageBuilder = new RequestLogMessageBuilder(requestId, method, uri,
+ bodyObfuscator.get(), headerObfuscator.get());
+ addHeaders(logMessageBuilder, request.getAllHeaders());
+
+ if (request instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) request;
+
+ HttpEntity entity = entityEnclosingRequest.getEntity();
+
+ String contentType = getContentType(entity, () -> request.getFirstHeader(HttpHeaders.CONTENT_TYPE));
+ boolean isBinaryContent = isBinaryContent(contentType);
+
+ if (entity != null && !entity.isRepeatable() && !isBinaryContent) {
+ entity = new BufferedHttpEntity(entity);
+ entityEnclosingRequest.setEntity(entity);
+ }
+
+ setBody(logMessageBuilder, entity, contentType, isBinaryContent);
+ }
+
+ logger.log(logMessageBuilder.getMessage());
+
+ } catch (Exception e) {
+ logger.log(String.format("An error occurred trying to log request '%s'", requestId), e);
+ }
+ }
+
+ private void logResponse(HttpResponse response, String requestId, long startTime, CommunicatorLogger logger) {
+ long endTime = System.currentTimeMillis();
+ long duration = endTime - startTime;
+
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ ResponseLogMessageBuilder logMessageBuilder = new ResponseLogMessageBuilder(requestId, statusCode, duration,
+ bodyObfuscator.get(), headerObfuscator.get());
+ addHeaders(logMessageBuilder, response.getAllHeaders());
+
+ HttpEntity entity = response.getEntity();
+
+ String contentType = getContentType(entity, () -> response.getFirstHeader(HttpHeaders.CONTENT_TYPE));
+ boolean isBinaryContent = isBinaryContent(contentType);
+
+ if (entity != null && !entity.isRepeatable() && !isBinaryContent) {
+ entity = new BufferedHttpEntity(entity);
+ response.setEntity(entity);
+ }
+
+ setBody(logMessageBuilder, entity, contentType, isBinaryContent);
+
+ logger.log(logMessageBuilder.getMessage());
+
+ } catch (Exception e) {
+ logger.log(String.format("An error occurred trying to log response '%s'", requestId), e);
+ }
+ }
+
+ private static void addHeaders(LogMessageBuilder logMessageBuilder, Header[] headers) {
+ if (headers != null) {
+ for (Header header : headers) {
+ logMessageBuilder.addHeader(header.getName(), header.getValue());
+ }
+ }
+ }
+
+ private static String getContentType(HttpEntity entity, Supplier defaultHeaderSupplier) {
+ Header contentTypeHeader = entity != null ? entity.getContentType() : null;
+ if (contentTypeHeader == null) {
+ contentTypeHeader = defaultHeaderSupplier.get();
+ }
+
+ return contentTypeHeader != null ? contentTypeHeader.getValue() : null;
+ }
+
+ private static void setBody(LogMessageBuilder logMessageBuilder, HttpEntity entity, String contentType, boolean isBinaryContent) throws IOException {
+ if (entity == null) {
+ logMessageBuilder.setBody("", contentType);
+
+ } else if (entity instanceof JsonEntity) {
+ String body = ((JsonEntity) entity).getString();
+ logMessageBuilder.setBody(body, contentType);
+
+ } else if (isBinaryContent) {
+ logMessageBuilder.setBinaryContentBody(contentType);
+
+ } else {
+ @SuppressWarnings("resource")
+ InputStream body = entity.getContent();
+ logMessageBuilder.setBody(body, CHARSET, contentType);
+ }
+ }
+
+ private static boolean isBinaryContent(String contentType) {
+ return contentType != null
+ && !contentType.startsWith("text/")
+ && !contentType.contains("json")
+ && !contentType.contains("xml");
+ }
+
+ private static void logError(String requestId, Exception error, long startTime, CommunicatorLogger logger) {
+ if (logger != null) {
+ final String messageTemplate = "Error occurred for outgoing request (requestId='%s', %d ms)";
+
+ long endTime = System.currentTimeMillis();
+ long duration = endTime - startTime;
+
+ String message = String.format(messageTemplate, requestId, duration);
+
+ logger.log(message, error);
+ }
+ }
+
+ private final class LoggingInterceptor implements HttpRequestInterceptor, HttpResponseInterceptor {
+
+ @Override
+ public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+ CommunicatorLogger logger = communicatorLogger.get();
+ if (logger != null) {
+ String requestId = (String) context.getAttribute(REQUEST_ID_ATTRIBUTE);
+ if (requestId != null) {
+ logRequest(request, requestId, logger);
+ }
+ // else the context was not sent through executeRequest
+ }
+ }
+
+ @Override
+ public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
+ CommunicatorLogger logger = communicatorLogger.get();
+ if (logger != null) {
+ String requestId = (String) context.getAttribute(REQUEST_ID_ATTRIBUTE);
+ Long startTime = (Long) context.getAttribute(START_TIMME_ATTRIBUTE);
+ if (requestId != null && startTime != null) {
+ logResponse(response, requestId, startTime, logger);
+ }
+ // else the context was not sent through executeRequest
+ }
+ }
+ }
+
+ private static final class MultipartFormDataEntity implements HttpEntity {
+
+ private static final ContentType TEXT_PLAIN_UTF8 = ContentType.create("text/plain", CHARSET);
+
+ private final HttpEntity delegate;
+ private final boolean isChunked;
+
+ private MultipartFormDataEntity(MultipartFormDataObject multipart) {
+ boolean hasNegativeContentLength = false;
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create()
+ .setBoundary(multipart.getBoundary())
+ .setMode(HttpMultipartMode.RFC6532);
+ for (Map.Entry entry : multipart.getValues().entrySet()) {
+ builder = builder.addTextBody(entry.getKey(), entry.getValue(), TEXT_PLAIN_UTF8);
+ }
+ for (Map.Entry entry : multipart.getFiles().entrySet()) {
+ builder = builder.addPart(entry.getKey(), new UploadableFileBody(entry.getValue()));
+ hasNegativeContentLength |= entry.getValue().getContentLength() < 0;
+ }
+ delegate = builder.build();
+ isChunked = hasNegativeContentLength;
+
+ Header contentType = delegate.getContentType();
+ if (contentType == null || !(multipart.getContentType()).equals(contentType.getValue())) {
+ throw new IllegalStateException("MultipartEntityBuilder did not create the expected content type");
+ }
+ }
+
+ @Override
+ public boolean isRepeatable() {
+ return false;
+ }
+
+ @Override
+ public boolean isChunked() {
+ return isChunked;
+ }
+
+ @Override
+ public long getContentLength() {
+ return delegate.getContentLength();
+ }
+
+ @Override
+ public Header getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override
+ public Header getContentEncoding() {
+ return delegate.getContentEncoding();
+ }
+
+ @Override
+ public InputStream getContent() throws IOException {
+ return delegate.getContent();
+ }
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ delegate.writeTo(outstream);
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return true;
+ }
+
+ @Override
+ @Deprecated
+ public void consumeContent() throws IOException {
+ delegate.consumeContent();
+ }
+ }
+
+ private static final class UploadableFileBody implements ContentBody {
+
+ private final ContentBody delegate;
+ private final long contentLength;
+
+ private UploadableFileBody(UploadableFile file) {
+ @SuppressWarnings("resource")
+ InputStream content = file.getContent();
+ delegate = new InputStreamBody(content, ContentType.create(file.getContentType()), file.getFileName());
+ contentLength = Math.max(file.getContentLength(), -1);
+ }
+
+ @Override
+ public String getMimeType() {
+ return delegate.getMimeType();
+ }
+
+ @Override
+ public String getMediaType() {
+ return delegate.getMediaType();
+ }
+
+ @Override
+ public String getSubType() {
+ return delegate.getSubType();
+ }
+
+ @Override
+ public String getCharset() {
+ return delegate.getCharset();
+ }
+
+ @Override
+ public String getTransferEncoding() {
+ return delegate.getTransferEncoding();
+ }
+
+ @Override
+ public long getContentLength() {
+ return contentLength;
+ }
+
+ @Override
+ public String getFilename() {
+ return delegate.getFilename();
+ }
+
+ @Override
+ public void writeTo(OutputStream out) throws IOException {
+ delegate.writeTo(out);
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionBuilder.java
new file mode 100644
index 0000000..e9affc6
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionBuilder.java
@@ -0,0 +1,87 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.Set;
+
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+import com.worldline.acquiring.sdk.java.ProxyConfiguration;
+
+/**
+ * Builder for {@link DefaultConnection} objects.
+ */
+public class DefaultConnectionBuilder {
+
+ final int connectTimeout;
+ final int socketTimeout;
+ int maxConnections;
+ boolean connectionReuse;
+ ProxyConfiguration proxyConfiguration;
+ SSLConnectionSocketFactory sslConnectionSocketFactory;
+
+ public DefaultConnectionBuilder(int connectTimeout, int socketTimeout) {
+ this.connectTimeout = connectTimeout;
+ this.socketTimeout = socketTimeout;
+
+ maxConnections = CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS;
+ sslConnectionSocketFactory = DefaultConnection.createSSLConnectionSocketFactory(CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ connectionReuse = true;
+ }
+
+ /**
+ * Sets the maximum number of connections. Defaults to {@link CommunicatorConfiguration#DEFAULT_MAX_CONNECTIONS}.
+ */
+ public DefaultConnectionBuilder withMaxConnections(int maxConnections) {
+ this.maxConnections = maxConnections;
+ return this;
+ }
+
+ /**
+ * Sets whether or not connections should be reused. Defaults to on ({@code true}).
+ *
+ * This method can be used to turn off connection reuse. This may be necessary in case (proxy) servers do not
+ * handle reused connections well. This may lead to instances of {@link NoHttpResponseException} to be thrown.
+ */
+ public DefaultConnectionBuilder withConnectionReuse(boolean connectionReuse) {
+ this.connectionReuse = connectionReuse;
+ return this;
+ }
+
+ /**
+ * Sets the proxy configuration to use. Defaults to no proxy configuration.
+ */
+ public DefaultConnectionBuilder withProxyConfiguration(ProxyConfiguration proxyConfiguration) {
+ this.proxyConfiguration = proxyConfiguration;
+ return this;
+ }
+
+ /**
+ * Sets the HTTPS protocols to support. Defaults to {@link CommunicatorConfiguration#DEFAULT_HTTPS_PROTOCOLS}.
+ *
+ * This method is mutually exclusive with {@link #withSslConnectionSocketFactory(SSLConnectionSocketFactory)}.
+ */
+ public DefaultConnectionBuilder withHttpsProtocols(Set httpsProtocols) {
+ return withSslConnectionSocketFactory(DefaultConnection.createSSLConnectionSocketFactory(httpsProtocols));
+ }
+
+ /**
+ * Sets a custom SSL connection socket factory to use.
+ *
+ * This method can be used to provide a fully customizable SSL connection socket factory,
+ * in case the SSL connection socket factory that is created by default cannot be used due to SSL issues.
+ *
+ * This method is mutually exclusive with {@link #withHttpsProtocols(Set)}.
+ */
+ public DefaultConnectionBuilder withSslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) {
+ this.sslConnectionSocketFactory = sslConnectionSocketFactory;
+ return this;
+ }
+
+ /**
+ * Creates a fully initialized {@link DefaultConnection} object.
+ */
+ public DefaultConnection build() {
+ return new DefaultConnection(this);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/JsonEntity.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/JsonEntity.java
new file mode 100644
index 0000000..16cb580
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/JsonEntity.java
@@ -0,0 +1,23 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.nio.charset.Charset;
+
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+
+/**
+ * A sub class of {@link StringEntity} that keeps its original string.
+ */
+class JsonEntity extends StringEntity {
+
+ private final String string;
+
+ JsonEntity(String string, Charset charset) {
+ super(string, ContentType.create(ContentType.APPLICATION_JSON.getMimeType(), charset));
+ this.string = string;
+ }
+
+ String getString() {
+ return string;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProvider.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProvider.java
new file mode 100644
index 0000000..e0982c8
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProvider.java
@@ -0,0 +1,142 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.worldline.acquiring.sdk.java.domain.ShoppingCartExtension;
+import com.worldline.acquiring.sdk.java.json.DefaultMarshaller;
+
+/**
+ * Provides meta info about the server. Thread-safe.
+ */
+public class MetadataProvider {
+
+ private static final String SDK_VERSION = "0.1.0";
+
+ private static final String SERVER_META_INFO_HEADER = "X-WL-ServerMetaInfo";
+ static final Set PROHIBITED_HEADERS;
+
+ static {
+ Set prohibitedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ prohibitedHeaders.add(SERVER_META_INFO_HEADER);
+ prohibitedHeaders.add("Date");
+ prohibitedHeaders.add("Content-Type");
+ prohibitedHeaders.add("Authorization");
+
+ PROHIBITED_HEADERS = Collections.unmodifiableSet(prohibitedHeaders);
+ }
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ static final class ServerMetaInfo {
+ String platformIdentifier;
+ String sdkIdentifier;
+ String sdkCreator;
+ String integrator;
+ ShoppingCartExtension shoppingCartExtension;
+ }
+
+ private final Collection metadataHeaders;
+
+ public MetadataProvider(String integrator) {
+ this(
+ integrator,
+ null,
+ Collections.emptyList()
+ );
+ }
+
+ protected MetadataProvider(MetadataProviderBuilder builder) {
+ this(
+ builder.integrator,
+ builder.shoppingCartExtension,
+ builder.additionalRequestHeaders
+ );
+ }
+
+ private MetadataProvider(String integrator,
+ ShoppingCartExtension shoppingCartExtension,
+ Collection additionalRequestHeaders) {
+
+ if (integrator == null || integrator.trim().isEmpty()) {
+ throw new IllegalArgumentException("integrator is required");
+ }
+
+ validateAdditionalRequestHeaders(additionalRequestHeaders);
+
+ ServerMetaInfo serverMetaInfo = new ServerMetaInfo();
+ serverMetaInfo.platformIdentifier = getPlatformIdentifier();
+ serverMetaInfo.sdkIdentifier = getSdkIdentifier();
+ serverMetaInfo.sdkCreator = "Worldline";
+ serverMetaInfo.integrator = integrator;
+ serverMetaInfo.shoppingCartExtension = shoppingCartExtension;
+
+ String serverMetaInfoString = DefaultMarshaller.INSTANCE.marshal(serverMetaInfo);
+ RequestHeader serverMetaInfoHeader = new RequestHeader(SERVER_META_INFO_HEADER,
+ Base64.getEncoder().encodeToString(serverMetaInfoString.getBytes(CHARSET)));
+
+ if (additionalRequestHeaders == null || additionalRequestHeaders.isEmpty()) {
+ metadataHeaders = Collections.singleton(serverMetaInfoHeader);
+ } else {
+ List requestHeaders = new ArrayList<>(additionalRequestHeaders.size() + 1);
+ requestHeaders.add(serverMetaInfoHeader);
+ requestHeaders.addAll(additionalRequestHeaders);
+ metadataHeaders = Collections.unmodifiableList(requestHeaders);
+ }
+ }
+
+ static void validateAdditionalRequestHeaders(Collection additionalRequestHeaders) {
+ if (additionalRequestHeaders != null) {
+ for (RequestHeader additionalRequestHeader : additionalRequestHeaders) {
+ validateAdditionalRequestHeader(additionalRequestHeader);
+ }
+ }
+ }
+
+ static void validateAdditionalRequestHeader(RequestHeader additionalRequestHeader) {
+ if (PROHIBITED_HEADERS.contains(additionalRequestHeader.getName())) {
+ throw new IllegalArgumentException("request header not allowed: " + additionalRequestHeader);
+ }
+ }
+
+ /**
+ * @return The server related headers containing the metadata to be associated with the request (if any).
+ * This will always contain at least an automatically generated header {@code X-WL-ServerMetaInfo}.
+ */
+ public final Collection getServerMetadataHeaders() {
+ return metadataHeaders;
+ }
+
+ protected final String getPlatformIdentifier() {
+ Properties properties = System.getProperties();
+ StringBuilder sb = new StringBuilder();
+ sb.append(properties.get("os.name"));
+ sb.append("/");
+ sb.append(properties.get("os.version"));
+ sb.append(" ");
+ sb.append("Java");
+ sb.append("/");
+ sb.append(properties.get("java.vm.specification.version"));
+ sb.append(" ");
+ sb.append("(");
+ sb.append(properties.get("java.vm.vendor"));
+ sb.append("; ");
+ sb.append(properties.get("java.vm.name"));
+ sb.append("; ");
+ sb.append(properties.get("java.version"));
+ sb.append(")");
+ return sb.toString();
+ }
+
+ protected final String getSdkIdentifier() {
+ return "JavaServerSDK/v" + SDK_VERSION;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProviderBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProviderBuilder.java
new file mode 100644
index 0000000..ecac8c0
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/MetadataProviderBuilder.java
@@ -0,0 +1,60 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.worldline.acquiring.sdk.java.domain.ShoppingCartExtension;
+
+/**
+ * Builder for a {@link MetadataProvider} object.
+ */
+public class MetadataProviderBuilder {
+
+ final String integrator;
+
+ ShoppingCartExtension shoppingCartExtension;
+
+ final List additionalRequestHeaders = new ArrayList<>();
+
+ /**
+ * @param integrator The integrator to use.
+ */
+ public MetadataProviderBuilder(String integrator) {
+ if (integrator == null || integrator.trim().isEmpty()) {
+ throw new IllegalArgumentException("integrator is required");
+ }
+ this.integrator = integrator;
+ }
+
+ /**
+ * Sets the shopping cart extension to use.
+ */
+ public MetadataProviderBuilder withShoppingCartExtension(ShoppingCartExtension shoppingCartExtension) {
+ this.shoppingCartExtension = shoppingCartExtension;
+ return this;
+ }
+
+ /**
+ * Adds an additional request header.
+ * The following names are prohibited in these additional request headers, because these will be set automatically
+ * as needed:
+ *
+ * - X-WL-ServerMetaInfo
+ * - Date
+ * - Content-Type
+ * - Authorization
+ *
+ */
+ public MetadataProviderBuilder withAdditionalRequestHeader(RequestHeader additionalRequestHeader) {
+ MetadataProvider.validateAdditionalRequestHeader(additionalRequestHeader);
+ additionalRequestHeaders.add(additionalRequestHeader);
+ return this;
+ }
+
+ /**
+ * Creates a fully initialized {@link MetadataProvider} object.
+ */
+ public MetadataProvider build() {
+ return new MetadataProvider(this);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataObject.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataObject.java
new file mode 100644
index 0000000..259da2e
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataObject.java
@@ -0,0 +1,69 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import com.worldline.acquiring.sdk.java.domain.UploadableFile;
+
+/**
+ * A representation of a multipart/form-data object.
+ */
+public class MultipartFormDataObject {
+
+ private final String boundary;
+ private final String contentType;
+
+ private final Map values;
+ private final Map files;
+
+ public MultipartFormDataObject() {
+ boundary = UUID.randomUUID().toString();
+ contentType = "multipart/form-data; boundary=" + boundary;
+ values = new LinkedHashMap<>();
+ files = new LinkedHashMap<>();
+ }
+
+ public String getBoundary() {
+ return boundary;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public Map getValues() {
+ return Collections.unmodifiableMap(values);
+ }
+
+ public Map getFiles() {
+ return Collections.unmodifiableMap(files);
+ }
+
+ public void addValue(String parameterName, String value) {
+ if (parameterName == null || parameterName.trim().isEmpty()) {
+ throw new IllegalArgumentException("parameterName is required");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("value is required");
+ }
+ if (values.containsKey(parameterName) || files.containsKey(parameterName)) {
+ throw new IllegalArgumentException("duplicate parameterName: " + parameterName);
+ }
+ values.put(parameterName, value);
+ }
+
+ public void addFile(String parameterName, UploadableFile file) {
+ if (parameterName == null || parameterName.trim().isEmpty()) {
+ throw new IllegalArgumentException("parameterName is required");
+ }
+ if (file == null) {
+ throw new IllegalArgumentException("file is required");
+ }
+ if (values.containsKey(parameterName) || files.containsKey(parameterName)) {
+ throw new IllegalArgumentException("duplicate parameterName: " + parameterName);
+ }
+ files.put(parameterName, file);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataRequest.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataRequest.java
new file mode 100644
index 0000000..34797c6
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/MultipartFormDataRequest.java
@@ -0,0 +1,9 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+/**
+ * Represents a multipart/form-data request.
+ */
+public interface MultipartFormDataRequest {
+
+ MultipartFormDataObject toMultipartFormDataObject();
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/NotFoundException.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/NotFoundException.java
new file mode 100644
index 0000000..49d0525
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/NotFoundException.java
@@ -0,0 +1,18 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+/**
+ * Indicates an exception that occurs when the requested resource is not found.
+ * In normal usage of the SDK, this exception should not occur, however it is possible.
+ * For example when path parameters are set with invalid values.
+ */
+@SuppressWarnings("serial")
+public class NotFoundException extends RuntimeException {
+
+ public NotFoundException(Exception e) {
+ super(e);
+ }
+
+ public NotFoundException(String message, Exception e) {
+ super(message, e);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/ParamRequest.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/ParamRequest.java
new file mode 100644
index 0000000..b6eae26
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/ParamRequest.java
@@ -0,0 +1,11 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.List;
+
+/**
+ * Represents a set of request parameters.
+ */
+public interface ParamRequest {
+
+ List toRequestParameters();
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/PooledConnection.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/PooledConnection.java
new file mode 100644
index 0000000..89f1781
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/PooledConnection.java
@@ -0,0 +1,24 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents a pooled connection to the Worldline Acquiring platform server.
+ * Instead of setting up a new HTTP connection for each request, this connection uses a pool of HTTP connections.
+ * Thread-safe.
+ */
+public interface PooledConnection extends Connection {
+
+ /**
+ * Closes all HTTP connections that have been idle for the specified time.
+ * This should also include all expired HTTP connections.
+ *
+ * @see #closeExpiredConnections()
+ */
+ void closeIdleConnections(long idleTime, TimeUnit timeUnit);
+
+ /**
+ * Closes all expired HTTP connections.
+ */
+ void closeExpiredConnections();
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestHeader.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestHeader.java
new file mode 100644
index 0000000..a7fad4d
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestHeader.java
@@ -0,0 +1,67 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A single request header. Immutable.
+ */
+public class RequestHeader {
+
+ private static final Pattern WHITE_SPACE_NORMALIZER = Pattern.compile("\r?\n[\\s&&[^\r\n]]*");
+
+ private final String name;
+ private final String value;
+
+ public RequestHeader(String name, String value) {
+ if (name == null || name.trim().isEmpty()) {
+ throw new IllegalArgumentException("name is required");
+ }
+ this.name = name;
+ this.value = normalizeValue(value);
+ }
+
+ static String normalizeValue(String value) {
+ if (value == null || value.isEmpty()) {
+ return value;
+ }
+ // Replace all sequences of linebreak-whitespace* with a single space
+ return WHITE_SPACE_NORMALIZER.matcher(value).replaceAll(" ").trim();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The un-encoded value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + ":" + getValue();
+ }
+
+ /**
+ * @return The header from the given list with the given name, or {@code null} if there was no such header.
+ */
+ public static RequestHeader getHeader(List headers, String headerName) {
+ for (RequestHeader header : headers) {
+ if (header.getName().equalsIgnoreCase(headerName)) {
+ return header;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return The value of the header from the given list with the given name, or {@code null} if there was no such header.
+ */
+ public static String getHeaderValue(List headers, String headerName) {
+ RequestHeader header = getHeader(headers, headerName);
+ return header != null ? header.getValue() : null;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestParam.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestParam.java
new file mode 100644
index 0000000..ebc5962
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/RequestParam.java
@@ -0,0 +1,34 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+/**
+ * A single request parameter. Immutable.
+ */
+public class RequestParam {
+
+ private final String name;
+ private final String value;
+
+ public RequestParam(String name, String value) {
+ if (name == null || name.trim().isEmpty()) {
+ throw new IllegalArgumentException("name is required");
+ }
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The un-encoded value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + ":" + getValue();
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseException.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseException.java
new file mode 100644
index 0000000..404f8c2
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseException.java
@@ -0,0 +1,69 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Thrown when a response was received from the Worldline Acquiring platform which indicates an error.
+ */
+@SuppressWarnings("serial")
+public class ResponseException extends RuntimeException {
+
+ private final int statusCode;
+ private final String body;
+ private final List headers;
+
+ public ResponseException(int statusCode, String body, List headers) {
+ super("the Worldline Acquiring platform returned an error response");
+ this.statusCode = statusCode;
+ this.body = body;
+ this.headers = headers != null ? Collections.unmodifiableList(headers) : Collections.emptyList();
+ }
+
+ /**
+ * @return The HTTP status code that was returned by the Worldline Acquiring platform.
+ */
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * @return The raw response body that was returned by the Worldline Acquiring platform.
+ */
+ public String getBody() {
+ return body;
+ }
+
+ /**
+ * @return The headers that were returned by the Worldline Acquiring platform. Never {@code null}.
+ */
+ public List getHeaders() {
+ return headers;
+ }
+
+ /**
+ * @return The header with the given name, or {@code null} if there was no such header.
+ */
+ public ResponseHeader getHeader(String headerName) {
+ return ResponseHeader.getHeader(headers, headerName);
+ }
+
+ /**
+ * @return The value of the header with the given name, or {@code null} if there was no such header.
+ */
+ public String getHeaderValue(String headerName) {
+ return ResponseHeader.getHeaderValue(headers, headerName);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(super.toString());
+ if (statusCode > 0) {
+ sb.append("; statusCode=").append(statusCode);
+ }
+ if (body != null && body.length() > 0) {
+ sb.append("; responseBody='").append(body).append("'");
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHandler.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHandler.java
new file mode 100644
index 0000000..9ce9951
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHandler.java
@@ -0,0 +1,17 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * An interface for handling responses from the Worldline Acquiring platform.
+ */
+public interface ResponseHandler {
+
+ /**
+ * @param statusCode The HTTP status code that was returned by the Worldline Acquiring platform.
+ * @param bodyStream The raw response body that was returned by the Worldline Acquiring platform. Note that it will be closed outside of this method.
+ * @param headers The headers that were returned by the Worldline Acquiring platform. Never {@code null}.
+ */
+ R handleResponse(int statusCode, InputStream bodyStream, List headers);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHeader.java b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHeader.java
new file mode 100644
index 0000000..6cfef3f
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/communication/ResponseHeader.java
@@ -0,0 +1,88 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A single response header. Immutable.
+ */
+public class ResponseHeader {
+
+ private static final Pattern DISPOSITION_FILENAME_PATTERN = Pattern.compile("(?:^|;)\\s*filename\\s*=\\s*(.*?)\\s*(?:;|$)", Pattern.CASE_INSENSITIVE);
+
+ private final String name;
+ private final String value;
+
+ public ResponseHeader(String name, String value) {
+ if (name == null) {
+ throw new IllegalArgumentException("name is required");
+ }
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The un-encoded value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + ":" + getValue();
+ }
+
+ /**
+ * @return The header from the given list with the given name, or {@code null} if there was no such header.
+ */
+ public static ResponseHeader getHeader(List headers, String headerName) {
+ for (ResponseHeader header : headers) {
+ if (header.getName().equalsIgnoreCase(headerName)) {
+ return header;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return The value of the header from the given list with the given name, or {@code null} if there was no such header.
+ */
+ public static String getHeaderValue(List headers, String headerName) {
+ ResponseHeader header = getHeader(headers, headerName);
+ return header != null ? header.getValue() : null;
+ }
+
+ /**
+ * @return The value of the {@code filename} parameter of the {@code Content-Disposition} header from the given list,
+ * or {@code null} if there was no such header or parameter.
+ */
+ public static String getDispositionFilename(List headers) {
+ String headerValue = getHeaderValue(headers, "Content-Disposition");
+ if (headerValue == null) {
+ return null;
+ }
+ Matcher matcher = DISPOSITION_FILENAME_PATTERN.matcher(headerValue);
+ if (matcher.find()) {
+ String filename = matcher.group(1);
+ return trimQuotes(filename);
+ }
+ return null;
+ }
+
+ private static String trimQuotes(String filename) {
+ if (filename.length() < 2) {
+ return filename;
+ }
+ if (filename.startsWith("\"") && filename.endsWith("\"")
+ || filename.startsWith("'") && filename.endsWith("'")) {
+ return filename.substring(1, filename.length() - 1);
+ }
+ return filename;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/domain/ShoppingCartExtension.java b/src/main/java/com/worldline/acquiring/sdk/java/domain/ShoppingCartExtension.java
new file mode 100644
index 0000000..183a33d
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/domain/ShoppingCartExtension.java
@@ -0,0 +1,60 @@
+package com.worldline.acquiring.sdk.java.domain;
+
+public class ShoppingCartExtension {
+
+ private final String creator;
+ private final String name;
+ private final String version;
+ private final String extensionId;
+
+ public ShoppingCartExtension(String creator, String name, String version) {
+ if (creator == null || creator.trim().isEmpty()) {
+ throw new IllegalArgumentException("creator is required");
+ }
+ if (name == null || name.trim().isEmpty()) {
+ throw new IllegalArgumentException("name is required");
+ }
+ if (version == null || version.trim().isEmpty()) {
+ throw new IllegalArgumentException("version is required");
+ }
+ this.creator = creator;
+ this.name = name;
+ this.version = version;
+ this.extensionId = null;
+ }
+
+ public ShoppingCartExtension(String creator, String name, String version, String extensionId) {
+ if (creator == null || creator.trim().isEmpty()) {
+ throw new IllegalArgumentException("creator is required");
+ }
+ if (name == null || name.trim().isEmpty()) {
+ throw new IllegalArgumentException("name is required");
+ }
+ if (version == null || version.trim().isEmpty()) {
+ throw new IllegalArgumentException("version is required");
+ }
+ if (extensionId == null || extensionId.trim().isEmpty()) {
+ throw new IllegalArgumentException("extensionId is required");
+ }
+ this.creator = creator;
+ this.name = name;
+ this.version = version;
+ this.extensionId = extensionId;
+ }
+
+ public String getCreator() {
+ return creator;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getExtensionId() {
+ return extensionId;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/domain/UploadableFile.java b/src/main/java/com/worldline/acquiring/sdk/java/domain/UploadableFile.java
new file mode 100644
index 0000000..c800609
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/domain/UploadableFile.java
@@ -0,0 +1,62 @@
+package com.worldline.acquiring.sdk.java.domain;
+
+import java.io.InputStream;
+
+/**
+ * A file that can be uploaded.
+ */
+public class UploadableFile {
+
+ private final String fileName;
+ private final InputStream content;
+ private final String contentType;
+ private final long contentLength;
+
+ public UploadableFile(String fileName, InputStream content, String contentType) {
+ this(fileName, content, contentType, -1);
+ }
+
+ public UploadableFile(String fileName, InputStream content, String contentType, long contentLength) {
+ if (fileName == null || fileName.trim().isEmpty()) {
+ throw new IllegalArgumentException("fileName is required");
+ }
+ if (content == null) {
+ throw new IllegalArgumentException("content is required");
+ }
+ if (contentType == null || contentType.trim().isEmpty()) {
+ throw new IllegalArgumentException("contentType is required");
+ }
+ this.fileName = fileName;
+ this.content = content;
+ this.contentType = contentType;
+ this.contentLength = Math.max(contentLength, -1);
+ }
+
+ /**
+ * @return The name of the file.
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * @return An input stream with the file's content.
+ */
+ public InputStream getContent() {
+ return content;
+ }
+
+ /**
+ * @return The file's content type.
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * @return The file's content length, or -1 if not known.
+ */
+ public long getContentLength() {
+ return contentLength;
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/json/DefaultMarshaller.java b/src/main/java/com/worldline/acquiring/sdk/java/json/DefaultMarshaller.java
new file mode 100644
index 0000000..69bd4f6
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/json/DefaultMarshaller.java
@@ -0,0 +1,88 @@
+package com.worldline.acquiring.sdk.java.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * {@link Marshaller} implementation based on {@link Gson}.
+ */
+public class DefaultMarshaller implements Marshaller {
+
+ // Gson instances are thread-safe, so reuse one single instance
+ private static final Gson GSON = new GsonBuilder()
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
+ .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter().nullSafe())
+ .create();
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ public static final DefaultMarshaller INSTANCE = new DefaultMarshaller();
+
+ protected DefaultMarshaller() {
+ }
+
+ @Override
+ public String marshal(Object requestObject) {
+ return GSON.toJson(requestObject);
+ }
+
+ @Override
+ public T unmarshal(String responseJson, Class type) {
+ try {
+ return GSON.fromJson(responseJson, type);
+ } catch (JsonSyntaxException e) {
+ throw new MarshallerSyntaxException(e);
+ }
+ }
+
+ @Override
+ public T unmarshal(InputStream responseJson, Class type) {
+ try {
+ return GSON.fromJson(new InputStreamReader(responseJson, CHARSET), type);
+ } catch (JsonSyntaxException e) {
+ throw new MarshallerSyntaxException(e);
+ }
+ }
+
+ private static final class LocalDateAdapter extends TypeAdapter {
+
+ @Override
+ @SuppressWarnings("resource")
+ public void write(JsonWriter out, LocalDate value) throws IOException {
+ out.value(value.format(DateTimeFormatter.ISO_DATE));
+ }
+
+ @Override
+ public LocalDate read(JsonReader in) throws IOException {
+ return LocalDate.parse(in.nextString(), DateTimeFormatter.ISO_DATE);
+ }
+ }
+
+ private static final class ZonedDateTimeAdapter extends TypeAdapter {
+
+ @Override
+ @SuppressWarnings("resource")
+ public void write(JsonWriter out, ZonedDateTime value) throws IOException {
+ out.value(value.truncatedTo(ChronoUnit.MILLIS).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+ }
+
+ @Override
+ public ZonedDateTime read(JsonReader in) throws IOException {
+ return ZonedDateTime.parse(in.nextString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/json/Marshaller.java b/src/main/java/com/worldline/acquiring/sdk/java/json/Marshaller.java
new file mode 100644
index 0000000..0cba4f7
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/json/Marshaller.java
@@ -0,0 +1,30 @@
+package com.worldline.acquiring.sdk.java.json;
+
+import java.io.InputStream;
+
+/**
+ * Used to marshal and unmarshal Worldline Acquiring platform request and response objects to and from JSON. Thread-safe.
+ */
+public interface Marshaller {
+
+ /**
+ * Marshal a request object to a JSON string.
+ */
+ String marshal(Object requestObject);
+
+ /**
+ * Unmarshal a JSON string to a response object.
+ *
+ * @param type The response object type.
+ * @throws MarshallerSyntaxException if the JSON is not a valid representation for an object of the given type
+ */
+ T unmarshal(String responseJson, Class type);
+
+ /**
+ * Unmarshal a JSON stream to a response object.
+ *
+ * @param type The response object type.
+ * @throws MarshallerSyntaxException if the JSON is not a valid representation for an object of the given type
+ */
+ T unmarshal(InputStream responseJson, Class type);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/json/MarshallerSyntaxException.java b/src/main/java/com/worldline/acquiring/sdk/java/json/MarshallerSyntaxException.java
new file mode 100644
index 0000000..5f4dd05
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/json/MarshallerSyntaxException.java
@@ -0,0 +1,16 @@
+package com.worldline.acquiring.sdk.java.json;
+
+/**
+ * Thrown when a JSON string cannot be converted to a response object.
+ */
+@SuppressWarnings("serial")
+public class MarshallerSyntaxException extends RuntimeException {
+
+ public MarshallerSyntaxException() {
+ super();
+ }
+
+ public MarshallerSyntaxException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/BodyObfuscator.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/BodyObfuscator.java
new file mode 100644
index 0000000..0b0352c
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/BodyObfuscator.java
@@ -0,0 +1,218 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class that can be used to obfuscate properties in JSON bodies. Thread-safe if all its obfuscation rules are.
+ */
+public final class BodyObfuscator {
+
+ private static final BodyObfuscator DEFAULT_OBFUSCATOR = custom().build();
+
+ private final Map obfuscationRules;
+ private final Pattern propertyPattern;
+
+ private BodyObfuscator(Map obfuscationRules) {
+ // case sensitive
+ this.obfuscationRules = Collections.unmodifiableMap(new LinkedHashMap<>(obfuscationRules));
+ this.propertyPattern = buildPropertyPattern(obfuscationRules.keySet());
+ }
+
+ private static Pattern buildPropertyPattern(Set propertyNames) {
+ if (propertyNames.isEmpty()) {
+ // no matches possible
+ return Pattern.compile("$^");
+ }
+
+ Iterator iterator = propertyNames.iterator();
+
+ /*
+ * Regex to create: (["'])(X|Y|Z)\1\s*:\s*(?:(["'])(.*?)(? obfuscationRules;
+
+ private Builder() {
+ obfuscationRules = new LinkedHashMap<>();
+ }
+
+ /**
+ * Adds an obfuscation rule that will replace all characters with {@code *}.
+ */
+ public Builder obfuscateAll(String propertyName) {
+ obfuscationRules.put(propertyName, ValueObfuscator.ALL);
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will replace values with a fixed length string containing
+ * only {@code *}.
+ */
+ public Builder obfuscateWithFixedLength(int fixedLength, String propertyName) {
+ obfuscationRules.put(propertyName, ValueObfuscator.fixedLength(fixedLength));
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will keep a fixed number of characters at the start, then
+ * replaces all other characters with {@code *}.
+ */
+ public Builder obfuscateAllButFirst(int count, String propertyName) {
+ obfuscationRules.put(propertyName, ValueObfuscator.keepStartCount(count));
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will keep a fixed number of characters at the end, then
+ * replaces all other characters with {@code *}.
+ */
+ public Builder obfuscateAllButLast(int count, String propertyName) {
+ obfuscationRules.put(propertyName, ValueObfuscator.keepEndCount(count));
+ return this;
+ }
+
+ /**
+ * Adds a custom, non-{@code null} obfuscation rule.
+ */
+ public Builder obfuscateCustom(String propertyName, ObfuscationRule obfuscationRule) {
+ if (obfuscationRule == null) {
+ throw new IllegalArgumentException("obfuscationRule is required");
+ }
+ obfuscationRules.put(propertyName, obfuscationRule);
+ return this;
+ }
+
+ public BodyObfuscator build() {
+ return new BodyObfuscator(obfuscationRules);
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/CommunicatorLogger.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/CommunicatorLogger.java
new file mode 100644
index 0000000..f7a1c05
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/CommunicatorLogger.java
@@ -0,0 +1,22 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * Used to log messages from communicators. Thread-safe.
+ */
+public interface CommunicatorLogger {
+
+ /**
+ * Logs a message.
+ *
+ * @param message The message to log.
+ */
+ void log(String message);
+
+ /**
+ * Logs a throwable with an accompanying message.
+ *
+ * @param message The message accompanying the throwable.
+ * @param thrown The throwable to log.
+ */
+ void log(String message, Throwable thrown);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/HeaderObfuscator.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/HeaderObfuscator.java
new file mode 100644
index 0000000..6920bb3
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/HeaderObfuscator.java
@@ -0,0 +1,111 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class that can be used to obfuscate headers. Thread-safe if all its obfuscation rules are.
+ */
+public final class HeaderObfuscator {
+
+ private static final HeaderObfuscator DEFAULT_OBFUSCATOR = custom().build();
+
+ private final Map obfuscationRules;
+
+ private HeaderObfuscator(Map obfuscationRules) {
+ // case insensitive
+ Map copy = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ copy.putAll(obfuscationRules);
+ this.obfuscationRules = Collections.unmodifiableMap(copy);
+ }
+
+ /**
+ * Obfuscates the value for the given header as necessary.
+ */
+ public String obfuscateHeader(String name, String value) {
+ ObfuscationRule obfuscationRule = obfuscationRules.get(name);
+ return obfuscationRule != null ? obfuscationRule.obfuscateValue(value) : value;
+ }
+
+ /**
+ * Returns a builder to create custom header obfuscators.
+ * This builder will contain some pre-defined obfuscation rules. These cannot be removed, but
+ * replacing them is possible.
+ */
+ public static Builder custom() {
+ return new Builder()
+ .obfuscateWithFixedLength(8, "Authorization")
+ .obfuscateWithFixedLength(8, "WWW-Authenticate")
+ .obfuscateWithFixedLength(8, "Proxy-Authenticate")
+ .obfuscateWithFixedLength(8, "Proxy-Authorization");
+ }
+
+ /**
+ * Returns a default header obfuscator. The result will be equivalent to calling
+ * {@link #custom()}.{@link Builder#build() build()}.
+ */
+ public static HeaderObfuscator defaultObfuscator() {
+ return DEFAULT_OBFUSCATOR;
+ }
+
+ public static final class Builder {
+
+ private final Map obfuscationRules;
+
+ private Builder() {
+ obfuscationRules = new LinkedHashMap<>();
+ }
+
+ /**
+ * Adds an obfuscation rule that will replace all characters with {@code *}.
+ */
+ public Builder obfuscateAll(String headerName) {
+ obfuscationRules.put(headerName, ValueObfuscator.ALL);
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will replace values with a fixed length string containing
+ * only {@code *}.
+ */
+ public Builder obfuscateWithFixedLength(int fixedLength, String headerName) {
+ obfuscationRules.put(headerName, ValueObfuscator.fixedLength(fixedLength));
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will keep a fixed number of characters at the start, then
+ * replaces all other characters with {@code *}.
+ */
+ public Builder obfuscateAllButFirst(int count, String headerName) {
+ obfuscationRules.put(headerName, ValueObfuscator.keepStartCount(count));
+ return this;
+ }
+
+ /**
+ * Adds an obfuscation rule that will keep a fixed number of characters at the end, then
+ * replaces all other characters with {@code *}.
+ */
+ public Builder obfuscateAllButLast(int count, String headerName) {
+ obfuscationRules.put(headerName, ValueObfuscator.keepEndCount(count));
+ return this;
+ }
+
+ /**
+ * Adds a custom, non-{@code null} obfuscation rule.
+ */
+ public Builder obfuscateCustom(String headerName, ObfuscationRule obfuscationRule) {
+ if (obfuscationRule == null) {
+ throw new IllegalArgumentException("obfuscationRule is required");
+ }
+ obfuscationRules.put(headerName, obfuscationRule);
+ return this;
+ }
+
+ public HeaderObfuscator build() {
+ return new HeaderObfuscator(obfuscationRules);
+ }
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/JdkCommunicatorLogger.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/JdkCommunicatorLogger.java
new file mode 100644
index 0000000..742fff6
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/JdkCommunicatorLogger.java
@@ -0,0 +1,56 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A communicator logger that is backed by a {@link java.util.logging.Logger}.
+ */
+public class JdkCommunicatorLogger implements CommunicatorLogger {
+
+ private final Logger logger;
+ private final Level logLevel;
+ private final Level errorLogLevel;
+
+ /**
+ * Creates a new communicator logger.
+ *
+ * @param logger The backing logger.
+ * @param level The level to use when logging through both {@link #log(String)} and {@link #log(String, Throwable)}.
+ */
+ public JdkCommunicatorLogger(Logger logger, Level level) {
+ this(logger, level, level);
+ }
+
+ /**
+ * Creates a new communicator logger.
+ *
+ * @param logger The backing logger.
+ * @param logLevel The level to use when logging through {@link #log(String)}.
+ * @param errorLogLevel The level to use when logging through {@link #log(String, Throwable)}.
+ */
+ public JdkCommunicatorLogger(Logger logger, Level logLevel, Level errorLogLevel) {
+ if (logger == null) {
+ throw new IllegalArgumentException("logger is required");
+ }
+ if (logLevel == null) {
+ throw new IllegalArgumentException("logLevel is required");
+ }
+ if (errorLogLevel == null) {
+ throw new IllegalArgumentException("exceptionLogLevel is required");
+ }
+ this.logger = logger;
+ this.logLevel = logLevel;
+ this.errorLogLevel = errorLogLevel;
+ }
+
+ @Override
+ public void log(String message) {
+ logger.log(logLevel, message);
+ }
+
+ @Override
+ public void log(String message, Throwable thrown) {
+ logger.log(errorLogLevel, message, thrown);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/LogMessageBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/LogMessageBuilder.java
new file mode 100644
index 0000000..264e9dc
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/LogMessageBuilder.java
@@ -0,0 +1,104 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+/**
+ * A utility class to build log messages.
+ */
+public abstract class LogMessageBuilder {
+
+ private final String requestId;
+ private final StringBuilder headers;
+
+ private String body;
+ private String contentType;
+
+ private final BodyObfuscator bodyObfuscator;
+ private final HeaderObfuscator headerObfuscator;
+
+ protected LogMessageBuilder(String requestId, BodyObfuscator bodyObfuscator, HeaderObfuscator headerObfuscator) {
+ if (requestId == null || requestId.isEmpty()) {
+ throw new IllegalArgumentException("requestId is required");
+ }
+ if (bodyObfuscator == null) {
+ throw new IllegalArgumentException("bodyObfuscator is required");
+ }
+ if (headerObfuscator == null) {
+ throw new IllegalArgumentException("headerObfuscator is required");
+ }
+
+ this.requestId = requestId;
+ this.headers = new StringBuilder();
+ this.bodyObfuscator = bodyObfuscator;
+ this.headerObfuscator = headerObfuscator;
+ }
+
+ protected final String requestId() {
+ return requestId;
+ }
+
+ protected final String headers() {
+ return headers.toString();
+ }
+
+ protected final String body() {
+ return body;
+ }
+
+ protected final String contentType() {
+ return contentType;
+ }
+
+ public final void addHeader(String name, String value) {
+ if (headers.length() > 0) {
+ headers.append(", ");
+ }
+
+ headers.append(name);
+ headers.append("=\"");
+ if (value != null) {
+ String obfuscatedValue = headerObfuscator.obfuscateHeader(name, value);
+ headers.append(obfuscatedValue);
+ }
+ headers.append('"');
+ }
+
+ public final void setBody(String body, String contentType) {
+ this.body = bodyObfuscator.obfuscateBody(body);
+ this.contentType = contentType;
+ }
+
+ public final void setBody(InputStream bodyStream, Charset charset, String contentType) throws IOException {
+ this.body = isBinaryContent(contentType) ? "" : bodyObfuscator.obfuscateBody(bodyStream, charset);
+ this.contentType = contentType;
+ }
+
+ public final void setBinaryContentBody(String contentType) {
+ if (!isBinaryContent(contentType)) {
+ throw new IllegalArgumentException("Not a binary content type: " + contentType);
+ }
+ this.body = "";
+ this.contentType = contentType;
+ }
+
+ private static boolean isBinaryContent(String contentType) {
+ return contentType != null
+ && !contentType.startsWith("text/")
+ && !contentType.contains("json")
+ && !contentType.contains("xml");
+ }
+
+ public final void setBody(Reader bodyStream, String contentType) throws IOException {
+ this.body = bodyObfuscator.obfuscateBody(bodyStream);
+ this.contentType = contentType;
+ }
+
+ public abstract String getMessage();
+
+ protected final String emptyIfNull(String value) {
+ return value != null ? value : "";
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/LoggingCapable.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/LoggingCapable.java
new file mode 100644
index 0000000..ff86835
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/LoggingCapable.java
@@ -0,0 +1,19 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * Classes that implement this interface have support for logging messages from communicators.
+ */
+public interface LoggingCapable {
+
+ /**
+ * Turns on logging using the given communicator logger.
+ *
+ * @throws IllegalArgumentException If the given communicator logger is {@code null}.
+ */
+ void enableLogging(CommunicatorLogger communicatorLogger);
+
+ /**
+ * Turns off logging.
+ */
+ void disableLogging();
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationCapable.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationCapable.java
new file mode 100644
index 0000000..86fbde5
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationCapable.java
@@ -0,0 +1,17 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * Classes that implement this interface support obfuscating bodies and headers.
+ */
+public interface ObfuscationCapable {
+
+ /**
+ * Sets the current non-{@code null} body obfuscator to use.
+ */
+ void setBodyObfuscator(BodyObfuscator bodyObfuscator);
+
+ /**
+ * Sets the current non-{@code null} header obfuscator to use.
+ */
+ void setHeaderObfuscator(HeaderObfuscator headerObfuscator);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationRule.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationRule.java
new file mode 100644
index 0000000..9e0c94d
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/ObfuscationRule.java
@@ -0,0 +1,12 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * An obfuscation rule defines how a single value is obfuscated. This can be as simple as returning a fixed mask,
+ * or as complex as necessary.
+ *
+ * Obfuscation rules should be thread-safe.
+ */
+public interface ObfuscationRule {
+
+ String obfuscateValue(String value);
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/RequestLogMessageBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/RequestLogMessageBuilder.java
new file mode 100644
index 0000000..a2febe0
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/RequestLogMessageBuilder.java
@@ -0,0 +1,43 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * A utility class to build request log messages.
+ */
+public class RequestLogMessageBuilder extends LogMessageBuilder {
+
+ private final String method;
+ private final String uri;
+
+ public RequestLogMessageBuilder(String requestId, String method, String uri,
+ BodyObfuscator bodyObfuscator, HeaderObfuscator headerObfuscator) {
+ super(requestId, bodyObfuscator, headerObfuscator);
+ this.method = method;
+ this.uri = uri;
+ }
+
+ @Override
+ public String getMessage() {
+ final String messageTemplateWithoutBody = "Outgoing request (requestId='%s'):%n"
+ + " method: '%s'%n"
+ + " uri: '%s'%n"
+ + " headers: '%s'";
+
+ final String messageTemplateWithBody = messageTemplateWithoutBody + "%n"
+ + " content-type: '%s'%n"
+ + " body: '%s'";
+
+ String body = body();
+ if (body == null) {
+ return String.format(messageTemplateWithoutBody, requestId(),
+ emptyIfNull(method),
+ emptyIfNull(uri),
+ headers());
+ }
+ return String.format(messageTemplateWithBody, requestId(),
+ emptyIfNull(method),
+ emptyIfNull(uri),
+ headers(),
+ emptyIfNull(contentType()),
+ body);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/ResponseLogMessageBuilder.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/ResponseLogMessageBuilder.java
new file mode 100644
index 0000000..23de11b
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/ResponseLogMessageBuilder.java
@@ -0,0 +1,50 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+/**
+ * A utility class to build request log messages.
+ */
+public class ResponseLogMessageBuilder extends LogMessageBuilder {
+
+ private final int statusCode;
+ private final long duration;
+
+ public ResponseLogMessageBuilder(String requestId, int statusCode,
+ BodyObfuscator bodyObfuscator, HeaderObfuscator headerObfuscator) {
+ this(requestId, statusCode, -1, bodyObfuscator, headerObfuscator);
+ }
+
+ public ResponseLogMessageBuilder(String requestId, int statusCode, long duration,
+ BodyObfuscator bodyObfuscator, HeaderObfuscator headerObfuscator) {
+ super(requestId, bodyObfuscator, headerObfuscator);
+ this.statusCode = statusCode;
+ this.duration = duration;
+ }
+
+ @Override
+ public String getMessage() {
+ if (duration < 0) {
+ final String messageTemplate = "Incoming response (requestId='%s'):%n"
+ + " status-code: '%d'%n"
+ + " headers: '%s'%n"
+ + " content-type: '%s'%n"
+ + " body: '%s'";
+
+ return String.format(messageTemplate, requestId(),
+ statusCode,
+ headers(),
+ emptyIfNull(contentType()),
+ emptyIfNull(body()));
+ }
+ final String messageTemplate = "Incoming response (requestId='%s', %d ms):%n"
+ + " status-code: '%d'%n"
+ + " headers: '%s'%n"
+ + " content-type: '%s'%n"
+ + " body: '%s'";
+
+ return String.format(messageTemplate, requestId(), duration,
+ statusCode,
+ headers(),
+ emptyIfNull(contentType()),
+ emptyIfNull(body()));
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/SysOutCommunicatorLogger.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/SysOutCommunicatorLogger.java
new file mode 100644
index 0000000..042caee
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/SysOutCommunicatorLogger.java
@@ -0,0 +1,41 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.io.PrintStream;
+import java.util.Date;
+
+/**
+ * A communicator logger that prints its message to {@link System#out}.
+ * It includes a timestamp in yyyy-MM-ddTHH:mm:ss format in the system time zone.
+ */
+public final class SysOutCommunicatorLogger implements CommunicatorLogger {
+
+ public static final SysOutCommunicatorLogger INSTANCE = new SysOutCommunicatorLogger();
+
+ private SysOutCommunicatorLogger() {
+ }
+
+ @Override
+ public void log(String message) {
+ // System.out can be changed using System.setOut; make sure the same object is used for locking and printing
+ final PrintStream sysOut = System.out;
+ synchronized (sysOut) {
+ printMessage(sysOut, message);
+ }
+ }
+
+ @Override
+ public void log(String message, Throwable thrown) {
+ // System.out can be changed using System.setOut; make sure the same object is used for locking and printing
+ final PrintStream sysOut = System.out;
+ synchronized (sysOut) {
+ printMessage(sysOut, message);
+ if (thrown != null) {
+ thrown.printStackTrace(sysOut);
+ }
+ }
+ }
+
+ private static void printMessage(PrintStream sysOut, String message) {
+ sysOut.printf("%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS %2$s%n", new Date(), message);
+ }
+}
diff --git a/src/main/java/com/worldline/acquiring/sdk/java/logging/ValueObfuscator.java b/src/main/java/com/worldline/acquiring/sdk/java/logging/ValueObfuscator.java
new file mode 100644
index 0000000..a5042d1
--- /dev/null
+++ b/src/main/java/com/worldline/acquiring/sdk/java/logging/ValueObfuscator.java
@@ -0,0 +1,57 @@
+package com.worldline.acquiring.sdk.java.logging;
+
+import java.util.Arrays;
+
+final class ValueObfuscator implements ObfuscationRule {
+
+ static final ValueObfuscator ALL = new ValueObfuscator(0, 0, 0);
+
+ private final char maskCharacter;
+ private final int fixedLength;
+ private final int keepStartCount;
+ private final int keepEndCount;
+
+ private ValueObfuscator(int fixedLength, int keepStartCount, int keepEndCount) {
+ this.maskCharacter = '*';
+ this.fixedLength = fixedLength;
+ this.keepStartCount = keepStartCount;
+ this.keepEndCount = keepEndCount;
+ }
+
+ static ValueObfuscator fixedLength(int fixedLength) {
+ return new ValueObfuscator(fixedLength, 0, 0);
+ }
+
+ static ValueObfuscator keepStartCount(int count) {
+ return new ValueObfuscator(0, count, 0);
+ }
+
+ static ValueObfuscator keepEndCount(int count) {
+ return new ValueObfuscator(0, 0, count);
+ }
+
+ @Override
+ public String obfuscateValue(String value) {
+ if (value == null || value.isEmpty()) {
+ return value;
+ }
+ if (fixedLength > 0) {
+ return repeatMask(fixedLength);
+ }
+ if (keepStartCount == 0 && keepEndCount == 0) {
+ return repeatMask(value.length());
+ }
+ if (value.length() < keepStartCount + keepEndCount) {
+ return value;
+ }
+ char[] chars = value.toCharArray();
+ Arrays.fill(chars, keepStartCount, value.length() - keepEndCount, maskCharacter);
+ return new String(chars);
+ }
+
+ private String repeatMask(int count) {
+ char[] chars = new char[count];
+ Arrays.fill(chars, maskCharacter);
+ return new String(chars);
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/ClientTest.java b/src/test/java/com/worldline/acquiring/sdk/java/ClientTest.java
new file mode 100644
index 0000000..de5053a
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/ClientTest.java
@@ -0,0 +1,81 @@
+package com.worldline.acquiring.sdk.java;
+
+import static com.worldline.acquiring.sdk.java.FactoryTest.AUTH_ID;
+import static com.worldline.acquiring.sdk.java.FactoryTest.AUTH_SECRET;
+import static com.worldline.acquiring.sdk.java.FactoryTest.PROPERTIES_OAUTH2_URI;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.PooledConnection;
+
+class ClientTest {
+
+ private PooledConnection pooledConnection;
+ private Connection connection;
+
+ @BeforeEach
+ void initMocks() {
+ pooledConnection = mock(PooledConnection.class);
+ connection = mock(Connection.class);
+ }
+
+ @Nested
+ class CloseIdleConnections {
+
+ @Test
+ @SuppressWarnings("resource")
+ void testNotPooled() {
+ CommunicatorBuilder builder = Factory.createCommunicatorBuilder(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+ Communicator communicator = builder.withConnection(connection).build();
+ Client client = Factory.createClient(communicator);
+ // with a connection that isn't a PooledConnection, this doesn't throw any exceptions
+ assertDoesNotThrow(() -> client.closeIdleConnections(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testPooled() {
+ CommunicatorBuilder builder = Factory.createCommunicatorBuilder(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+ Communicator communicator = builder.withConnection(pooledConnection).build();
+ Client client = Factory.createClient(communicator);
+ // with a connection that is a PooledConnection, this gets delegated to pooledConnection
+ client.closeIdleConnections(5, TimeUnit.SECONDS);
+
+ verify(pooledConnection).closeIdleConnections(5, TimeUnit.SECONDS);
+ }
+ }
+
+ @Nested
+ class CloseExpiredConnections {
+
+ @Test
+ @SuppressWarnings("resource")
+ void testNotPooled() {
+ CommunicatorBuilder builder = Factory.createCommunicatorBuilder(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+ Communicator communicator = builder.withConnection(connection).build();
+ Client client = Factory.createClient(communicator);
+ // with a connection that isn't a PooledConnection, this doesn't throw any exceptions
+ assertDoesNotThrow(client::closeExpiredConnections);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testPooled() {
+ CommunicatorBuilder builder = Factory.createCommunicatorBuilder(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+ Communicator communicator = builder.withConnection(pooledConnection).build();
+ Client client = Factory.createClient(communicator);
+ // with a connection that is a PooledConnection, this gets delegated to pooledConnection
+ client.closeExpiredConnections();
+
+ verify(pooledConnection).closeExpiredConnections();
+ }
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorConfigurationTest.java b/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorConfigurationTest.java
new file mode 100644
index 0000000..aee9356
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorConfigurationTest.java
@@ -0,0 +1,276 @@
+package com.worldline.acquiring.sdk.java;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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 java.net.URI;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.authentication.AuthorizationType;
+
+class CommunicatorConfigurationTest {
+
+ @Nested
+ class ConstructFromProperties {
+
+ @Test
+ void testWithoutProxy() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+ assertNull(configuration.getProxyConfiguration());
+ assertEquals(CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS, configuration.getHttpsProtocols());
+ assertNull(configuration.getIntegrator());
+ assertNull(configuration.getShoppingCartExtension());
+ }
+
+ @Test
+ void testWithProxyWithoutAuthentication() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.proxy.uri", "http://proxy.example.org:3128");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+
+ assertNotNull(configuration.getProxyConfiguration());
+ ProxyConfiguration proxyConfiguration = configuration.getProxyConfiguration();
+ assertEquals("http", proxyConfiguration.getScheme());
+ assertEquals("proxy.example.org", proxyConfiguration.getHost());
+ assertEquals(3128, proxyConfiguration.getPort());
+ assertNull(proxyConfiguration.getUsername());
+ assertNull(proxyConfiguration.getPassword());
+ }
+
+ @Test
+ void testWithProxyWithAuthentication() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.proxy.uri", "http://proxy.example.org:3128");
+ properties.setProperty("acquiring.api.proxy.username", "proxy-username");
+ properties.setProperty("acquiring.api.proxy.password", "proxy-password");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+
+ assertNotNull(configuration.getProxyConfiguration());
+ ProxyConfiguration proxyConfiguration = configuration.getProxyConfiguration();
+ assertEquals("http", proxyConfiguration.getScheme());
+ assertEquals("proxy.example.org", proxyConfiguration.getHost());
+ assertEquals(3128, proxyConfiguration.getPort());
+ assertEquals("proxy-username", proxyConfiguration.getUsername());
+ assertEquals("proxy-password", proxyConfiguration.getPassword());
+ }
+
+ @Test
+ void testWithMaxConnections() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.maxConnections", "100");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(100, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+ assertNull(configuration.getProxyConfiguration());
+ }
+
+ @Test
+ void testWithConnectionReuse() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.connectionReuse", "false");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertFalse(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+ assertNull(configuration.getProxyConfiguration());
+ }
+
+ @Test
+ void testWithHost() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ }
+
+ @Test
+ void testWithHostAndScheme() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.endpoint.scheme", "http");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("http://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ }
+
+ @Test
+ void testWithHostAndPort() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.endpoint.port", "8443");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com:8443"), configuration.getApiEndpoint());
+ }
+
+ @Test
+ void testWithHostSchemeAndPort() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.endpoint.scheme", "http");
+ properties.setProperty("acquiring.api.endpoint.port", "8080");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("http://api.preprod.acquiring.worldline-solutions.com:8080"), configuration.getApiEndpoint());
+ }
+
+ @Test
+ void testWithIPv6Host() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "::1");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://[::1]"), configuration.getApiEndpoint());
+ }
+
+ @Test
+ void testWithHttpsProtocols() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.https.protocols", "TLSv1, TLSv1.1, TLSv1.2");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+ assertNull(configuration.getProxyConfiguration());
+ assertEquals(new HashSet<>(Arrays.asList("TLSv1", "TLSv1.1", "TLSv1.2")), configuration.getHttpsProtocols());
+ assertNull(configuration.getIntegrator());
+ assertNull(configuration.getShoppingCartExtension());
+ }
+
+ @Test
+ void testWithMetadata() {
+ Properties properties = new Properties();
+ properties.setProperty("acquiring.api.endpoint.host", "api.preprod.acquiring.worldline-solutions.com");
+ properties.setProperty("acquiring.api.authorizationType", "OAUTH2");
+ properties.setProperty("acquiring.api.connectTimeout", "20000");
+ properties.setProperty("acquiring.api.socketTimeout", "10000");
+ properties.setProperty("acquiring.api.integrator", "Worldline.Integrator");
+ properties.setProperty("acquiring.api.shoppingCartExtension.creator", "Worldline.Creator");
+ properties.setProperty("acquiring.api.shoppingCartExtension.name", "Worldline.ShoppingCarts");
+ properties.setProperty("acquiring.api.shoppingCartExtension.version", "1.0");
+
+ CommunicatorConfiguration configuration = new CommunicatorConfiguration(properties);
+
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(20000, configuration.getConnectTimeout());
+ assertEquals(10000, configuration.getSocketTimeout());
+ assertEquals(CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, configuration.getMaxConnections());
+ assertTrue(configuration.isConnectionReuse());
+ assertNull(configuration.getAuthorizationId());
+ assertNull(configuration.getAuthorizationSecret());
+ assertNull(configuration.getProxyConfiguration());
+ assertEquals("Worldline.Integrator", configuration.getIntegrator());
+ assertNotNull(configuration.getShoppingCartExtension());
+ assertEquals("Worldline.Creator", configuration.getShoppingCartExtension().getCreator());
+ assertEquals("Worldline.ShoppingCarts", configuration.getShoppingCartExtension().getName());
+ assertEquals("1.0", configuration.getShoppingCartExtension().getVersion());
+ }
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorTest.java b/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorTest.java
new file mode 100644
index 0000000..338e373
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/CommunicatorTest.java
@@ -0,0 +1,72 @@
+package com.worldline.acquiring.sdk.java;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.MetadataProvider;
+import com.worldline.acquiring.sdk.java.communication.RequestParam;
+import com.worldline.acquiring.sdk.java.json.Marshaller;
+
+class CommunicatorTest {
+
+ private static final URI BASE_URI = URI.create("https://api.preprod.acquiring.worldline-solutions.com");
+
+ private Connection connection;
+ private Authenticator authenticator;
+ private MetadataProvider metadataProvider;
+ private Marshaller marshaller;
+
+ @BeforeEach
+ void initMocks() {
+ connection = mock(Connection.class);
+ authenticator = mock(Authenticator.class);
+ metadataProvider = mock(MetadataProvider.class);
+ marshaller = mock(Marshaller.class);
+ }
+
+ @Nested
+ class ToAbsoluteURI {
+
+ @Test
+ void testWithoutRequestParams() {
+ @SuppressWarnings("resource")
+ Communicator communicator = new Communicator(BASE_URI, connection, authenticator, metadataProvider, marshaller);
+
+ URI uri = communicator.toAbsoluteURI("services/v1/100812/520000214/dcc-rates", Collections.emptyList());
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com/services/v1/100812/520000214/dcc-rates"), uri);
+
+ uri = communicator.toAbsoluteURI("/services/v1/100812/520000214/dcc-rates", Collections.emptyList());
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com/services/v1/100812/520000214/dcc-rates"), uri);
+ }
+
+ @Test
+ void testWithRequestParams() {
+ List requestParams = Arrays.asList(
+ new RequestParam("amount", "123"),
+ new RequestParam("source", "USD"),
+ new RequestParam("target", "EUR"),
+ new RequestParam("dummy", "é&%=")
+ );
+
+ @SuppressWarnings("resource")
+ Communicator communicator = new Communicator(BASE_URI, connection, authenticator, metadataProvider, marshaller);
+
+ URI uri = communicator.toAbsoluteURI("services/v1/100812/520000214/dcc-rates", requestParams);
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com/services/v1/100812/520000214/dcc-rates?amount=123&source=USD&target=EUR&dummy=%C3%A9%26%25%3D"), uri);
+
+ uri = communicator.toAbsoluteURI("/services/v1/100812/520000214/dcc-rates", requestParams);
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com/services/v1/100812/520000214/dcc-rates?amount=123&source=USD&target=EUR&dummy=%C3%A9%26%25%3D"), uri);
+ }
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/FactoryTest.java b/src/test/java/com/worldline/acquiring/sdk/java/FactoryTest.java
new file mode 100644
index 0000000..535f44e
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/FactoryTest.java
@@ -0,0 +1,79 @@
+package com.worldline.acquiring.sdk.java;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.authentication.AuthorizationType;
+import com.worldline.acquiring.sdk.java.authentication.OAuth2Authenticator;
+import com.worldline.acquiring.sdk.java.communication.Connection;
+import com.worldline.acquiring.sdk.java.communication.DefaultConnection;
+import com.worldline.acquiring.sdk.java.communication.MetadataProvider;
+import com.worldline.acquiring.sdk.java.communication.RequestHeader;
+import com.worldline.acquiring.sdk.java.json.DefaultMarshaller;
+import com.worldline.acquiring.sdk.java.util.DefaultConnectionAssertions;
+import com.worldline.acquiring.sdk.java.util.ReflectionUtil;
+
+class FactoryTest {
+
+ static final URI PROPERTIES_OAUTH2_URI;
+ static final String AUTH_ID = "someId";
+ static final String AUTH_SECRET = "someSecret";
+
+ static {
+ try {
+ PROPERTIES_OAUTH2_URI = FactoryTest.class.getResource("configuration.oauth2.properties").toURI();
+ } catch (URISyntaxException e) {
+ InstantiationError error = new InstantiationError(e.getMessage());
+ error.initCause(e);
+ throw error;
+ }
+ }
+
+ @Test
+ void testCreateConfiguration() {
+ CommunicatorConfiguration configuration = Factory.createConfiguration(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+ assertEquals(URI.create("https://api.preprod.acquiring.worldline-solutions.com"), configuration.getApiEndpoint());
+ assertEquals(AuthorizationType.OAUTH2, configuration.getAuthorizationType());
+ assertEquals(1000, configuration.getConnectTimeout());
+ assertEquals(1000, configuration.getSocketTimeout());
+ assertEquals(100, configuration.getMaxConnections());
+ assertEquals(AUTH_ID, configuration.getOAuth2ClientId());
+ assertEquals(AUTH_SECRET, configuration.getOAuth2ClientSecret());
+ assertNull(configuration.getProxyConfiguration());
+ }
+
+ @Test
+ void testCreateCommunicator() {
+ @SuppressWarnings("resource")
+ Communicator communicator = Factory.createCommunicator(PROPERTIES_OAUTH2_URI, AUTH_ID, AUTH_SECRET);
+
+ assertSame(DefaultMarshaller.INSTANCE, communicator.getMarshaller());
+
+ @SuppressWarnings("resource")
+ Connection connection = ReflectionUtil.getField(communicator, "connection", Connection.class);
+ @SuppressWarnings("resource")
+ DefaultConnection defaultConnection = assertInstanceOf(DefaultConnection.class, connection);
+ DefaultConnectionAssertions.assertConnection(defaultConnection, 1000, 1000, 100, null);
+
+ Authenticator authenticator = ReflectionUtil.getField(communicator, "authenticator", Authenticator.class);
+ assertInstanceOf(OAuth2Authenticator.class, authenticator);
+ assertEquals(AUTH_ID, ReflectionUtil.getField(authenticator, "clientId", String.class));
+ assertEquals(AUTH_SECRET, ReflectionUtil.getField(authenticator, "clientSecret", String.class));
+
+ MetadataProvider metadataProvider = ReflectionUtil.getField(communicator, "metadataProvider", MetadataProvider.class);
+ assertEquals(MetadataProvider.class, metadataProvider.getClass());
+ Collection requestHeaders = metadataProvider.getServerMetadataHeaders();
+ assertEquals(1, requestHeaders.size());
+ RequestHeader requestHeader = requestHeaders.iterator().next();
+ assertEquals("X-WL-ServerMetaInfo", requestHeader.getName());
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AuthenticatorTest.java b/src/test/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AuthenticatorTest.java
new file mode 100644
index 0000000..647091d
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/authentication/OAuth2AuthenticatorTest.java
@@ -0,0 +1,185 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.localserver.LocalServerTestBase;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+
+class OAuth2AuthenticatorTest extends LocalServerTestBase {
+
+ private static final String CLIENT_ID = UUID.randomUUID().toString();
+ private static final String CLIENT_SECRET = UUID.randomUUID().toString();
+ private static final int CONNECTION_TIMEOUT = 1000;
+ private static final int SOCKET_TIMEOUT = 1000;
+ private static final String OAUTH2_TOKEN_URI = "/auth/realms/api/protocol/openid-connect/token";
+
+ private CommunicatorConfiguration communicatorConfiguration;
+
+ private HttpRequestHandler httpRequestHandler;
+
+ @BeforeEach
+ void setup() throws Exception {
+ setUp();
+
+ httpRequestHandler = mock(HttpRequestHandler.class);
+
+ serverBootstrap.registerHandler(OAUTH2_TOKEN_URI, httpRequestHandler);
+ HttpHost host = start();
+
+ communicatorConfiguration = new CommunicatorConfiguration();
+ communicatorConfiguration.setOAuth2ClientId(CLIENT_ID);
+ communicatorConfiguration.setOAuth2ClientSecret(CLIENT_SECRET);
+ communicatorConfiguration.setConnectTimeout(CONNECTION_TIMEOUT);
+ communicatorConfiguration.setSocketTimeout(SOCKET_TIMEOUT);
+ communicatorConfiguration.setOAuth2TokenUri(host.toURI() + OAUTH2_TOKEN_URI);
+ }
+
+ @AfterEach
+ void shutdown() throws Exception {
+ shutDown();
+ }
+
+ @Nested
+ class GetAuthorization {
+
+ @Test
+ void testSuccess() throws Exception {
+ OAuth2Authenticator oauth2Authenticator = new OAuth2Authenticator(communicatorConfiguration);
+ setupRequestHandler(setJsonResponse("oauth2AccessToken.json", 200));
+
+ String authorization = oauth2Authenticator.getAuthorization(null, URI.create("/api/v1/payments"), null);
+
+ assertEquals("Bearer accessToken", authorization);
+ verify(httpRequestHandler).handle(any(), any(), any());
+ }
+
+ @Test
+ void testWithInvalidClient() throws Exception {
+ OAuth2Authenticator oauth2Authenticator = new OAuth2Authenticator(communicatorConfiguration);
+ setupRequestHandler(setJsonResponse("oauth2AccessToken.invalidClient.json", 401));
+
+ URI uri = URI.create("/api/v1/payments");
+ OAuth2Exception exception = assertThrows(OAuth2Exception.class, () -> oauth2Authenticator.getAuthorization(null, uri, null));
+
+ assertEquals("There was an error while retrieving the OAuth2 access token: unauthorized_client - INVALID_CREDENTIALS: Invalid client credentials", exception.getMessage());
+ verify(httpRequestHandler).handle(any(), any(), any());
+ }
+
+ @Nested
+ class MultiThreaded {
+
+ @Test
+ void testWithValidToken() throws Exception {
+ OAuth2Authenticator oauth2Authenticator = new OAuth2Authenticator(communicatorConfiguration);
+ setupRequestHandler(setJsonResponse("oauth2AccessToken.json", 200));
+
+ CountDownLatch startLatch = new CountDownLatch(1);
+ CountDownLatch readyLatch = new CountDownLatch(10);
+ CountDownLatch finishLatch = new CountDownLatch(10);
+
+ List exceptions = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ new Thread(() -> {
+ try {
+ readyLatch.countDown();
+ startLatch.await();
+ oauth2Authenticator.getAuthorization(null, URI.create("/api/v1/payments"), null);
+ } catch (Exception e) {
+ exceptions.add(e);
+ } finally {
+ finishLatch.countDown();
+ }
+ }).start();
+ }
+
+ readyLatch.await();
+ startLatch.countDown();
+ finishLatch.await();
+
+ assertEquals(Collections.emptyList(), exceptions);
+ verify(httpRequestHandler, times(1)).handle(any(), any(), any());
+ }
+
+ @Test
+ void testWithExpiredToken() throws Exception {
+ OAuth2Authenticator oauth2Authenticator = new OAuth2Authenticator(communicatorConfiguration);
+ setupRequestHandler(setJsonResponse("oauth2AccessToken.expired.json", 200));
+
+ CountDownLatch startLatch = new CountDownLatch(1);
+ CountDownLatch readyLatch = new CountDownLatch(10);
+ CountDownLatch finishLatch = new CountDownLatch(10);
+
+ List exceptions = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ new Thread(() -> {
+ try {
+ readyLatch.countDown();
+ startLatch.await();
+ oauth2Authenticator.getAuthorization(null, URI.create("/api/v1/payments"), null);
+ } catch (Exception e) {
+ exceptions.add(e);
+ } finally {
+ finishLatch.countDown();
+ }
+ }).start();
+ }
+
+ readyLatch.await();
+ startLatch.countDown();
+ finishLatch.await();
+
+ assertEquals(Collections.emptyList(), exceptions);
+ verify(httpRequestHandler, times(10)).handle(any(), any(), any());
+ }
+ }
+ }
+
+ private void setupRequestHandler(Answer answer) throws IOException, HttpException {
+ doAnswer(answer).when(httpRequestHandler).handle(any(), any(), any());
+ }
+
+ private Answer setJsonResponse(String resource, int statusCode) {
+ return invocation -> {
+ HttpResponse response = invocation.getArgument(1, HttpResponse.class);
+
+ response.setStatusCode(statusCode);
+ response.setHeader("Content-Type", "application/json");
+
+ response.setEntity(createEntity(resource));
+
+ return null;
+ };
+ }
+
+ @SuppressWarnings("resource")
+ private InputStreamEntity createEntity(String resource) {
+ return new InputStreamEntity(OAuth2AuthenticatorTest.class.getResourceAsStream(resource));
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/authentication/TokenTypeTest.java b/src/test/java/com/worldline/acquiring/sdk/java/authentication/TokenTypeTest.java
new file mode 100644
index 0000000..7139ff2
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/authentication/TokenTypeTest.java
@@ -0,0 +1,124 @@
+package com.worldline.acquiring.sdk.java.authentication;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.mockito.stubbing.Answer;
+
+import com.worldline.acquiring.sdk.java.ApiResource;
+import com.worldline.acquiring.sdk.java.BodyHandler;
+import com.worldline.acquiring.sdk.java.CallContext;
+import com.worldline.acquiring.sdk.java.Client;
+import com.worldline.acquiring.sdk.java.Communicator;
+import com.worldline.acquiring.sdk.java.authentication.OAuth2Authenticator.TokenType;
+
+class TokenTypeTest {
+
+ @ParameterizedTest
+ @ArgumentsSource(PathProvider.class)
+ void testTokenExistsForPath(String path) {
+ assertDoesNotThrow(() -> TokenType.of(path));
+ }
+
+ @Test
+ @Disabled("currently all paths match")
+ void testTokenDoesNotExistForInvalidPath() {
+ String invalidPath = "/this-path-is-invalid";
+ assertThrows(IllegalArgumentException.class, () -> TokenType.of(invalidPath));
+ }
+
+ static final class PathProvider implements ArgumentsProvider {
+
+ private static final Map, Object> PARAMETER_VALUES = new HashMap<>();
+
+ static {
+ PARAMETER_VALUES.put(String.class, "");
+ PARAMETER_VALUES.put(Integer.class, 1000);
+ PARAMETER_VALUES.put(Long.class, 1000L);
+ PARAMETER_VALUES.put(Boolean.class, true);
+ }
+
+ @Override
+ public Stream extends Arguments> provideArguments(ExtensionContext context) throws Exception {
+ return collectPaths()
+ .stream()
+ .map(Arguments::of);
+ }
+
+ @SuppressWarnings("resource")
+ private List collectPaths() {
+ List paths = new ArrayList<>();
+
+ Communicator communicator = mock(Communicator.class);
+
+ doAnswer(addToList(paths)).when(communicator).get(any(), any(), any(), anyResponseType(), any());
+ doAnswer(addToList(paths)).when(communicator).delete(any(), any(), any(), anyResponseType(), any());
+ doAnswer(addToList(paths)).when(communicator).post(any(), any(), any(), any(), anyResponseType(), any());
+ doAnswer(addToList(paths)).when(communicator).put(any(), any(), any(), any(), anyResponseType(), any());
+
+ doAnswer(addToList(paths)).when(communicator).get(any(), any(), any(), anyBodyHandler(), any());
+ doAnswer(addToList(paths)).when(communicator).delete(any(), any(), any(), anyBodyHandler(), any());
+ doAnswer(addToList(paths)).when(communicator).post(any(), any(), any(), any(), anyBodyHandler(), any());
+ doAnswer(addToList(paths)).when(communicator).put(any(), any(), any(), any(), anyBodyHandler(), any());
+
+ Client client = new Client(communicator);
+ invokeAll(client);
+
+ return paths;
+ }
+
+ private Class> anyResponseType() {
+ return any();
+ }
+
+ private BodyHandler anyBodyHandler() {
+ return any();
+ }
+
+ private Answer addToList(List paths) {
+ return invocation -> {
+ paths.add(invocation.getArgument(0));
+ return null;
+ };
+ }
+
+ private void invokeAll(ApiResource apiResource) {
+ Method[] methods = apiResource.getClass().getMethods();
+ for (Method method : methods) {
+ Class>[] parameterTypes = method.getParameterTypes();
+ if (ApiResource.class.isAssignableFrom(method.getReturnType())) {
+ Object[] parameterValues = getParameterValues(parameterTypes);
+ ApiResource childResource = (ApiResource) assertDoesNotThrow(() -> method.invoke(apiResource, parameterValues));
+ invokeAll(childResource);
+ } else if (parameterTypes.length > 0 && parameterTypes[parameterTypes.length - 1] == CallContext.class) {
+ Object[] parameterValues = getParameterValues(parameterTypes);
+ assertDoesNotThrow(() -> method.invoke(apiResource, parameterValues));
+ }
+ }
+ }
+
+ private Object[] getParameterValues(Class>[] parameterTypes) {
+ return Arrays.stream(parameterTypes)
+ .map(PARAMETER_VALUES::get)
+ .toArray();
+ }
+ }
+}
diff --git a/src/test/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionTest.java b/src/test/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionTest.java
new file mode 100644
index 0000000..af988fc
--- /dev/null
+++ b/src/test/java/com/worldline/acquiring/sdk/java/communication/DefaultConnectionTest.java
@@ -0,0 +1,1103 @@
+package com.worldline.acquiring.sdk.java.communication;
+
+import static com.worldline.acquiring.sdk.java.util.DefaultConnectionAssertions.assertMaxConnections;
+import static com.worldline.acquiring.sdk.java.util.DefaultConnectionAssertions.assertNoProxy;
+import static com.worldline.acquiring.sdk.java.util.DefaultConnectionAssertions.assertProxy;
+import static com.worldline.acquiring.sdk.java.util.DefaultConnectionAssertions.assertRequestConfig;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+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.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.config.Registry;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.impl.execchain.ClientExecChain;
+import org.apache.http.impl.execchain.MainClientExec;
+import org.apache.http.localserver.LocalServerTestBase;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import com.worldline.acquiring.sdk.java.BodyHandler;
+import com.worldline.acquiring.sdk.java.Communicator;
+import com.worldline.acquiring.sdk.java.CommunicatorConfiguration;
+import com.worldline.acquiring.sdk.java.Factory;
+import com.worldline.acquiring.sdk.java.ProxyConfiguration;
+import com.worldline.acquiring.sdk.java.authentication.Authenticator;
+import com.worldline.acquiring.sdk.java.domain.UploadableFile;
+import com.worldline.acquiring.sdk.java.logging.CommunicatorLogger;
+import com.worldline.acquiring.sdk.java.util.ReflectionUtil;
+import com.worldline.acquiring.sdk.java.util.ResponseHeaderMatcher;
+
+class DefaultConnectionTest {
+
+ private static final int CONNECT_TIMEOUT = 10000;
+ private static final int SOCKET_TIMEOUT = 20000;
+ private static final int MAX_CONNECTIONS = 100;
+
+ @Nested
+ class Construct {
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithoutBuilder() {
+ DefaultConnection connection = new DefaultConnection(CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithProxyWithoutAuthentication() {
+ ProxyConfiguration proxyConfiguration = new ProxyConfiguration(URI.create("http://test-proxy"));
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withProxyConfiguration(proxyConfiguration)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, proxyConfiguration);
+ assertConnectionReuse(connection, true);
+ assertProxy(connection, proxyConfiguration);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithProxyWithAuthentication() {
+ ProxyConfiguration proxyConfiguration = new ProxyConfiguration(URI.create("http://test-proxy"), "test-username", "test-password");
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withProxyConfiguration(proxyConfiguration)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, proxyConfiguration);
+ assertConnectionReuse(connection, true);
+ assertProxy(connection, proxyConfiguration);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithMaxConnectionsWithoutProxy() {
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithMaxConnectionsWithProxy() {
+ ProxyConfiguration proxyConfiguration = new ProxyConfiguration(URI.create("http://test-proxy"));
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withProxyConfiguration(proxyConfiguration)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, proxyConfiguration);
+ assertConnectionReuse(connection, true);
+ assertProxy(connection, proxyConfiguration);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithHttpsProtocols() {
+ Set httpsProtocols = new HashSet<>(Arrays.asList("TLSv1", "TLSv1.1", "TLSv1.2"));
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withHttpsProtocols(httpsProtocols)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, httpsProtocols);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithZeroHttpsProtocols() {
+ Set httpsProtocols = Collections.emptySet();
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withHttpsProtocols(httpsProtocols)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithNullHttpsProtocols() {
+ Set httpsProtocols = null;
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withHttpsProtocols(httpsProtocols)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithMinimalBuilder() {
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, CommunicatorConfiguration.DEFAULT_MAX_CONNECTIONS, null);
+ assertConnectionReuse(connection, true);
+ assertNoProxy(connection);
+ assertHttpsProtocols(connection, CommunicatorConfiguration.DEFAULT_HTTPS_PROTOCOLS);
+ }
+
+ @Test
+ @SuppressWarnings("resource")
+ void testConstructWithFullBuilder() {
+ ProxyConfiguration proxyConfiguration = new ProxyConfiguration(URI.create("http://test-proxy"));
+ Set httpsProtocols = new HashSet<>(Arrays.asList("TLSv1", "TLSv1.1", "TLSv1.2"));
+
+ DefaultConnection connection = new DefaultConnectionBuilder(CONNECT_TIMEOUT, SOCKET_TIMEOUT)
+ .withMaxConnections(MAX_CONNECTIONS)
+ .withConnectionReuse(false)
+ .withProxyConfiguration(proxyConfiguration)
+ .withHttpsProtocols(httpsProtocols)
+ .build();
+ assertRequestConfig(connection, CONNECT_TIMEOUT, SOCKET_TIMEOUT);
+ assertMaxConnections(connection, MAX_CONNECTIONS, proxyConfiguration);
+ assertConnectionReuse(connection, false);
+ assertProxy(connection, proxyConfiguration);
+ assertHttpsProtocols(connection, httpsProtocols);
+ }
+
+ @SuppressWarnings("resource")
+ private void assertConnectionReuse(DefaultConnection connection, boolean connectionReuse) {
+ CloseableHttpClient httpClient = ReflectionUtil.getField(connection, "httpClient", CloseableHttpClient.class);
+ ClientExecChain execChain = ReflectionUtil.getField(httpClient, "execChain", ClientExecChain.class);
+ while (execChain != null && !(execChain instanceof MainClientExec)) {
+ execChain = ReflectionUtil.getField(execChain, "requestExecutor", ClientExecChain.class);
+ }
+ assertNotNull(execChain);
+ ConnectionReuseStrategy reuseStrategy = ReflectionUtil.getField(execChain, "reuseStrategy", ConnectionReuseStrategy.class);
+
+ if (connectionReuse) {
+ assertNotNull(reuseStrategy);
+ assertNotSame(NoConnectionReuseStrategy.INSTANCE, reuseStrategy);
+ } else {
+ assertSame(NoConnectionReuseStrategy.INSTANCE, reuseStrategy);
+ }
+ }
+
+ @SuppressWarnings("resource")
+ private void assertHttpsProtocols(DefaultConnection connection, Set httpsProtocols) {
+ CloseableHttpClient httpClient = ReflectionUtil.getField(connection, "httpClient", CloseableHttpClient.class);
+ PoolingHttpClientConnectionManager connectionManager = ReflectionUtil.getField(httpClient, "connManager", PoolingHttpClientConnectionManager.class);
+ DefaultHttpClientConnectionOperator connectionOperator = ReflectionUtil.getField(connectionManager, "connectionOperator", DefaultHttpClientConnectionOperator.class);
+ Registry> socketFactoryRegistry = ReflectionUtil.getField(connectionOperator, "socketFactoryRegistry", Registry.class);
+ SSLConnectionSocketFactory sslConnectionSocketFactory = (SSLConnectionSocketFactory) socketFactoryRegistry.lookup("https");
+ String[] supportedProtocols = ReflectionUtil.getField(sslConnectionSocketFactory, "supportedProtocols", String[].class);
+ assertNotNull(supportedProtocols);
+ assertEquals(httpsProtocols, new HashSet<>(Arrays.asList(supportedProtocols)));
+ }
+ }
+
+ @Nested
+ class Logging extends LocalServerTestBase {
+
+ private HttpRequestHandler requestHandler;
+
+ @BeforeEach
+ void setup() throws Exception {
+ setUp();
+
+ requestHandler = mock(HttpRequestHandler.class);
+ }
+
+ @AfterEach
+ void shutdown() throws Exception {
+ shutDown();
+ }
+
+ @Test
+ void testGetWithoutQueryParams() throws Exception {
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setOKJsonResponse("getWithoutQueryParams.json"));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ Map, ?> response = communicator.get("v1/get", null, null, Map.class, null);
+
+ assertNotNull(response);
+ assertEquals("OK", response.get("result"));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "getWithoutQueryParams");
+ }
+
+ @Test
+ void testGetWithQueryParams() throws Exception {
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setOKJsonResponse("getWithQueryParams.json"));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ ParamRequest params = () -> Arrays.asList(
+ new RequestParam("source", "EUR"),
+ new RequestParam("target", "USD"),
+ new RequestParam("amount", "1000"));
+
+ Map, ?> response = communicator.get("v1/get", null, params, Map.class, null);
+
+ assertNotNull(response);
+ assertEquals(4547504D, response.get("convertedAmount"));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "getWithQueryParams");
+ }
+
+ @Test
+ void testDeleteWithVoidResponse() throws Exception {
+ serverBootstrap.registerHandler("/v1/void", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setVoidResponse());
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ communicator.delete("/v1/void", null, null, Void.class, null);
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "deleteWithVoidResponse");
+ }
+
+ @Test
+ void testPostWithCreatedResponse() throws Exception {
+ serverBootstrap.registerHandler("/v1/created", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setCreatedJsonResponse("postWithCreatedResponse.json", "http://localhost/v1/created/000000123410000595980000100001"));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ Map card = new LinkedHashMap<>();
+ card.put("cardSecurityCode", "123");
+ card.put("cardNumber", "1234567890123456");
+ card.put("expiryDate", "122024");
+
+ Map request = new LinkedHashMap<>();
+ request.put("card", card);
+
+ Map, ?> response = communicator.post("/v1/created", null, null, request, Map.class, null);
+
+ assertNotNull(response);
+ Map, ?> payment = assertInstanceOf(Map.class, response.get("payment"));
+ assertEquals("000000123410000595980000100001", payment.get("id"));
+ assertEquals("PENDING_APPROVAL", payment.get("status"));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "postWithCreatedResponse");
+ }
+
+ @Test
+ void testPostWithBadRequestResponse() throws Exception {
+ // an exception is thrown after logging the response
+
+ serverBootstrap.registerHandler("/v1/bad-request", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setJsonResponse("postWithBadRequestResponse.json", 400));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ Map card = new LinkedHashMap<>();
+ card.put("cardSecurityCode", "123");
+ card.put("cardNumber", "1234567890123456");
+ card.put("expiryDate", "122024");
+
+ Map request = new LinkedHashMap<>();
+ request.put("card", card);
+
+ assertThrows(ResponseException.class, () -> communicator.post("/v1/bad-request", null, null, request, Map.class, null));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "postWithBadRequestResponse");
+ }
+
+ @Test
+ void testBinaryRequestWithKnownLength() throws Exception {
+ serverBootstrap.registerHandler("/binaryRequest", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ byte[] data = new byte[1024];
+ new Random().nextBytes(data);
+
+ UploadableFile file = new UploadableFile("dummyFile", new ByteArrayInputStream(data), "application/octetstream", data.length);
+
+ MultipartFormDataObject multipart = new MultipartFormDataObject();
+ multipart.addFile("file", file);
+
+ setupRequestHandler(setVoidResponse());
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ communicator.post("/binaryRequest", new ArrayList<>(), null, multipart, Void.class, null);
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "binaryRequest");
+
+ assertThat(requestEntry.message, containsString("Content-Length="));
+ assertThat(requestEntry.message, not(containsString("Transfer-Encoding=\"chunked\"")));
+ }
+
+ @Test
+ void testBinaryRequestWithUnknownLength() throws Exception {
+ serverBootstrap.registerHandler("/binaryRequest", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ byte[] data = new byte[1024];
+ new Random().nextBytes(data);
+
+ UploadableFile file = new UploadableFile("dummyFile", new ByteArrayInputStream(data), "application/octetstream");
+
+ MultipartFormDataObject multipart = new MultipartFormDataObject();
+ multipart.addFile("file", file);
+
+ setupRequestHandler(setVoidResponse());
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ communicator.post("/binaryRequest", new ArrayList<>(), null, multipart, Void.class, null);
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "binaryRequest");
+
+ assertThat(requestEntry.message, not(containsString("Content-Length=")));
+ assertThat(requestEntry.message, containsString("Transfer-Encoding=\"chunked\""));
+ }
+
+ @Test
+ void testBinaryResponse() throws Exception {
+ serverBootstrap.registerHandler("/binaryContent", requestHandler);
+ HttpHost host = start();
+
+ URI uri = toURI(host, "/binaryContent");
+
+ TestLogger logger = new TestLogger();
+
+ byte[] data = new byte[1024];
+ new Random().nextBytes(data);
+
+ setupRequestHandler(setBinaryResponse(data, 200));
+
+ // also test that the response handler is called correctly
+ ResponseHandler> responseHandler = (statusCode, bodyStream, headers) -> {
+ ByteArrayOutputStream output = new ByteArrayOutputStream(data.length);
+ try {
+ byte[] buffer = new byte[data.length];
+ int len;
+ while ((len = bodyStream.read(buffer)) != -1) {
+ output.write(buffer, 0, len);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ assertEquals(200, statusCode);
+ assertArrayEquals(data, output.toByteArray());
+ assertThat(headers, hasItems(
+ new ResponseHeaderMatcher("Dummy", ""),
+ new ResponseHeaderMatcher("Content-Type", "application/octet-stream"),
+ new ResponseHeaderMatcher("Content-Length", "1024")
+ ));
+
+ return null;
+ };
+
+ try (Connection connection = createConnection()) {
+ connection.enableLogging(logger);
+
+ connection.get(uri, new ArrayList<>(), responseHandler);
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "binaryResponse");
+ }
+
+ @Test
+ void testVoidContent() throws Exception {
+ serverBootstrap.registerHandler("/v1/void", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setVoidResponse());
+
+ // also test that the body handler is called correctly
+ BodyHandler bodyHandler = (bodyStream, headers) -> assertEquals(-1, bodyStream.read());
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ communicator.delete("/v1/void", null, null, bodyHandler, null);
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "deleteWithVoidResponse");
+ }
+
+ @Test
+ void testUnknownServerError() throws Exception {
+ // reuse the request from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setJsonResponse("unknownServerError.json", 500));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ assertThrows(ResponseException.class, () -> communicator.get("v1/get", null, null, Map.class, null));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "getWithoutQueryParams", "unknownServerError");
+ }
+
+ @Test
+ void testNonJson() throws Exception {
+ // reuse the request from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(setHtmlResponse("notFound.html", 404));
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ assertThrows(NotFoundException.class, () -> communicator.get("v1/get", null, null, Map.class, null));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry responseEntry = logger.entries.get(1);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertRequestAndResponse(requestEntry.message, responseEntry.message, "getWithoutQueryParams", "notFound");
+ }
+
+ @Test
+ void testReadTimeout() throws Exception {
+ // an exception is thrown before logging the response
+
+ // reuse the request from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ setupRequestHandler(delayedAnswer(setHtmlResponse("notFound.html", 404), 100));
+
+ try (Communicator communicator = createCommunicator(host, 1000, 10)) {
+ communicator.enableLogging(logger);
+
+ assertThrows(CommunicationException.class, () -> communicator.get("v1/get", null, null, Map.class, null));
+ }
+
+ assertEquals(2, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ TestLoggerEntry errorEntry = logger.entries.get(1);
+
+ assertNotNull(errorEntry.message);
+ assertNotNull(errorEntry.thrown);
+
+ assertRequestAndError(requestEntry.message, errorEntry.message, "getWithoutQueryParams");
+
+ assertEquals(SocketTimeoutException.class, errorEntry.thrown.getClass());
+ }
+
+ @Test
+ void testLogRequestOnly() throws Exception {
+ // logging is disabled after the request is logged but before the response is logged
+
+ // reuse the request and response from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ try (Communicator communicator = createCommunicator(host)) {
+ communicator.enableLogging(logger);
+
+ setupRequestHandler(disableLogging(setOKJsonResponse("getWithoutQueryParams.json"), communicator));
+
+ Map, ?> response = communicator.get("v1/get", null, null, Map.class, null);
+
+ assertNotNull(response);
+ assertEquals("OK", response.get("result"));
+ }
+
+ assertEquals(1, logger.entries.size());
+
+ TestLoggerEntry requestEntry = logger.entries.get(0);
+
+ assertNotNull(requestEntry.message);
+ assertNull(requestEntry.thrown);
+
+ assertRequest(requestEntry.message, "getWithoutQueryParams");
+ }
+
+ @Test
+ void testLogResponseOnly() throws Exception {
+ // logging is enabled after the request is logged but before the response is logged
+
+ // reuse the request and response from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ try (Communicator communicator = createCommunicator(host)) {
+ setupRequestHandler(enableLogging(setOKJsonResponse("getWithoutQueryParams.json"), communicator, logger));
+
+ Map, ?> response = communicator.get("v1/get", null, null, Map.class, null);
+
+ assertNotNull(response);
+ assertEquals("OK", response.get("result"));
+ }
+
+ assertEquals(1, logger.entries.size());
+
+ TestLoggerEntry responseEntry = logger.entries.get(0);
+
+ assertNotNull(responseEntry.message);
+ assertNull(responseEntry.thrown);
+
+ assertResponse(responseEntry.message, "getWithoutQueryParams");
+ }
+
+ @Test
+ void testLogErrorOnly() throws Exception {
+ // logging is enabled after the request is logged but before the error is logged
+
+ // reuse the request from getWithoutQueryParams
+ serverBootstrap.registerHandler("/v1/get", requestHandler);
+ HttpHost host = start();
+
+ TestLogger logger = new TestLogger();
+
+ try (Communicator communicator = createCommunicator(host, 1000, 100)) {
+ setupRequestHandler(enableLogging(delayedAnswer(setHtmlResponse("notFound.html", 404), 200), communicator, logger));
+
+ assertThrows(CommunicationException.class, () -> communicator.get("v1/get", null, null, Map.class, null));
+ }
+
+ assertEquals(1, logger.entries.size());
+
+ TestLoggerEntry errorEntry = logger.entries.get(0);
+
+ assertNotNull(errorEntry.message);
+ assertNotNull(errorEntry.thrown);
+
+ assertError(errorEntry.message);
+
+ assertEquals(SocketTimeoutException.class, errorEntry.thrown.getClass());
+ }
+
+ // assertion utility methods
+
+ private String assertRequestAndResponse(String requestMessage, String responseMessage, String resourcePrefix) throws IOException {
+ return assertRequestAndResponse(requestMessage, responseMessage, resourcePrefix, resourcePrefix);
+ }
+
+ private String assertRequestAndResponse(String requestMessage, String responseMessage, String requestResourcePrefix, String responseResourcePrefix) throws IOException {
+ String requestId = assertRequest(requestMessage, requestResourcePrefix);
+ requestId = assertResponse(responseMessage, responseResourcePrefix, requestId);
+ return requestId;
+ }
+
+ private String assertRequestAndError(String requestMessage, String errorMessage, String resourcePrefix) throws IOException {
+ String requestId = assertRequest(requestMessage, resourcePrefix);
+ requestId = assertError(errorMessage, requestId);
+ return requestId;
+ }
+
+ private String assertRequest(String requestMessage, String resourcePrefix) throws IOException {
+ String requestResource = resourcePrefix + ".request";
+
+ Pattern requestPattern = Pattern.compile(normalizeLineBreaks(readResource(requestResource)), Pattern.DOTALL);
+
+ Matcher requestMatcher = requestPattern.matcher(normalizeLineBreaks(requestMessage));
+ assertTrue(requestMatcher.matches(), "request message '" + requestMessage + "' does not match pattern " + requestPattern);
+
+ String requestId = requestMatcher.group(1);
+
+ return requestId;
+ }
+
+ private String assertResponse(String responseMessage, String resourcePrefix) throws IOException {
+ return assertResponse(responseMessage, resourcePrefix, null);
+ }
+
+ private String assertResponse(String responseMessage, String resourcePrefix, String requestId) throws IOException {
+ String responseResource = resourcePrefix + ".response";
+ Pattern responsePattern = Pattern.compile(normalizeLineBreaks(readResource(responseResource)), Pattern.DOTALL);
+
+ Matcher responseMatcher = responsePattern.matcher(normalizeLineBreaks(responseMessage));
+ assertTrue(responseMatcher.matches(), "response message '" + responseMessage + "' does not match pattern " + responsePattern);
+
+ String responseRequestId = responseMatcher.group(1);
+ if (requestId != null) {
+ assertEquals(requestId, responseRequestId,
+ "response requestId '" + responseRequestId + "' does not match request requestId '" + requestId + "'");
+ } else {
+ requestId = responseRequestId;
+ }
+
+ return requestId;
+ }
+
+ private String assertError(String errorMessage) throws IOException {
+ return assertError(errorMessage, null);
+ }
+
+ private String assertError(String errorMessage, String requestId) throws IOException {
+ String errorResource = "generic.error";
+ Pattern errorPattern = Pattern.compile(normalizeLineBreaks(readResource(errorResource)), Pattern.DOTALL);
+
+ Matcher errorMatcher = errorPattern.matcher(normalizeLineBreaks(errorMessage));
+ assertTrue(errorMatcher.matches(), "error message '" + errorMessage + "' does not match pattern " + errorPattern);
+
+ String errorRequestId = errorMatcher.group(1);
+ if (requestId != null) {
+ assertEquals(requestId, errorRequestId,
+ "error requestId '" + errorRequestId + "' does not match earlier requestId '" + requestId + "'");
+ } else {
+ requestId = errorRequestId;
+ }
+
+ return requestId;
+ }
+
+ // Mockito answer utility methods
+
+ private void setupRequestHandler(Answer answer) throws IOException, HttpException {
+ doAnswer(answer).when(requestHandler).handle(any(), any(), any());
+ }
+
+ private Answer setVoidResponse() {
+ return setVoidResponse(Collections.emptyMap());
+ }
+
+ private Answer setVoidResponse(Map additionalHeaders) {
+ return i -> {
+ HttpResponse response = i.getArgument(1, HttpResponse.class);
+
+ response.setStatusCode(204);
+
+ response.setHeader("Dummy", null);
+
+ for (Map.Entry entry : additionalHeaders.entrySet()) {
+ response.setHeader(entry.getKey(), entry.getValue());
+ }
+
+ return null;
+ };
+ }
+
+ private Answer setOKJsonResponse(String resource) {
+ return setJsonResponse(resource, 200, Collections.emptyMap());
+ }
+
+ private Answer setCreatedJsonResponse(String resource, String location) {
+ return setJsonResponse(resource, 201, Collections.singletonMap("Location", location));
+ }
+
+ private Answer setJsonResponse(String resource, int statusCode) {
+ return setJsonResponse(resource, statusCode, Collections.emptyMap());
+ }
+
+ private Answer setJsonResponse(String resource, int statusCode, Map additionalHeaders) {
+ return i -> {
+ HttpResponse response = i.getArgument(1, HttpResponse.class);
+
+ response.setStatusCode(statusCode);
+ response.setHeader("Content-Type", "application/json");
+
+ response.setHeader("Dummy", null);
+
+ for (Map.Entry entry : additionalHeaders.entrySet()) {
+ response.setHeader(entry.getKey(), entry.getValue());
+ }
+
+ response.setEntity(createEntity(resource));
+
+ return null;
+ };
+ }
+
+ private Answer