diff --git a/src/main/java/com/adyen/constants/ApiConstants.java b/src/main/java/com/adyen/constants/ApiConstants.java index 63558d7fa..6db112d3b 100644 --- a/src/main/java/com/adyen/constants/ApiConstants.java +++ b/src/main/java/com/adyen/constants/ApiConstants.java @@ -97,4 +97,15 @@ interface Data { interface PaymentMethodType { String TYPE_SCHEME = "scheme"; } + + interface RequestProperty + { + String IDEMPOTENCY_KEY = "Idempotency-Key"; + String ACCEPT_CHARSET = "Accept-Charset"; + String USER_AGENT = "User-Agent"; + String METHOD_POST ="POST"; + String CONTENT_TYPE ="Content-Type"; + String API_KEY = "x-api-key"; + String APPLICATION_JSON_TYPE ="application/json"; + } } diff --git a/src/main/java/com/adyen/httpclient/ClientInterface.java b/src/main/java/com/adyen/httpclient/ClientInterface.java index bcb7cbca2..46c75b4ea 100644 --- a/src/main/java/com/adyen/httpclient/ClientInterface.java +++ b/src/main/java/com/adyen/httpclient/ClientInterface.java @@ -21,6 +21,7 @@ package com.adyen.httpclient; import com.adyen.Config; +import com.adyen.model.RequestOptions; import java.io.IOException; import java.util.Map; @@ -29,5 +30,7 @@ public interface ClientInterface { String request(String endpoint, String json, Config config) throws IOException, HTTPClientException; String request(String endpoint, String json, Config config, boolean isApiKeyRequired) throws IOException, HTTPClientException; + String request(String endpoint, String json, Config config, boolean isApiKeyRequired, RequestOptions requestOptions) throws IOException, HTTPClientException; + String post(String endpoint, Map postParameters, Config config) throws IOException, HTTPClientException; } diff --git a/src/main/java/com/adyen/httpclient/HttpURLConnectionClient.java b/src/main/java/com/adyen/httpclient/HttpURLConnectionClient.java index 772ae2b29..652e85777 100644 --- a/src/main/java/com/adyen/httpclient/HttpURLConnectionClient.java +++ b/src/main/java/com/adyen/httpclient/HttpURLConnectionClient.java @@ -22,6 +22,7 @@ import com.adyen.Client; import com.adyen.Config; +import com.adyen.model.RequestOptions; import org.apache.commons.codec.binary.Base64; import java.io.IOException; @@ -35,6 +36,8 @@ import java.util.Map; import java.util.Scanner; +import static com.adyen.constants.ApiConstants.RequestProperty.*; + public class HttpURLConnectionClient implements ClientInterface { private static final String CHARSET = "UTF-8"; private Proxy proxy; @@ -53,8 +56,13 @@ public String request(String requestUrl, String requestBody, Config config) thro } @Override - public String request(String requestUrl, String requestBody, Config config, boolean isApiKeyRequired) throws IOException, HTTPClientException { - HttpURLConnection httpConnection = createRequest(requestUrl, config.getApplicationName()); + public String request(String endpoint, String json, Config config, boolean isApiKeyRequired) throws IOException, HTTPClientException { + return request(endpoint, json, config, isApiKeyRequired, null); + } + + @Override + public String request(String requestUrl, String requestBody, Config config, boolean isApiKeyRequired, RequestOptions requestOptions) throws IOException, HTTPClientException { + HttpURLConnection httpConnection = createRequest(requestUrl, config.getApplicationName(), requestOptions); String apiKey = config.getApiKey(); int connectionTimeoutMillis = config.getConnectionTimeoutMillis(); // Use Api key if required or if provided @@ -65,7 +73,7 @@ public String request(String requestUrl, String requestBody, Config config, bool } httpConnection.setConnectTimeout(connectionTimeoutMillis); - setContentType(httpConnection, "application/json"); + setContentType(httpConnection, APPLICATION_JSON_TYPE); return doPostRequest(httpConnection, requestBody); } @@ -117,6 +125,13 @@ private String getQuery(Map params) throws UnsupportedEncodingEx * Initialize the httpConnection */ private HttpURLConnection createRequest(String requestUrl, String applicationName) throws IOException { + return createRequest(requestUrl, applicationName, null); + } + + /** + * Initialize the httpConnection + */ + private HttpURLConnection createRequest(String requestUrl, String applicationName, RequestOptions requestOptions) throws IOException { URL targetUrl = new URL(requestUrl); HttpURLConnection httpConnection; @@ -128,11 +143,13 @@ private HttpURLConnection createRequest(String requestUrl, String applicationNam } httpConnection.setUseCaches(false); httpConnection.setDoOutput(true); - httpConnection.setRequestMethod("POST"); - - httpConnection.setRequestProperty("Accept-Charset", CHARSET); - httpConnection.setRequestProperty("User-Agent", String.format("%s %s%s", applicationName, Client.USER_AGENT_SUFFIX, Client.LIB_VERSION)); + httpConnection.setRequestMethod(METHOD_POST); + httpConnection.setRequestProperty(ACCEPT_CHARSET, CHARSET); + httpConnection.setRequestProperty(USER_AGENT, String.format("%s %s%s", applicationName, Client.USER_AGENT_SUFFIX, Client.LIB_VERSION)); + if (requestOptions != null && requestOptions.getIdempotencyKey() != null) { + httpConnection.setRequestProperty(IDEMPOTENCY_KEY, requestOptions.getIdempotencyKey()); + } return httpConnection; } @@ -153,7 +170,7 @@ private HttpURLConnection setBasicAuthentication(HttpURLConnection httpConnectio * Sets content type */ private HttpURLConnection setContentType(HttpURLConnection httpConnection, String contentType) { - httpConnection.setRequestProperty("Content-Type", contentType); + httpConnection.setRequestProperty(CONTENT_TYPE, contentType); return httpConnection; } @@ -162,7 +179,7 @@ private HttpURLConnection setContentType(HttpURLConnection httpConnection, Strin */ private HttpURLConnection setApiKey(HttpURLConnection httpConnection, String apiKey) { if (apiKey != null && !apiKey.isEmpty()) { - httpConnection.setRequestProperty("x-api-key", apiKey); + httpConnection.setRequestProperty(API_KEY, apiKey); } return httpConnection; } diff --git a/src/main/java/com/adyen/model/RequestOptions.java b/src/main/java/com/adyen/model/RequestOptions.java new file mode 100644 index 000000000..73fc4fbd8 --- /dev/null +++ b/src/main/java/com/adyen/model/RequestOptions.java @@ -0,0 +1,16 @@ +package com.adyen.model; + +public class RequestOptions { + + private String idempotencyKey; + + public String getIdempotencyKey() { + return idempotencyKey; + } + + public void setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = idempotencyKey; + } + + +} diff --git a/src/main/java/com/adyen/service/Checkout.java b/src/main/java/com/adyen/service/Checkout.java index 20ede3c9c..df9ff11d0 100644 --- a/src/main/java/com/adyen/service/Checkout.java +++ b/src/main/java/com/adyen/service/Checkout.java @@ -24,6 +24,7 @@ import java.io.IOException; import com.adyen.ApiKeyAuthenticatedService; import com.adyen.Client; +import com.adyen.model.RequestOptions; import com.adyen.model.checkout.*; import com.adyen.service.exception.ApiException; import com.adyen.service.resource.checkout.*; @@ -57,11 +58,14 @@ public Checkout(Client client) { * @throws ApiException */ public PaymentsResponse payments(PaymentsRequest paymentsRequest) throws ApiException, IOException { + return payments(paymentsRequest,null); + } + + public PaymentsResponse payments(PaymentsRequest paymentsRequest, RequestOptions requestOptions) throws ApiException, IOException { String jsonRequest = GSON.toJson(paymentsRequest); - String jsonResult = payments.request(jsonRequest); + String jsonResult = payments.request(jsonRequest, requestOptions); return GSON.fromJson(jsonResult, new TypeToken() { }.getType()); - } /** @@ -107,8 +111,12 @@ public PaymentsResponse paymentsDetails(PaymentsDetailsRequest paymentsDetailsRe */ public PaymentSessionResponse paymentSession(PaymentSessionRequest paymentSessionRequest) throws ApiException, IOException { + return paymentSession(paymentSessionRequest, null); + } + + public PaymentSessionResponse paymentSession(PaymentSessionRequest paymentSessionRequest, RequestOptions requestOptions) throws ApiException, IOException { String jsonRequest = GSON.toJson(paymentSessionRequest); - String jsonResult = paymentSession.request(jsonRequest); + String jsonResult = paymentSession.request(jsonRequest, requestOptions); return GSON.fromJson(jsonResult, new TypeToken() { }.getType()); } diff --git a/src/main/java/com/adyen/service/Modification.java b/src/main/java/com/adyen/service/Modification.java index c2086c954..a315e36b0 100644 --- a/src/main/java/com/adyen/service/Modification.java +++ b/src/main/java/com/adyen/service/Modification.java @@ -23,6 +23,7 @@ import java.io.IOException; import com.adyen.Client; import com.adyen.Service; +import com.adyen.model.RequestOptions; import com.adyen.model.modification.AbstractModificationRequest; import com.adyen.model.modification.CancelOrRefundRequest; import com.adyen.model.modification.CancelRequest; @@ -60,10 +61,12 @@ public Modification(Client client) { * @throws ApiException */ public ModificationResult capture(CaptureRequest captureRequest) throws IOException, ApiException { - String jsonRequest = serializeRequest(captureRequest); - - String jsonResult = capture.request(jsonRequest); + return capture(captureRequest,null); + } + public ModificationResult capture(CaptureRequest captureRequest, RequestOptions requestOptions) throws IOException, ApiException { + String jsonRequest = serializeRequest(captureRequest); + String jsonResult = capture.request(jsonRequest, requestOptions); return deserializeResponse(jsonResult); } @@ -76,10 +79,12 @@ public ModificationResult capture(CaptureRequest captureRequest) throws IOExcept * @throws ApiException */ public ModificationResult cancelOrRefund(CancelOrRefundRequest cancelOrRefundRequest) throws IOException, ApiException { - String jsonRequest = serializeRequest(cancelOrRefundRequest); - - String jsonResult = cancelOrRefund.request(jsonRequest); + return cancelOrRefund(cancelOrRefundRequest, null); + } + public ModificationResult cancelOrRefund(CancelOrRefundRequest cancelOrRefundRequest, RequestOptions requestOptions) throws IOException, ApiException { + String jsonRequest = serializeRequest(cancelOrRefundRequest); + String jsonResult = cancelOrRefund.request(jsonRequest, requestOptions); return deserializeResponse(jsonResult); } @@ -92,10 +97,12 @@ public ModificationResult cancelOrRefund(CancelOrRefundRequest cancelOrRefundReq * @throws ApiException */ public ModificationResult refund(RefundRequest refundRequest) throws IOException, ApiException { - String jsonRequest = serializeRequest(refundRequest); - - String jsonResult = refund.request(jsonRequest); + return refund(refundRequest, null); + } + public ModificationResult refund(RefundRequest refundRequest, RequestOptions requestOptions) throws IOException, ApiException { + String jsonRequest = serializeRequest(refundRequest); + String jsonResult = refund.request(jsonRequest,requestOptions); return deserializeResponse(jsonResult); } @@ -108,10 +115,13 @@ public ModificationResult refund(RefundRequest refundRequest) throws IOException * @throws ApiException */ public ModificationResult cancel(CancelRequest cancelRequest) throws IOException, ApiException { - String jsonRequest = serializeRequest(cancelRequest); + return cancel(cancelRequest, null); + } - String jsonResult = cancel.request(jsonRequest); + public ModificationResult cancel(CancelRequest cancelRequest, RequestOptions requestOptions) throws IOException, ApiException { + String jsonRequest = serializeRequest(cancelRequest); + String jsonResult = cancel.request(jsonRequest, requestOptions); return deserializeResponse(jsonResult); } diff --git a/src/main/java/com/adyen/service/Payment.java b/src/main/java/com/adyen/service/Payment.java index 88bf66dfb..97675538e 100644 --- a/src/main/java/com/adyen/service/Payment.java +++ b/src/main/java/com/adyen/service/Payment.java @@ -1,4 +1,4 @@ -/** +/* * ###### * ###### * ############ ####( ###### #####. ###### ############ ############ @@ -25,6 +25,7 @@ import com.adyen.model.PaymentRequest; import com.adyen.model.PaymentRequest3d; import com.adyen.model.PaymentResult; +import com.adyen.model.RequestOptions; import com.adyen.service.exception.ApiException; import com.adyen.service.resource.payment.Authorise; import com.adyen.service.resource.payment.Authorise3D; @@ -50,10 +51,12 @@ public Payment(Client client) { * @param paymentRequest */ public PaymentResult authorise(PaymentRequest paymentRequest) throws ApiException, IOException { - String jsonRequest = GSON.toJson(paymentRequest); - - String jsonResult = authorise.request(jsonRequest); + return authorise(paymentRequest, null); + } + public PaymentResult authorise(PaymentRequest paymentRequest, RequestOptions requestOptions) throws ApiException, IOException { + String jsonRequest = GSON.toJson(paymentRequest); + String jsonResult = authorise.request(jsonRequest, requestOptions); PaymentResult paymentResult = GSON.fromJson(jsonResult, new TypeToken() { }.getType()); diff --git a/src/main/java/com/adyen/service/Resource.java b/src/main/java/com/adyen/service/Resource.java index cc7208d8b..d0bfd4202 100644 --- a/src/main/java/com/adyen/service/Resource.java +++ b/src/main/java/com/adyen/service/Resource.java @@ -20,18 +20,20 @@ */ package com.adyen.service; -import java.io.IOException; -import java.util.List; import com.adyen.Config; import com.adyen.Service; import com.adyen.httpclient.ClientInterface; import com.adyen.httpclient.HTTPClientException; import com.adyen.model.ApiError; +import com.adyen.model.RequestOptions; import com.adyen.service.exception.ApiException; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.util.List; + public class Resource { protected static final Gson GSON = new Gson(); @@ -49,13 +51,20 @@ public Resource(Service service, String endpoint, List requiredFields) { * Request using json String */ public String request(String json) throws ApiException, IOException { + return request(json, null); + } + + /** + * Request using json String with additional request parameters like idempotency-key + */ + public String request(String json, RequestOptions requestOptions) throws ApiException, IOException { ClientInterface clientInterface = (ClientInterface) this.service.getClient().getHttpClient(); Config config = this.service.getClient().getConfig(); String responseBody; ApiException apiException; try { - return clientInterface.request(this.endpoint, json, config, this.service.isApiKeyRequired()); + return clientInterface.request(this.endpoint, json, config, this.service.isApiKeyRequired(), requestOptions); } catch (HTTPClientException e) { responseBody = e.getResponseBody(); apiException = new ApiException(e.getMessage(), e.getCode()); diff --git a/src/test/java/com/adyen/BaseTest.java b/src/test/java/com/adyen/BaseTest.java index beac69b75..6bd1563d7 100644 --- a/src/test/java/com/adyen/BaseTest.java +++ b/src/test/java/com/adyen/BaseTest.java @@ -49,7 +49,9 @@ protected Client createMockClientFromResponse(String response) { HttpURLConnectionClient httpURLConnectionClient = mock(HttpURLConnectionClient.class); try { when(httpURLConnectionClient.post(any(String.class), any(Map.class), any(Config.class))).thenReturn(response); - when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean())).thenReturn(response); + when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean(), any(RequestOptions.class))).thenReturn(response); + when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean(), (RequestOptions) isNull())).thenReturn(response); + } catch (IOException | HTTPClientException e) { e.printStackTrace(); } @@ -217,7 +219,7 @@ protected Client createMockClientForErrors(int status, String fileName) { HttpURLConnectionClient httpURLConnectionClient = mock(HttpURLConnectionClient.class); HTTPClientException httpClientException = new HTTPClientException(status, "An error occured", new HashMap>(), response); try { - when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean())).thenThrow(httpClientException); + when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean(), (RequestOptions) isNull())).thenThrow(httpClientException); } catch (IOException | HTTPClientException e) { fail("Unexpected exception: " + e.getMessage()); } diff --git a/src/test/java/com/adyen/PaymentTest.java b/src/test/java/com/adyen/PaymentTest.java index 78d50221c..246f6f096 100644 --- a/src/test/java/com/adyen/PaymentTest.java +++ b/src/test/java/com/adyen/PaymentTest.java @@ -34,6 +34,7 @@ import com.adyen.model.PaymentRequest; import com.adyen.model.PaymentRequest3d; import com.adyen.model.PaymentResult; +import com.adyen.model.RequestOptions; import com.adyen.service.Payment; import com.adyen.service.exception.ApiException; import static com.adyen.constants.ApiConstants.SelectedBrand.BOLETO_SANTANDER; @@ -46,6 +47,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -203,7 +205,8 @@ public void TestError401Mocked() throws Exception { HttpURLConnectionClient httpURLConnectionClient = mock(HttpURLConnectionClient.class); HTTPClientException httpClientException = new HTTPClientException(401, "An error occured", new HashMap>(), null); - when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean())).thenThrow(httpClientException); + when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean(), any(RequestOptions.class))).thenThrow(httpClientException); + when(httpURLConnectionClient.request(any(String.class), any(String.class), any(Config.class), anyBoolean(), (RequestOptions)isNull())).thenThrow(httpClientException); Client client = new Client(); client.setHttpClient(httpURLConnectionClient); diff --git a/src/test/java/com/adyen/service/ResourceTest.java b/src/test/java/com/adyen/service/ResourceTest.java index cf49f935e..0ac719aa3 100644 --- a/src/test/java/com/adyen/service/ResourceTest.java +++ b/src/test/java/com/adyen/service/ResourceTest.java @@ -59,7 +59,7 @@ public void setUp() { @Test public void testRequest() throws Exception { - when(clientInterfaceMock.request("", "request", null, false)).thenReturn("response"); + when(clientInterfaceMock.request("", "request", null, false, null)).thenReturn("response"); Resource resource = new Resource(serviceMock, "", null); String response = resource.request("request"); @@ -70,7 +70,7 @@ public void testRequest() throws Exception { @Test public void testRequestExceptionEmpty() throws IOException, HTTPClientException { try { - when(clientInterfaceMock.request("", "request", null, false)) + when(clientInterfaceMock.request("", "request", null, false, null)) .thenThrow(new HTTPClientException("message", 403, new HashMap>(), null)); Resource resource = new Resource(serviceMock, "", null);