Skip to content

Commit 936c6de

Browse files
TerminalAPI: PredefinedContentHelper for managing Display Events (#1546)
* Add helper class * Update README * Correct javadoc * Address PR comments * Update README.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent a6b8dfd commit 936c6de

File tree

3 files changed

+258
-0
lines changed

3 files changed

+258
-0
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,22 @@ terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest);
454454
TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest);
455455
```
456456

457+
### Helper classes
458+
459+
Use `PredefinedContentHelper` to parse Display notification types which you find in `PredefinedContent->ReferenceID`
460+
```java
461+
PredefinedContentHelper helper = new PredefinedContentHelper(predefinedContent.getReferenceID());
462+
463+
// Safely extract and use the event type with Optional
464+
helper.getEvent().ifPresent(event -> {
465+
System.out.println("Received event: " + event);
466+
if (event == PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED) {
467+
// Handle PIN entry event
468+
System.out.println("The user has entered their PIN.");
469+
}
470+
});
471+
```
472+
457473
## Using the Local Terminal API Integration
458474
The request and response payloads are identical to the Cloud Terminal API, however, additional encryption details are required to perform the requests.
459475
### Local terminal API Using Keystore
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.adyen.model.nexo;
2+
3+
import java.io.UnsupportedEncodingException;
4+
import java.net.URLDecoder;
5+
import java.nio.charset.StandardCharsets;
6+
import java.util.Collections;
7+
import java.util.LinkedHashMap;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
11+
/**
12+
* A helper class to parse and manage the key-value pairs within a PredefinedContent referenceID string.
13+
* The referenceID is expected to be in a URL query string format (e.g., {@code key1=value1&key2=value2}).
14+
*/
15+
public final class PredefinedContentHelper {
16+
17+
private static final String KEY_EVENT = "event";
18+
private static final String KEY_TRANSACTION_ID = "TransactionID";
19+
private static final String KEY_TIME_STAMP = "TimeStamp";
20+
21+
/**
22+
* Defines the supported events for display notifications within a PredefinedContent reference ID.
23+
*/
24+
public enum DisplayNotificationEvent {
25+
TENDER_CREATED,
26+
CARD_INSERTED,
27+
CARD_PRESENTED,
28+
CARD_SWIPED,
29+
WAIT_FOR_APP_SELECTION,
30+
APPLICATION_SELECTED,
31+
ASK_SIGNATURE,
32+
CHECK_SIGNATURE,
33+
SIGNATURE_CHECKED,
34+
WAIT_FOR_PIN,
35+
PIN_ENTERED,
36+
PRINT_RECEIPT,
37+
RECEIPT_PRINTED,
38+
CARD_REMOVED,
39+
TENDER_FINAL,
40+
ASK_DCC,
41+
DCC_ACCEPTED,
42+
DCC_REJECTED,
43+
ASK_GRATUITY,
44+
GRATUITY_ENTERED,
45+
BALANCE_QUERY_STARTED,
46+
BALANCE_QUERY_COMPLETED,
47+
LOAD_STARTED,
48+
LOAD_COMPLETED,
49+
PROVIDE_CARD_DETAILS,
50+
CARD_DETAILS_PROVIDED
51+
}
52+
53+
private final Map<String, String> params;
54+
55+
/**
56+
* Constructs a helper instance by parsing the provided reference ID.
57+
*
58+
* @param referenceId The string from {@link PredefinedContent#getReferenceID()},
59+
* expected to be in URL query string format.
60+
*/
61+
public PredefinedContentHelper(String referenceId) {
62+
this.params = parse(referenceId);
63+
}
64+
65+
/**
66+
* Extracts and validates the 'event' value from the reference ID.
67+
*
68+
* @return An {@link Optional} containing the {@link DisplayNotificationEvent} if it is present and valid,
69+
* otherwise an empty Optional.
70+
* <pre>{@code
71+
* PredefinedContentHelper helper = new PredefinedContentHelper("...&event=PIN_ENTERED");
72+
* helper.getEvent().ifPresent(event -> System.out.println(event)); // Prints PIN_ENTERED
73+
* }</pre>
74+
*/
75+
public Optional<DisplayNotificationEvent> getEvent() {
76+
return get(KEY_EVENT).flatMap(eventValue -> {
77+
try {
78+
return Optional.of(DisplayNotificationEvent.valueOf(eventValue));
79+
} catch (IllegalArgumentException e) {
80+
return Optional.empty(); // The event string is not a valid enum constant
81+
}
82+
});
83+
}
84+
85+
/**
86+
* Gets the transaction ID from the reference ID.
87+
*
88+
* @return An {@link Optional} containing the TransactionID, or an empty Optional if not present.
89+
*/
90+
public Optional<String> getTransactionId() {
91+
return get(KEY_TRANSACTION_ID);
92+
}
93+
94+
/**
95+
* Gets the timestamp from the reference ID.
96+
*
97+
* @return An {@link Optional} containing the TimeStamp, or an empty Optional if not present.
98+
*/
99+
public Optional<String> getTimeStamp() {
100+
return get(KEY_TIME_STAMP);
101+
}
102+
103+
/**
104+
* Gets the value for a given key from the reference ID.
105+
*
106+
* @param key The name of the parameter to retrieve.
107+
* @return An {@link Optional} containing the parameter's value, or an empty Optional if not present.
108+
*/
109+
public Optional<String> get(String key) {
110+
return Optional.ofNullable(params.get(key));
111+
}
112+
113+
/**
114+
* Returns an unmodifiable view of all parsed parameters.
115+
*
116+
* @return An unmodifiable {@link Map} of all key-value pairs from the reference ID.
117+
*/
118+
public Map<String, String> toMap() {
119+
return Collections.unmodifiableMap(params);
120+
}
121+
122+
/**
123+
* Parses a URL query-like string into a map.
124+
*
125+
* @param referenceId The string to parse.
126+
* @return A map of the parsed key-value pairs.
127+
*/
128+
private static Map<String, String> parse(String referenceId) {
129+
if (referenceId == null || referenceId.trim().isEmpty()) {
130+
return Collections.emptyMap();
131+
}
132+
133+
Map<String, String> queryPairs = new LinkedHashMap<>();
134+
String[] pairs = referenceId.split("&");
135+
for (String pair : pairs) {
136+
int idx = pair.indexOf("=");
137+
if (idx > 0 && idx < pair.length() - 1) {
138+
String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
139+
String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
140+
queryPairs.put(key, value);
141+
}
142+
}
143+
return queryPairs;
144+
}
145+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.adyen.model.nexo;
2+
3+
import org.junit.Test;
4+
5+
import java.util.Map;
6+
import java.util.Optional;
7+
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertFalse;
10+
import static org.junit.Assert.assertTrue;
11+
12+
/**
13+
* Tests for {@link PredefinedContentHelper}.
14+
*/
15+
public class PredefinedContentHelperTest {
16+
17+
@Test
18+
public void testShouldExtractValidEvent() {
19+
String referenceId = "TransactionID=oLkO001517998574000&TimeStamp=2018-02-07T10%3a16%3a14.000Z&event=PIN_ENTERED";
20+
PredefinedContentHelper helper = new PredefinedContentHelper(referenceId);
21+
22+
Optional<PredefinedContentHelper.DisplayNotificationEvent> event = helper.getEvent();
23+
assertTrue("Event should be present", event.isPresent());
24+
assertEquals(PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED, event.get());
25+
}
26+
27+
@Test
28+
public void testShouldReturnEmptyForInvalidEvent() {
29+
PredefinedContentHelper helper = new PredefinedContentHelper("event=INVALID_EVENT");
30+
31+
assertFalse("Event should not be present for invalid value", helper.getEvent().isPresent());
32+
}
33+
34+
@Test
35+
public void testShouldExtractTransactionId() {
36+
PredefinedContentHelper helper = new PredefinedContentHelper("TransactionID=12345&TimeStamp=2018-02-07T10%3a16%3a14.000Z&event=PIN_ENTERED");
37+
38+
Optional<String> transactionId = helper.getTransactionId();
39+
assertTrue("TransactionID should be present", transactionId.isPresent());
40+
assertEquals("12345", transactionId.get());
41+
}
42+
43+
@Test
44+
public void testShouldExtractTimeStamp() {
45+
PredefinedContentHelper helper = new PredefinedContentHelper("TimeStamp=2024-07-11T12:00:00Z");
46+
47+
Optional<String> timeStamp = helper.getTimeStamp();
48+
assertTrue("TimeStamp should be present", timeStamp.isPresent());
49+
assertEquals("2024-07-11T12:00:00Z", timeStamp.get());
50+
}
51+
52+
@Test
53+
public void testShouldExtractArbitraryKey() {
54+
PredefinedContentHelper helper = new PredefinedContentHelper("foo=bar&baz=qux");
55+
56+
Optional<String> foo = helper.get("foo");
57+
assertTrue("Value for 'foo' should be present", foo.isPresent());
58+
assertEquals("bar", foo.get());
59+
60+
Optional<String> baz = helper.get("baz");
61+
assertTrue("Value for 'baz' should be present", baz.isPresent());
62+
assertEquals("qux", baz.get());
63+
64+
assertFalse("Value for 'missing' should not be present", helper.get("missing").isPresent());
65+
}
66+
67+
@Test
68+
public void testShouldConvertParamsToMap() {
69+
PredefinedContentHelper helper = new PredefinedContentHelper("a=1&b=2&event=WAIT_FOR_PIN");
70+
71+
Map<String, String> map = helper.toMap();
72+
assertEquals(3, map.size());
73+
assertEquals("1", map.get("a"));
74+
assertEquals("2", map.get("b"));
75+
assertEquals("WAIT_FOR_PIN", map.get("event"));
76+
}
77+
78+
@Test
79+
public void testShouldHandleEmptyReferenceId() {
80+
PredefinedContentHelper helper = new PredefinedContentHelper("");
81+
82+
assertFalse(helper.getEvent().isPresent());
83+
assertFalse(helper.getTransactionId().isPresent());
84+
assertFalse(helper.getTimeStamp().isPresent());
85+
assertTrue(helper.toMap().isEmpty());
86+
}
87+
88+
@Test
89+
public void testShouldHandleNullReferenceId() {
90+
PredefinedContentHelper helper = new PredefinedContentHelper(null);
91+
92+
assertFalse(helper.getEvent().isPresent());
93+
assertFalse(helper.getTransactionId().isPresent());
94+
assertFalse(helper.getTimeStamp().isPresent());
95+
assertTrue(helper.toMap().isEmpty());
96+
}
97+
}

0 commit comments

Comments
 (0)