Skip to content

Commit f649126

Browse files
committed
Add tests e2e for POS integration
1 parent 4b159e3 commit f649126

8 files changed

Lines changed: 440 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
**/target/**
44
.DS_Store
55
.vscode
6+
src/test/resources/config.properties

src/main/java/com/adyen/service/clouddevice/EncryptedCloudDeviceApi.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,15 @@ public CloudDeviceApiResponse sync(
126126
CloudDeviceApiSecuredResponse cloudDeviceApiSecuredResponse =
127127
CloudDeviceApiSecuredResponse.fromJson(response);
128128

129+
SaleToPOISecuredMessage saleToPOISecuredResponse =
130+
cloudDeviceApiSecuredResponse.getSaleToPOIResponse();
131+
if (saleToPOISecuredResponse == null || saleToPOISecuredResponse.getNexoBlob() == null) {
132+
// Terminal returned an unencrypted error response (e.g. terminal unreachable)
133+
return CloudDeviceApiResponse.fromJson(response);
134+
}
135+
129136
return CloudDeviceApiResponse.fromJson(
130-
nexoSecurityManager.decrypt(cloudDeviceApiSecuredResponse.getSaleToPOIResponse()));
137+
nexoSecurityManager.decrypt(saleToPOISecuredResponse));
131138
}
132139

133140
/**
@@ -190,7 +197,12 @@ public CloudDeviceApiAsyncResponse async(
190197
// Error response: an encrypted EventNotification wrapped in a SaleToPOIRequest envelope.
191198
// Decrypt it and surface the SaleToPOIRequest to the caller.
192199
CloudDeviceApiSecuredRequest encryptedError = CloudDeviceApiSecuredRequest.fromJson(response);
193-
String decryptedJson = nexoSecurityManager.decrypt(encryptedError.getSaleToPOIRequest());
200+
SaleToPOISecuredMessage encryptedErrorRequest = encryptedError.getSaleToPOIRequest();
201+
if (encryptedErrorRequest == null || encryptedErrorRequest.getNexoBlob() == null) {
202+
// Terminal returned an unencrypted error response (e.g. terminal unreachable)
203+
return CloudDeviceApiAsyncResponse.fromJson(response);
204+
}
205+
String decryptedJson = nexoSecurityManager.decrypt(encryptedErrorRequest);
194206
CloudDeviceApiAsyncResponse errorResponse =
195207
CloudDeviceApiAsyncResponse.fromJson(decryptedJson);
196208
cloudDeviceApiAsyncResponse.setSaleToPOIRequest(errorResponse.getSaleToPOIRequest());
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Adyen Java API Library
3+
*
4+
* Copyright (c) 2025 Adyen B.V.
5+
* This file is open source and available under the MIT license.
6+
* See the LICENSE file for more info.
7+
*/
8+
package com.adyen;
9+
10+
import com.adyen.enums.Environment;
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.util.Properties;
14+
15+
/**
16+
* Base class for Integration tests
17+
*
18+
* <p>Define in src/test/resources the configuration for the tests
19+
*
20+
* <p>``` ADYEN_API_KEY= ADYEN_MERCHANT_ACCOUNT= ADYEN_TERMINAL_DEVICE_ID=
21+
* ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER= ADYEN_TERMINAL_DEVICE_PASSPHRASE= ```
22+
*/
23+
public class BaseIntegrationTest {
24+
25+
private static Properties properties = null;
26+
27+
protected Client getClient() {
28+
return new Client(new Config().apiKey(getApiKey()).environment(Environment.TEST));
29+
}
30+
31+
protected String getApiKey() {
32+
return getProperty("ADYEN_API_KEY");
33+
}
34+
35+
protected String getMerchantAccount() {
36+
return getProperty("ADYEN_MERCHANT_ACCOUNT");
37+
}
38+
39+
protected String getTerminalDeviceId() {
40+
return getProperty("ADYEN_TERMINAL_DEVICE_ID");
41+
}
42+
43+
protected String getTerminalDeviceKeyIdentifier() {
44+
return getProperty("ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER");
45+
}
46+
47+
protected String getTerminalDevicePassphrase() {
48+
return getProperty("ADYEN_TERMINAL_DEVICE_PASSPHRASE");
49+
}
50+
51+
private Properties getProperties() {
52+
if (properties == null) {
53+
properties = new Properties();
54+
try (InputStream inputStream =
55+
BaseIntegrationTest.class.getClassLoader().getResourceAsStream("config.properties")) {
56+
if (inputStream != null) {
57+
properties.load(inputStream);
58+
}
59+
} catch (IOException e) {
60+
// Do nothing, properties will be empty
61+
}
62+
}
63+
64+
return properties;
65+
}
66+
67+
private String getProperty(String name) {
68+
String property = System.getenv(name);
69+
70+
if (property != null && !property.isEmpty()) {
71+
return property;
72+
}
73+
property = getProperties().getProperty(name);
74+
75+
if (property == null || property.isEmpty()) {
76+
throw new RuntimeException("Property " + name + " not defined");
77+
}
78+
79+
return property;
80+
}
81+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package com.adyen.service.clouddevice;
2+
3+
import com.adyen.BaseIntegrationTest;
4+
import com.adyen.model.clouddevice.*;
5+
import com.adyen.model.tapi.*;
6+
import com.adyen.security.clouddevice.EncryptionCredentialDetails;
7+
import java.math.BigDecimal;
8+
import java.time.OffsetDateTime;
9+
import java.time.ZoneOffset;
10+
import java.util.UUID;
11+
import org.junit.jupiter.api.Assertions;
12+
import org.junit.jupiter.api.Disabled;
13+
import org.junit.jupiter.api.Test;
14+
15+
/**
16+
* Verify Terminal integration: tests to send API requests to the Cloud Device API and test the
17+
* Terminal responds as expected.
18+
*
19+
* <p>Don't forget to:
20+
*
21+
* <ul>
22+
* <li>Enable the terminal
23+
* <li>Enable the test to run (by removing/commenting the {@code @Disabled} annotation)
24+
* <li>Set required variables by creating {@code src/test/resources/config.properties}:
25+
* </ul>
26+
*
27+
* <pre>{@code
28+
* # Example of config.properties
29+
* ADYEN_API_KEY=
30+
* ADYEN_MERCHANT_ACCOUNT=MyMerchantAccount
31+
* ADYEN_TERMINAL_DEVICE_ID=V400m-1234567890
32+
* ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER=
33+
* ADYEN_TERMINAL_DEVICE_PASSPHRASE=
34+
* }</pre>
35+
*
36+
* <ul>
37+
* <li>Run one test at a time with {@code mvn test -Dtest=CloudDeviceApiTerminalIT#sendSync}
38+
* <li>Disable the test again
39+
* </ul>
40+
*/
41+
public class CloudDeviceApiTerminalIT extends BaseIntegrationTest {
42+
43+
@Disabled("Enable when you want to test with the Terminal")
44+
@Test
45+
public void sendSync() throws Exception {
46+
47+
CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(getClient());
48+
49+
CloudDeviceApiRequest cloudDeviceApiRequest =
50+
createCloudDeviceAPIPaymentRequest(getTerminalDeviceId());
51+
52+
var response =
53+
cloudDeviceApi.sync(getMerchantAccount(), getTerminalDeviceId(), cloudDeviceApiRequest);
54+
55+
Assertions.assertNotNull(response);
56+
Assertions.assertNotNull(response.getSaleToPOIResponse());
57+
Assertions.assertEquals(getTerminalDeviceId(), response.getSaleToPOIResponse().getMessageHeader().getPOIID());
58+
}
59+
60+
@Disabled("Enable when you want to test with the Terminal")
61+
@Test
62+
public void sendAsync() throws Exception {
63+
64+
CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(getClient());
65+
66+
CloudDeviceApiRequest cloudDeviceApiRequest =
67+
createCloudDeviceAPIPaymentRequest(getTerminalDeviceId());
68+
69+
var response =
70+
cloudDeviceApi.async(getMerchantAccount(), getTerminalDeviceId(), cloudDeviceApiRequest);
71+
72+
Assertions.assertNotNull(response);
73+
Assertions.assertEquals("ok", response.getResult());
74+
}
75+
76+
@Disabled("Enable when you want to test with the Terminal")
77+
@Test
78+
public void sendEncryptedSync() throws Exception {
79+
80+
CloudDeviceApiRequest cloudDeviceApiRequest =
81+
createCloudDeviceAPIPaymentRequest(getTerminalDeviceId());
82+
83+
EncryptionCredentialDetails encryptionCredentialDetails =
84+
new EncryptionCredentialDetails()
85+
.adyenCryptoVersion(1)
86+
.keyIdentifier(getTerminalDeviceKeyIdentifier())
87+
.keyVersion(1)
88+
.passphrase(getTerminalDevicePassphrase());
89+
90+
EncryptedCloudDeviceApi encryptedCloudDeviceApi =
91+
new EncryptedCloudDeviceApi(getClient(), encryptionCredentialDetails);
92+
93+
var response =
94+
encryptedCloudDeviceApi.sync(
95+
getMerchantAccount(), getTerminalDeviceId(), cloudDeviceApiRequest);
96+
97+
Assertions.assertNotNull(response);
98+
Assertions.assertNotNull(response.getSaleToPOIResponse());
99+
Assertions.assertEquals(getTerminalDeviceId(), response.getSaleToPOIResponse().getMessageHeader().getPOIID());
100+
}
101+
102+
@Disabled("Enable when you want to test with the Terminal")
103+
@Test
104+
public void getConnectedDevices() throws Exception {
105+
106+
CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(getClient());
107+
108+
var response = cloudDeviceApi.getConnectedDevices(getMerchantAccount());
109+
110+
Assertions.assertNotNull(response);
111+
Assertions.assertNotNull(response.getUniqueDeviceIds());
112+
Assertions.assertTrue(response.getUniqueDeviceIds().contains(getTerminalDeviceId()));
113+
}
114+
115+
@Disabled("Enable when you want to test with the Terminal")
116+
@Test
117+
public void sendEncryptedAsync() throws Exception {
118+
119+
CloudDeviceApiRequest cloudDeviceApiRequest =
120+
createCloudDeviceAPIPaymentRequest(getTerminalDeviceId());
121+
122+
EncryptionCredentialDetails encryptionCredentialDetails =
123+
new EncryptionCredentialDetails()
124+
.adyenCryptoVersion(1)
125+
.keyIdentifier(getTerminalDeviceKeyIdentifier())
126+
.keyVersion(1)
127+
.passphrase(getTerminalDevicePassphrase());
128+
129+
EncryptedCloudDeviceApi encryptedCloudDeviceApi =
130+
new EncryptedCloudDeviceApi(getClient(), encryptionCredentialDetails);
131+
132+
var response =
133+
encryptedCloudDeviceApi.async(
134+
getMerchantAccount(), getTerminalDeviceId(), cloudDeviceApiRequest);
135+
136+
Assertions.assertNotNull(response);
137+
Assertions.assertEquals("ok", response.getResult());
138+
}
139+
140+
protected CloudDeviceApiRequest createCloudDeviceAPIPaymentRequest(String deviceId) {
141+
SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest();
142+
143+
var randomId = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 10);
144+
145+
MessageHeader messageHeader = new MessageHeader();
146+
messageHeader.setProtocolVersion("3.0");
147+
messageHeader.setMessageClass(MessageClass.SERVICE);
148+
messageHeader.setMessageCategory(MessageCategory.PAYMENT);
149+
messageHeader.setMessageType(MessageType.REQUEST);
150+
messageHeader.setSaleID(randomId);
151+
messageHeader.setServiceID(randomId);
152+
messageHeader.setPOIID(deviceId);
153+
154+
saleToPOIRequest.setMessageHeader(messageHeader);
155+
156+
PaymentRequest paymentRequest = new PaymentRequest();
157+
158+
SaleData saleData = new SaleData();
159+
TransactionIDType transactionIdentification = new TransactionIDType();
160+
transactionIdentification.setTransactionID(randomId);
161+
OffsetDateTime timestamp = OffsetDateTime.now(ZoneOffset.UTC);
162+
transactionIdentification.setTimeStamp(timestamp);
163+
saleData.setSaleTransactionID(transactionIdentification);
164+
165+
PaymentTransaction paymentTransaction = new PaymentTransaction();
166+
AmountsReq amountsReq = new AmountsReq();
167+
amountsReq.setCurrency("EUR");
168+
amountsReq.setRequestedAmount(BigDecimal.TEN);
169+
paymentTransaction.setAmountsReq(amountsReq);
170+
171+
paymentRequest.setSaleData(saleData);
172+
paymentRequest.setPaymentTransaction(paymentTransaction);
173+
174+
saleToPOIRequest.setPaymentRequest(paymentRequest);
175+
176+
CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest();
177+
cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest);
178+
179+
return cloudDeviceApiRequest;
180+
}
181+
}

src/test/java/com/adyen/service/clouddevice/CloudDeviceApiTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.adyen.enums.Region;
1212
import com.adyen.model.clouddevice.*;
1313
import com.adyen.model.tapi.*;
14+
import com.fasterxml.jackson.databind.JsonNode;
1415
import java.math.BigDecimal;
1516
import java.time.OffsetDateTime;
1617
import java.time.ZoneOffset;
@@ -224,6 +225,66 @@ public void getDeviceStatus() throws Exception {
224225
null);
225226
}
226227

228+
@Test
229+
public void cloudDeviceApiRequestSerialisesMessageHeader() throws Exception {
230+
CloudDeviceApiRequest request = createCloudDeviceAPIPaymentRequest();
231+
232+
String json = request.toJson();
233+
JsonNode root = JSON.getMapper().readTree(json);
234+
JsonNode messageHeader = root.path("SaleToPOIRequest").path("MessageHeader");
235+
236+
Assertions.assertEquals("3.0", messageHeader.path("ProtocolVersion").asText());
237+
Assertions.assertEquals("Service", messageHeader.path("MessageClass").asText());
238+
Assertions.assertEquals("Payment", messageHeader.path("MessageCategory").asText());
239+
Assertions.assertEquals("Request", messageHeader.path("MessageType").asText());
240+
Assertions.assertEquals("001", messageHeader.path("SaleID").asText());
241+
Assertions.assertEquals("001", messageHeader.path("ServiceID").asText());
242+
Assertions.assertEquals("P400Plus-123456789", messageHeader.path("POIID").asText());
243+
}
244+
245+
@Test
246+
public void cloudDeviceApiRequestSerialisesPaymentRequest() throws Exception {
247+
CloudDeviceApiRequest request = createCloudDeviceAPIPaymentRequest();
248+
249+
String json = request.toJson();
250+
JsonNode root = JSON.getMapper().readTree(json);
251+
JsonNode amountsReq =
252+
root.path("SaleToPOIRequest")
253+
.path("PaymentRequest")
254+
.path("PaymentTransaction")
255+
.path("AmountsReq");
256+
257+
Assertions.assertEquals("EUR", amountsReq.path("Currency").asText());
258+
Assertions.assertEquals(
259+
BigDecimal.ONE, amountsReq.path("RequestedAmount").decimalValue().stripTrailingZeros());
260+
}
261+
262+
@Test
263+
public void cloudDeviceApiRequestSerialisesTransactionId() throws Exception {
264+
CloudDeviceApiRequest request = createCloudDeviceAPIPaymentRequest();
265+
266+
String json = request.toJson();
267+
JsonNode root = JSON.getMapper().readTree(json);
268+
JsonNode saleTransactionID =
269+
root.path("SaleToPOIRequest")
270+
.path("PaymentRequest")
271+
.path("SaleData")
272+
.path("SaleTransactionID");
273+
274+
Assertions.assertEquals("001", saleTransactionID.path("TransactionID").asText());
275+
Assertions.assertFalse(saleTransactionID.path("TimeStamp").isMissingNode());
276+
}
277+
278+
@Test
279+
public void cloudDeviceApiRequestRoundTrip() throws Exception {
280+
CloudDeviceApiRequest original = createCloudDeviceAPIPaymentRequest();
281+
282+
String json = original.toJson();
283+
CloudDeviceApiRequest deserialised = CloudDeviceApiRequest.fromJson(json);
284+
285+
Assertions.assertEquals(original, deserialised);
286+
}
287+
227288
protected CloudDeviceApiRequest createCloudDeviceAPIPaymentRequest() {
228289
SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest();
229290

0 commit comments

Comments
 (0)