userPrincipal = Optional.ofNullable(requestContext.getSecurityContext().getUserPrincipal());
+ return userPrincipal.map(principal -> (Account) principal);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterFilter.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterFilter.java
new file mode 100644
index 000000000..e8222ade8
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterFilter.java
@@ -0,0 +1,86 @@
+package uk.gov.pay.api.filter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.filter.ratelimit.RateLimitException;
+import uk.gov.pay.api.filter.ratelimit.RateLimiter;
+import uk.gov.pay.api.resources.error.ApiErrorResponse.Code;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.util.Optional;
+
+import static uk.gov.pay.api.resources.error.ApiErrorResponse.anApiErrorResponse;
+
+/**
+ * Allow only a certain number of requests from the same source (given by the Authorization Header)
+ * within the given time configured in the RateLimiter. See {@link RateLimiter}
+ *
+ * 429 Too Many Requests will be returned when rate limit is reached.
+ */
+@Provider
+@Priority(Priorities.USER + 1000)
+public class RateLimiterFilter implements ContainerRequestFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterFilter.class);
+ private static final int TOO_MANY_REQUESTS_STATUS_CODE = 429;
+ private static final String UTF8_CHARACTER_ENCODING = "utf-8";
+
+ private final RateLimiter rateLimiter;
+ private ObjectMapper objectMapper;
+
+ /**
+ * @param rateLimiter Limiter in number of requests per given time coming from the same source (Authorization)
+ */
+ @Inject
+ public RateLimiterFilter(RateLimiter rateLimiter, ObjectMapper objectMapper) {
+ this.rateLimiter = rateLimiter;
+ this.objectMapper = objectMapper;
+ }
+
+ private void setTooManyRequestsError() throws IOException {
+ String errorResponse = objectMapper.writeValueAsString(anApiErrorResponse(Code.TOO_MANY_REQUESTS_ERROR));
+ Response.ResponseBuilder builder = Response.status(TOO_MANY_REQUESTS_STATUS_CODE)
+ .entity(errorResponse)
+ .encoding(UTF8_CHARACTER_ENCODING)
+ .variant(new Variant(MediaType.APPLICATION_JSON_TYPE, "", UTF8_CHARACTER_ENCODING));
+
+ throw new WebApplicationException(builder.build());
+ }
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ if ("healthcheck".equals(requestContext.getUriInfo().getPath())) {
+ return;
+ }
+
+ String accountId = getAccountId(requestContext);
+ RateLimiterKey key = RateLimiterKey.from(requestContext, accountId);
+ try {
+ rateLimiter.checkRateOf(accountId, key);
+ } catch (RateLimitException e) {
+ LOGGER.info("Rate limit reached for current service [account - {}, method - {}]. Sending response '429 Too Many Requests'",
+ accountId, key.getMethod());
+ setTooManyRequestsError();
+ }
+ }
+
+ private String getAccountId(ContainerRequestContext requestContext) {
+ Account account = (Account) requestContext.getSecurityContext().getUserPrincipal();
+ return Optional.ofNullable(account)
+ .map(Account::getAccountId)
+ .orElse(StringUtils.EMPTY);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterKey.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterKey.java
new file mode 100644
index 000000000..3a86b87d8
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/RateLimiterKey.java
@@ -0,0 +1,46 @@
+package uk.gov.pay.api.filter;
+
+import uk.gov.pay.api.utils.PathHelper;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+public class RateLimiterKey {
+
+ private String method;
+ private String key;
+ private String keyType;
+
+ public RateLimiterKey(String key, String keyType, String method) {
+ this.key = key;
+ this.keyType = keyType;
+ this.method = method;
+ }
+
+ public static RateLimiterKey from(ContainerRequestContext requestContext, String accountId) {
+ final String method = requestContext.getMethod();
+
+ StringBuilder builder = new StringBuilder(method);
+
+ final String pathType = PathHelper.getPathType(requestContext.getUriInfo().getPath(), method);
+ if (!pathType.isBlank()) {
+ builder.append("-").append(pathType);
+ }
+
+ final String keyType = builder.toString();
+ builder.append("-").append(accountId);
+
+ return new RateLimiterKey(builder.toString(), keyType, method);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getKeyType() {
+ return keyType;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/LocalRateLimiter.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/LocalRateLimiter.java
new file mode 100644
index 000000000..da7c055e2
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/LocalRateLimiter.java
@@ -0,0 +1,64 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.RateLimiterConfig;
+import uk.gov.pay.api.filter.RateLimiterKey;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.HttpMethod;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+@Singleton
+public class LocalRateLimiter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LocalRateLimiter.class);
+
+ private final int noOfReqPerNode;
+ private final int noOfReqForPostPerNode;
+ private final int perMillis;
+
+ private final Cache cache;
+
+ @Inject
+ public LocalRateLimiter(RateLimiterConfig rateLimiterConfig) {
+ this.noOfReqPerNode = rateLimiterConfig.getNoOfReqPerNode();
+ this.noOfReqForPostPerNode = rateLimiterConfig.getNoOfReqForPostPerNode();
+ this.perMillis = rateLimiterConfig.getPerMillis();
+ this.cache = CacheBuilder.newBuilder()
+ .expireAfterAccess(perMillis, TimeUnit.MILLISECONDS)
+ .build();
+ }
+
+ void checkRateOf(String accountId, RateLimiterKey rateLimiterKey) throws RateLimitException {
+
+ RateLimit rateLimit = null;
+ try {
+ rateLimit = cache.get(rateLimiterKey.getKey(), () -> new RateLimit(getNoOfRequestsForMethod(rateLimiterKey.getMethod()), perMillis));
+ rateLimit.updateAllowance();
+ } catch (ExecutionException e) {
+ //ExecutionException is thrown when the valueLoader (cache.get()) throws a checked exception.
+ //We just create a new instance (RateLimit) so no exceptions will be thrown, this should never happen.
+ LOGGER.error("Unexpected error creating a Rate Limiter object in cache", e);
+ } catch (RateLimitException e) {
+ LOGGER.info(String.format("LocalRateLimiter - Rate limit exceeded for account [%s] and method [%s] - count: %d, rate allowed: %d",
+ accountId,
+ rateLimiterKey.getMethod(),
+ rateLimit.getRequestCount(),
+ rateLimit.getNoOfReq()));
+
+ throw e;
+ }
+ }
+
+ private int getNoOfRequestsForMethod(String method) {
+ if (HttpMethod.POST.equals(method)) {
+ return noOfReqForPostPerNode;
+ }
+ return noOfReqPerNode;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimit.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimit.java
new file mode 100644
index 000000000..887b06fa2
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimit.java
@@ -0,0 +1,48 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import static java.time.temporal.ChronoUnit.MILLIS;
+
+final class RateLimit {
+
+ private final int noOfReq;
+ private final int perMillis;
+
+ private Instant created;
+ private int requestCount;
+
+ RateLimit(int noOfReq, int perMillis) {
+ this.noOfReq = noOfReq;
+ this.perMillis = perMillis;
+ this.requestCount = 0;
+ this.created = Instant.now().truncatedTo(MILLIS);
+ }
+
+ /**
+ * This block needs to be synchronous. Each RateLimit object will be shared between requests
+ * from the same source (Service), so is not shared across all the requests.
+ *
+ * @throws RateLimitException
+ */
+ synchronized void updateAllowance() throws RateLimitException {
+ requestCount += 1;
+ Instant now = Instant.now().truncatedTo(MILLIS);
+ if (Duration.between(created, now).toMillis() >= perMillis) {
+ requestCount = 1;
+ created = now;
+ }
+ if (requestCount > noOfReq) {
+ throw new RateLimitException();
+ }
+ }
+
+ public int getNoOfReq() {
+ return noOfReq;
+ }
+
+ public int getRequestCount() {
+ return requestCount;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitException.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitException.java
new file mode 100644
index 000000000..d3bf3a245
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitException.java
@@ -0,0 +1,4 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+public class RateLimitException extends Exception {
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitManager.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitManager.java
new file mode 100644
index 000000000..22c31e85c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimitManager.java
@@ -0,0 +1,46 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+import uk.gov.pay.api.app.config.RateLimiterConfig;
+import uk.gov.pay.api.filter.RateLimiterKey;
+
+import javax.ws.rs.HttpMethod;
+
+public class RateLimitManager {
+
+ private RateLimiterConfig configuration;
+
+ public RateLimitManager(RateLimiterConfig config) {
+ configuration = config;
+ }
+
+ public int getAllowedNumberOfRequests(RateLimiterKey rateLimiterKey, String account) {
+ if (configuration.getElevatedAccounts().contains(account)) {
+ if (HttpMethod.POST.equals(rateLimiterKey.getMethod())) {
+ return configuration.getNoOfPostReqForElevatedAccounts();
+ }
+
+ return configuration.getNoOfReqForElevatedAccounts();
+ }
+
+ if (configuration.getLowTrafficAccounts().contains(account)) {
+ if (HttpMethod.POST.equals(rateLimiterKey.getMethod())) {
+ return configuration.getNoOfPostReqForLowTrafficAccounts();
+ }
+ return configuration.getNoOfReqForLowTrafficAccounts();
+ }
+
+ if (HttpMethod.POST.equals(rateLimiterKey.getMethod())) {
+ return configuration.getNoOfReqForPost();
+ }
+
+ return configuration.getNoOfReq();
+ }
+
+ public int getRateLimitInterval(String account) {
+ if (configuration.getLowTrafficAccounts().contains(account)) {
+ return configuration.getIntervalInMillisForLowTrafficAccounts();
+ }
+
+ return configuration.getPerMillis();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimiter.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimiter.java
new file mode 100644
index 000000000..e6af13c2e
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RateLimiter.java
@@ -0,0 +1,32 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.filter.RateLimiterKey;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class RateLimiter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiter.class);
+
+ private final LocalRateLimiter localRateLimiter;
+ private final RedisRateLimiter redisRateLimiter;
+
+ @Inject
+ public RateLimiter(LocalRateLimiter localRateLimiter, RedisRateLimiter redisRateLimiter) {
+ this.localRateLimiter = localRateLimiter;
+ this.redisRateLimiter = redisRateLimiter;
+ }
+
+ public void checkRateOf(String accountId, RateLimiterKey key) throws RateLimitException {
+ try {
+ redisRateLimiter.checkRateOf(accountId, key);
+ } catch (RedisException e) {
+ LOGGER.warn("Exception occurred checking rate limits using RedisRateLimiter, falling back to LocalRateLimiter");
+ localRateLimiter.checkRateOf(accountId, key);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisException.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisException.java
new file mode 100644
index 000000000..06d7d72e4
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisException.java
@@ -0,0 +1,4 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+public class RedisException extends Exception {
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisRateLimiter.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisRateLimiter.java
new file mode 100644
index 000000000..cb7a99aae
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/filter/ratelimit/RedisRateLimiter.java
@@ -0,0 +1,101 @@
+package uk.gov.pay.api.filter.ratelimit;
+
+import com.codahale.metrics.MetricRegistry;
+import com.google.inject.Inject;
+import com.google.inject.OutOfScopeException;
+import io.dropwizard.setup.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.RateLimiterConfig;
+import uk.gov.pay.api.filter.RateLimiterKey;
+import uk.gov.pay.api.managed.RedisClientManager;
+
+import javax.inject.Singleton;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoField;
+import java.util.concurrent.Callable;
+
+@Singleton
+public class RedisRateLimiter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RedisRateLimiter.class);
+ private final MetricRegistry metricsRegistry;
+
+ private RateLimitManager rateLimitManager;
+ private RedisClientManager redisClientManager;
+
+ @Inject
+ public RedisRateLimiter(RateLimiterConfig rateLimiterConfig, RedisClientManager redisClientManager, Environment environment) {
+ this.rateLimitManager = new RateLimitManager(rateLimiterConfig);
+ this.redisClientManager = redisClientManager;
+ this.metricsRegistry = environment.metrics();
+ }
+
+ /**
+ * @throws RateLimitException
+ */
+ void checkRateOf(String accountId, RateLimiterKey key)
+ throws RedisException, RateLimitException {
+
+ Long count;
+
+ try {
+ int rateLimitInterval = rateLimitManager.getRateLimitInterval(accountId);
+ count = updateAllowance(key.getKey(), rateLimitInterval);
+ } catch (Exception e) {
+ LOGGER.info("Failed to update allowance. Cause of error: " + e.getMessage());
+ // Exception possible if redis is unavailable or perMillis is too high
+ throw new RedisException();
+ }
+
+ if (count != null) {
+ int allowedNumberOfRequests = rateLimitManager.getAllowedNumberOfRequests(key, accountId);
+ if (count > allowedNumberOfRequests) {
+ LOGGER.info(String.format("RedisRateLimiter - Rate limit exceeded for account [%s] and method [%s] - count: %d, rate allowed: %d",
+ accountId, key.getKeyType(), count, allowedNumberOfRequests));
+ throw new RateLimitException();
+ }
+ }
+ }
+
+ private Long updateAllowance(String key, int rateLimitInterval) throws Exception {
+ String derivedKey = getKeyForWindow(key, rateLimitInterval);
+ Long count = time("redis.incr", () -> redisClientManager.getRedisConnection().sync().incr(derivedKey));
+ time("redis.expire", () -> redisClientManager.getRedisConnection().sync().expire(derivedKey, rateLimitInterval / 1000));
+ return count;
+ }
+
+ private T time(String metricName, Callable callable) throws Exception {
+ return this.metricsRegistry.timer(metricName).time(callable);
+ }
+
+ /**
+ * Derives Key (Service Key + Window) to use in Redis for noOfReq limiting.
+ * Recommended to use perMillis to lowest granularity. i.e, to seconds. 1000, 2000
+ *
+ * Depends on perMillis
+ *
+ * - perMillis < 1000 : Window considered for milliseconds
+ * - perMillis >=1000 && <60000 : Window considered for seconds(s)
+ * - perMillis >=60000 && <3600000 : Window considered for minute(s)
+ *
+ * @return new key based on perMillis (works for second/minute/hour windows only)
+ */
+ private String getKeyForWindow(String key, int rateLimitInterval) throws OutOfScopeException {
+
+ LocalDateTime now = LocalDateTime.now();
+
+ int window;
+
+ if (rateLimitInterval >= 1 && rateLimitInterval < 1000) {
+ window = (now.get(ChronoField.MILLI_OF_DAY) / rateLimitInterval) + 1;
+ } else if (rateLimitInterval >= 1000 && rateLimitInterval < 60000) {
+ window = now.get(ChronoField.SECOND_OF_MINUTE) / (rateLimitInterval / 1000);
+ } else if (rateLimitInterval >= 60000 && rateLimitInterval < 3600000) {
+ window = now.get(ChronoField.MINUTE_OF_HOUR) / (rateLimitInterval / 1000);
+ } else {
+ throw new OutOfScopeException("Rate limit interval specified is not currently supported");
+ }
+
+ return key + window;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/healthcheck/Ping.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/healthcheck/Ping.java
new file mode 100644
index 000000000..2bbfdc8e9
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/healthcheck/Ping.java
@@ -0,0 +1,11 @@
+package uk.gov.pay.api.healthcheck;
+
+import com.codahale.metrics.health.HealthCheck;
+
+public class Ping extends HealthCheck {
+
+ @Override
+ protected Result check() {
+ return Result.healthy();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateAgreementRequestDeserializer.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateAgreementRequestDeserializer.java
new file mode 100644
index 000000000..32b677d65
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateAgreementRequestDeserializer.java
@@ -0,0 +1,31 @@
+package uk.gov.pay.api.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import uk.gov.pay.api.agreement.model.CreateAgreementRequest;
+import uk.gov.pay.api.exception.BadRequestException;
+
+import java.io.IOException;
+
+import static uk.gov.pay.api.json.RequestJsonParser.parseAgreementRequest;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_AGREEMENT_PARSING_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+
+public class CreateAgreementRequestDeserializer extends StdDeserializer {
+
+ public CreateAgreementRequestDeserializer() {
+ super(CreateAgreementRequest.class);
+ }
+
+ @Override
+ public CreateAgreementRequest deserialize(JsonParser parser, DeserializationContext context) {
+ try {
+ JsonNode json = parser.readValueAsTree();
+ return parseAgreementRequest(json);
+ } catch (IOException e) {
+ throw new BadRequestException(aRequestError(CREATE_AGREEMENT_PARSING_ERROR));
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateCardPaymentRequestDeserializer.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateCardPaymentRequestDeserializer.java
new file mode 100644
index 000000000..e004d865c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreateCardPaymentRequestDeserializer.java
@@ -0,0 +1,31 @@
+package uk.gov.pay.api.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import uk.gov.pay.api.exception.BadRequestException;
+import uk.gov.pay.api.model.CreateCardPaymentRequest;
+
+import java.io.IOException;
+
+import static uk.gov.pay.api.json.RequestJsonParser.parsePaymentRequest;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_PARSING_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+
+public class CreateCardPaymentRequestDeserializer extends StdDeserializer {
+
+ public CreateCardPaymentRequestDeserializer() {
+ super(CreateCardPaymentRequest.class);
+ }
+
+ @Override
+ public CreateCardPaymentRequest deserialize(JsonParser parser, DeserializationContext context) {
+ try {
+ JsonNode json = parser.readValueAsTree();
+ return parsePaymentRequest(json);
+ } catch (IOException e) {
+ throw new BadRequestException(aRequestError(CREATE_PAYMENT_PARSING_ERROR));
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreatePaymentRefundRequestDeserializer.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreatePaymentRefundRequestDeserializer.java
new file mode 100644
index 000000000..8a0236da4
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/CreatePaymentRefundRequestDeserializer.java
@@ -0,0 +1,39 @@
+package uk.gov.pay.api.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import uk.gov.pay.api.exception.BadRequestException;
+import uk.gov.pay.api.model.CreatePaymentRefundRequest;
+import uk.gov.pay.api.validation.PaymentRefundRequestValidator;
+
+import java.io.IOException;
+
+import static uk.gov.pay.api.json.RequestJsonParser.parseRefundRequest;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_REFUND_PARSING_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+
+public class CreatePaymentRefundRequestDeserializer extends StdDeserializer {
+
+ private PaymentRefundRequestValidator validator;
+
+ public CreatePaymentRefundRequestDeserializer(PaymentRefundRequestValidator validator) {
+ super(CreatePaymentRefundRequest.class);
+ this.validator = validator;
+ }
+
+ @Override
+ public CreatePaymentRefundRequest deserialize(JsonParser parser, DeserializationContext context) {
+
+ CreatePaymentRefundRequest paymentRefundRequest;
+
+ try {
+ paymentRefundRequest = parseRefundRequest(parser.readValueAsTree());
+ } catch (IOException e) {
+ throw new BadRequestException(aRequestError(CREATE_PAYMENT_REFUND_PARSING_ERROR));
+ }
+
+ validator.validate(paymentRefundRequest);
+ return paymentRefundRequest;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/RequestJsonParser.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/RequestJsonParser.java
new file mode 100644
index 000000000..af0149484
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/RequestJsonParser.java
@@ -0,0 +1,382 @@
+package uk.gov.pay.api.json;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import uk.gov.pay.api.agreement.model.CreateAgreementRequest;
+import uk.gov.pay.api.exception.BadRequestException;
+import uk.gov.pay.api.exception.PaymentValidationException;
+import uk.gov.pay.api.model.CreateAgreementRequestBuilder;
+import uk.gov.pay.api.model.CreateCardPaymentRequest;
+import uk.gov.pay.api.model.CreateCardPaymentRequestBuilder;
+import uk.gov.pay.api.model.CreatePaymentRefundRequest;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.RequestError.Code;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.Source;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
+import static uk.gov.pay.api.agreement.model.CreateAgreementRequest.USER_IDENTIFIER_FIELD;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.AGREEMENT_ID_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.AMOUNT_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.AUTHORISATION_MODE;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.DELAYED_CAPTURE_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.DESCRIPTION_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.EMAIL_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.INTERNAL;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.LANGUAGE_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.METADATA;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.MOTO_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_ADDRESS_CITY_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_ADDRESS_COUNTRY_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_ADDRESS_LINE1_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_ADDRESS_LINE2_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_ADDRESS_POSTCODE_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_BILLING_ADDRESS_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_CARDHOLDER_DETAILS_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.PREFILLED_CARDHOLDER_NAME_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.REFERENCE_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.RETURN_URL_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.SET_UP_AGREEMENT_FIELD_NAME;
+import static uk.gov.pay.api.model.CreateCardPaymentRequest.SOURCE_FIELD_NAME;
+import static uk.gov.pay.api.model.CreatePaymentRefundRequest.REFUND_AMOUNT_AVAILABLE;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_AGREEMENT_MISSING_FIELD_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_AGREEMENT_VALIDATION_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_MISSING_FIELD_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_REFUND_MISSING_FIELD_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_REFUND_VALIDATION_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_UNEXPECTED_FIELD_ERROR;
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_VALIDATION_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+import static uk.gov.service.payments.commons.model.Source.CARD_AGENT_INITIATED_MOTO;
+import static uk.gov.service.payments.commons.model.Source.CARD_API;
+import static uk.gov.service.payments.commons.model.Source.CARD_PAYMENT_LINK;
+
+class RequestJsonParser {
+
+ private static final Set ALLOWED_SOURCES = EnumSet.of(CARD_PAYMENT_LINK, CARD_AGENT_INITIATED_MOTO);
+ public static final Set ALLOWED_AUTHORISATION_MODES = EnumSet.of(AuthorisationMode.WEB, AuthorisationMode.MOTO_API, AuthorisationMode.AGREEMENT);
+
+ private static ObjectMapper objectMapper = new ObjectMapper();
+ private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+
+ static CreatePaymentRefundRequest parseRefundRequest(JsonNode rootNode) {
+ Integer amount = validateAndGetAmount(rootNode, CREATE_PAYMENT_REFUND_VALIDATION_ERROR, CREATE_PAYMENT_REFUND_MISSING_FIELD_ERROR);
+ Integer refundAmountAvailable = rootNode.get(REFUND_AMOUNT_AVAILABLE) == null ? null : rootNode.get(REFUND_AMOUNT_AVAILABLE).asInt();
+ return new CreatePaymentRefundRequest(amount, refundAmountAvailable);
+ }
+
+ static CreateCardPaymentRequest parsePaymentRequest(JsonNode paymentRequest) {
+
+ var builder = CreateCardPaymentRequestBuilder.builder()
+ .amount(validateAndGetAmount(paymentRequest, CREATE_PAYMENT_VALIDATION_ERROR, CREATE_PAYMENT_MISSING_FIELD_ERROR))
+ .reference(validateAndGetPaymentReference(paymentRequest))
+ .description(validateAndGetPaymentDescription(paymentRequest));
+
+ if (paymentRequest.has(RETURN_URL_FIELD_NAME)) {
+ String returnUrl = validateSkipNullValueAndGetString(paymentRequest.get(RETURN_URL_FIELD_NAME),
+ aRequestError(RETURN_URL_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be a valid URL format"));
+ builder.returnUrl(returnUrl);
+ }
+
+ if (paymentRequest.has(MOTO_FIELD_NAME)) {
+ builder.moto(validateAndGetMoto(paymentRequest));
+ }
+
+ if(paymentRequest.has(SET_UP_AGREEMENT_FIELD_NAME)) {
+ builder.setUpAgreement(validateAndGetSetUpAgreement(paymentRequest));
+ }
+
+ AuthorisationMode authorisationMode = null;
+ if (paymentRequest.has(AUTHORISATION_MODE)) {
+ authorisationMode = validateAndGetAuthorisationMode(paymentRequest);
+ builder.authorisationMode(authorisationMode);
+ }
+
+ if (paymentRequest.has(AGREEMENT_ID_FIELD_NAME)) {
+ if (AuthorisationMode.AGREEMENT == authorisationMode) {
+ builder.agreementId(validateAndGetString(
+ paymentRequest.get(AGREEMENT_ID_FIELD_NAME),
+ aRequestError(AGREEMENT_ID_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be a valid string format"),
+ aRequestError(AGREEMENT_ID_FIELD_NAME, CREATE_PAYMENT_MISSING_FIELD_ERROR)));
+ } else {
+ throw new BadRequestException(aRequestError(CREATE_PAYMENT_UNEXPECTED_FIELD_ERROR, AGREEMENT_ID_FIELD_NAME));
+ }
+ }
+
+ if (paymentRequest.has(LANGUAGE_FIELD_NAME)) {
+ builder.language(validateAndGetLanguage(paymentRequest));
+ }
+
+ if (paymentRequest.has(DELAYED_CAPTURE_FIELD_NAME)) {
+ builder.delayedCapture(validateAndGetDelayedCapture(paymentRequest));
+ }
+
+ if (paymentRequest.has(EMAIL_FIELD_NAME)) {
+ String email = validateSkipNullValueAndGetString(paymentRequest.get(EMAIL_FIELD_NAME),
+ aRequestError(EMAIL_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.email(email);
+ }
+
+ if (paymentRequest.has(PREFILLED_CARDHOLDER_DETAILS_FIELD_NAME)) {
+ JsonNode prefilledNode = paymentRequest.get(PREFILLED_CARDHOLDER_DETAILS_FIELD_NAME);
+ validatePrefilledCardholderDetails(prefilledNode, builder);
+ }
+
+ if (paymentRequest.has(METADATA)) {
+ builder.metadata(validateAndGetMetadata(paymentRequest));
+ }
+
+ builder.source(validateAndGetSource(paymentRequest));
+
+ return builder.build();
+ }
+
+ static CreateAgreementRequest parseAgreementRequest(JsonNode agreementRequest) {
+ var builder = CreateAgreementRequestBuilder.builder()
+ .reference(validateAndGetAgreementReference(agreementRequest))
+ .description(validateAndGetAgreementDescription(agreementRequest));
+
+ if (agreementRequest.has(USER_IDENTIFIER_FIELD)) {
+ String userIdentifier = validateSkipNullValueAndGetString(agreementRequest.get(USER_IDENTIFIER_FIELD),
+ aRequestError(USER_IDENTIFIER_FIELD, CREATE_AGREEMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.userIdentifier(userIdentifier);
+ }
+
+ return builder.build();
+ }
+
+ private static SupportedLanguage validateAndGetLanguage(JsonNode paymentRequest) {
+ String errorMessage = "Must be \"en\" or \"cy\"";
+ RequestError requestError = aRequestError(LANGUAGE_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, errorMessage);
+ String language = validateAndGetString(paymentRequest.get(LANGUAGE_FIELD_NAME), requestError, requestError);
+ try {
+ return SupportedLanguage.fromIso639AlphaTwoCode(language);
+ } catch (IllegalArgumentException e) {
+ throw new WebApplicationException(Response.status(SC_UNPROCESSABLE_ENTITY).entity(requestError).build());
+ }
+ }
+
+ private static AuthorisationMode validateAndGetAuthorisationMode(JsonNode paymentRequest) {
+ String errorMessage = "Must be one of " + ALLOWED_AUTHORISATION_MODES.stream()
+ .map(AuthorisationMode::getName)
+ .collect(Collectors.joining(", "));
+ RequestError requestError = aRequestError(AUTHORISATION_MODE, CREATE_PAYMENT_VALIDATION_ERROR, errorMessage);
+ String value = validateAndGetString(paymentRequest.get(AUTHORISATION_MODE), requestError, requestError);
+
+ try {
+ AuthorisationMode authorisationMode = AuthorisationMode.of(value);
+ if (ALLOWED_AUTHORISATION_MODES.contains(authorisationMode)) {
+ return authorisationMode;
+ } else {
+ throw new PaymentValidationException(requestError);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new PaymentValidationException(requestError);
+ }
+ }
+
+ private static String validateAndGetPaymentDescription(JsonNode paymentRequest) {
+ return validateAndGetDescription(paymentRequest, CREATE_PAYMENT_VALIDATION_ERROR, CREATE_PAYMENT_MISSING_FIELD_ERROR);
+ }
+
+ private static String validateAndGetAgreementDescription(JsonNode paymentRequest) {
+ return validateAndGetDescription(paymentRequest, CREATE_AGREEMENT_VALIDATION_ERROR, CREATE_AGREEMENT_MISSING_FIELD_ERROR);
+ }
+
+ private static String validateAndGetDescription(JsonNode request, RequestError.Code validationError, RequestError.Code missingFieldError) {
+ return validateAndGetString(
+ request.get(DESCRIPTION_FIELD_NAME),
+ aRequestError(DESCRIPTION_FIELD_NAME, validationError, "Must be a valid string format"),
+ aRequestError(DESCRIPTION_FIELD_NAME, missingFieldError));
+ }
+
+ private static String validateAndGetPaymentReference(JsonNode paymentRequest) {
+ return validateAndGetReference(paymentRequest, CREATE_PAYMENT_VALIDATION_ERROR, CREATE_PAYMENT_MISSING_FIELD_ERROR);
+ }
+
+ private static String validateAndGetAgreementReference(JsonNode agreementRequest) {
+ return validateAndGetReference(agreementRequest, CREATE_AGREEMENT_VALIDATION_ERROR, CREATE_AGREEMENT_MISSING_FIELD_ERROR);
+ }
+
+ private static String validateAndGetReference(JsonNode request, RequestError.Code validationError, RequestError.Code missingFieldError) {
+ return validateAndGetString(
+ request.get(REFERENCE_FIELD_NAME),
+ aRequestError(REFERENCE_FIELD_NAME, validationError, "Must be a valid string format"),
+ aRequestError(REFERENCE_FIELD_NAME, missingFieldError));
+ }
+
+ private static String validateAndGetString(JsonNode jsonNode, RequestError validationError, RequestError missingError) {
+ String value = validateAndGetValue(jsonNode, validationError, missingError, JsonNode::isTextual, JsonNode::asText);
+ check(isNotBlank(value), missingError);
+ return value;
+ }
+
+ private static Boolean validateAndGetDelayedCapture(JsonNode paymentRequest) {
+ return validateAndGetValue(
+ paymentRequest.get(DELAYED_CAPTURE_FIELD_NAME),
+ aRequestError(DELAYED_CAPTURE_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be true or false"),
+ aRequestError(DELAYED_CAPTURE_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be true or false"),
+ JsonNode::isBoolean,
+ JsonNode::booleanValue);
+ }
+
+ private static Boolean validateAndGetMoto(JsonNode paymentRequest) {
+ return validateAndGetValue(
+ paymentRequest.get(MOTO_FIELD_NAME),
+ aRequestError(MOTO_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be true or false"),
+ aRequestError(MOTO_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Must be true or false"),
+ JsonNode::isBoolean,
+ JsonNode::booleanValue);
+ }
+
+ private static String validateAndGetSetUpAgreement(JsonNode paymentRequest) {
+ return paymentRequest.get(SET_UP_AGREEMENT_FIELD_NAME).textValue();
+ }
+
+ private static Integer validateAndGetAmount(JsonNode paymentRequest, Code validationError, Code missingError) {
+ return validateAndGetValue(
+ paymentRequest.get(AMOUNT_FIELD_NAME),
+ aRequestError(AMOUNT_FIELD_NAME, validationError, "Must be a valid numeric format"),
+ aRequestError(AMOUNT_FIELD_NAME, missingError),
+ JsonNode::isInt,
+ JsonNode::intValue);
+ }
+
+ private static ExternalMetadata validateAndGetMetadata(JsonNode paymentRequest) {
+ Map metadataMap;
+ try {
+ metadataMap = objectMapper.convertValue(paymentRequest.get("metadata"), Map.class);
+ } catch (IllegalArgumentException e) {
+ RequestError requestError = aRequestError(METADATA, CREATE_PAYMENT_VALIDATION_ERROR,
+ "Must be an object of JSON key-value pairs");
+ throw new WebApplicationException(Response.status(SC_UNPROCESSABLE_ENTITY).entity(requestError).build());
+ }
+
+ if (metadataMap == null) {
+ RequestError requestError = aRequestError(METADATA, CREATE_PAYMENT_VALIDATION_ERROR,
+ "Value must not be null");
+ throw new WebApplicationException(Response.status(SC_UNPROCESSABLE_ENTITY).entity(requestError).build());
+ }
+
+ ExternalMetadata metadata = new ExternalMetadata(metadataMap);
+ Set> violations = validator.validate(metadata);
+ if (violations.size() > 0) {
+ String message = violations.stream()
+ .map(ConstraintViolation::getMessage)
+ .map(msg -> msg.replace("Field [metadata] ", ""))
+ .map(StringUtils::capitalize)
+ .collect(Collectors.joining(". "));
+ RequestError requestError = aRequestError(METADATA, CREATE_PAYMENT_VALIDATION_ERROR, message);
+ throw new WebApplicationException(Response.status(SC_UNPROCESSABLE_ENTITY).entity(requestError).build());
+ }
+ return metadata;
+ }
+
+ private static void validatePrefilledCardholderDetails(JsonNode prefilledNode, CreateCardPaymentRequestBuilder builder) {
+ if (prefilledNode.has(PREFILLED_CARDHOLDER_NAME_FIELD_NAME)) {
+ String cardHolderName = validateSkipNullValueAndGetString(prefilledNode.get(PREFILLED_CARDHOLDER_NAME_FIELD_NAME),
+ aRequestError(PREFILLED_CARDHOLDER_NAME_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.cardholderName(cardHolderName);
+ }
+ if (prefilledNode.has(PREFILLED_BILLING_ADDRESS_FIELD_NAME)) {
+ JsonNode addressNode = prefilledNode.get(PREFILLED_BILLING_ADDRESS_FIELD_NAME);
+ if (addressNode.has(PREFILLED_ADDRESS_LINE1_FIELD_NAME)) {
+ String addressLine1 = validateSkipNullValueAndGetString(addressNode.get(PREFILLED_ADDRESS_LINE1_FIELD_NAME),
+ aRequestError(PREFILLED_ADDRESS_LINE1_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.addressLine1(addressLine1);
+ }
+ if (addressNode.has(PREFILLED_ADDRESS_LINE2_FIELD_NAME)) {
+ String addressLine1 = validateSkipNullValueAndGetString(addressNode.get(PREFILLED_ADDRESS_LINE2_FIELD_NAME),
+ aRequestError(PREFILLED_ADDRESS_LINE2_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.addressLine2(addressLine1);
+ }
+ if (addressNode.has(PREFILLED_ADDRESS_CITY_FIELD_NAME)) {
+ String addressCity = validateSkipNullValueAndGetString(addressNode.get(PREFILLED_ADDRESS_CITY_FIELD_NAME),
+ aRequestError(PREFILLED_ADDRESS_CITY_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.city(addressCity);
+ }
+ if (addressNode.has(PREFILLED_ADDRESS_POSTCODE_FIELD_NAME)) {
+ String addressPostcode = validateSkipNullValueAndGetString(addressNode.get(PREFILLED_ADDRESS_POSTCODE_FIELD_NAME),
+ aRequestError(PREFILLED_ADDRESS_POSTCODE_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.postcode(addressPostcode);
+ }
+ if (addressNode.has(PREFILLED_ADDRESS_COUNTRY_FIELD_NAME)) {
+ String countryCode = validateSkipNullValueAndGetString(addressNode.get(PREFILLED_ADDRESS_COUNTRY_FIELD_NAME),
+ aRequestError(PREFILLED_ADDRESS_COUNTRY_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, "Field must be a string"));
+ builder.country(countryCode);
+ }
+ }
+ }
+
+ private static Source validateAndGetSource(JsonNode paymentRequest) {
+ if (paymentRequest.has(INTERNAL)) {
+ JsonNode internalNode = paymentRequest.get(INTERNAL);
+
+ if (internalNode.has(SOURCE_FIELD_NAME)) {
+ String errorMessage = "Accepted values are only CARD_PAYMENT_LINK, CARD_AGENT_INITIATED_MOTO";
+ RequestError requestError = aRequestError(SOURCE_FIELD_NAME, CREATE_PAYMENT_VALIDATION_ERROR, errorMessage);
+ String sourceString = validateSkipNullValueAndGetString(internalNode.get(SOURCE_FIELD_NAME), requestError);
+
+ try {
+ Source source = Source.valueOf(sourceString);
+ if (ALLOWED_SOURCES.contains(source)) {
+ return source;
+ }
+ throw new BadRequestException(requestError);
+ } catch (IllegalArgumentException e) {
+ throw new WebApplicationException(Response.status(SC_UNPROCESSABLE_ENTITY).entity(requestError).build());
+ }
+ }
+ }
+
+ return CARD_API;
+ }
+
+ private static T validateAndGetValue(JsonNode jsonNode,
+ RequestError validationError,
+ RequestError missingError,
+ Function isExpectedType,
+ Function valueFromJsonNode) {
+ if (jsonNode != null && !jsonNode.isNull()) {
+ check(isExpectedType.apply(jsonNode), validationError);
+ return valueFromJsonNode.apply(jsonNode);
+ }
+ throw new BadRequestException(missingError);
+ }
+
+ private static String validateSkipNullValueAndGetString(JsonNode jsonNode, RequestError validationError) {
+ return validateSkipNullAndGetValue(jsonNode, validationError, JsonNode::isTextual, JsonNode::asText);
+ }
+
+ private static T validateSkipNullAndGetValue(JsonNode jsonNode,
+ RequestError validationError,
+ Function isExpectedType,
+ Function valueFromJsonNode) {
+ if (jsonNode == null || jsonNode.isNull()) {
+ return null;
+ }
+ check(isExpectedType.apply(jsonNode), validationError);
+ return valueFromJsonNode.apply(jsonNode);
+ }
+
+ private static void check(boolean condition, RequestError error) {
+ if (!condition) {
+ throw new BadRequestException(error);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/StringDeserializer.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/StringDeserializer.java
new file mode 100644
index 000000000..462a4ce2b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/json/StringDeserializer.java
@@ -0,0 +1,28 @@
+package uk.gov.pay.api.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import uk.gov.pay.api.exception.PaymentValidationException;
+
+import java.io.IOException;
+
+import static uk.gov.pay.api.model.RequestError.Code.CREATE_PAYMENT_VALIDATION_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+
+public class StringDeserializer extends StdDeserializer {
+
+ public StringDeserializer() {
+ super(String.class);
+ }
+
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return p.getText();
+ }
+ var requestError = aRequestError(p.getCurrentName(), CREATE_PAYMENT_VALIDATION_ERROR, "Must be of type String");
+ throw new PaymentValidationException(requestError);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/AgreementSearchParams.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/AgreementSearchParams.java
new file mode 100644
index 000000000..219db71a7
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/AgreementSearchParams.java
@@ -0,0 +1,97 @@
+package uk.gov.pay.api.ledger.model;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.ws.rs.QueryParam;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static uk.gov.pay.api.common.SearchConstants.DISPLAY_SIZE;
+import static uk.gov.pay.api.common.SearchConstants.PAGE;
+import static uk.gov.pay.api.common.SearchConstants.REFERENCE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.STATUS_KEY;
+
+public class AgreementSearchParams {
+ @QueryParam("reference")
+ @Parameter(name = "reference", description = "Returns agreements with a `reference` that exactly matches the value you sent. " +
+ "This parameter is not case sensitive. " +
+ "A `reference` was associated with the agreement when that agreement was created.",
+ example = "CT-22-23-0001")
+ private String reference;
+
+ @QueryParam("status")
+ @Parameter(name = "status", description = "Returns agreements in a matching `status`. " +
+ "`status` reflects where an agreement is in its lifecycle. " +
+ "You can [read more about the meanings of the different agreement status values]" +
+ "(https://docs.payments.service.gov.uk/recurring_payments/#understanding-agreement-status).",
+ schema = @Schema(allowableValues = {"created", "active", "cancelled", "inactive"}))
+ private String status;
+
+ @QueryParam("page")
+ @Parameter(name = "page",
+ description = "Returns a specific page of results. Defaults to `1`. " +
+ "You can [read about search pagination](https://docs.payments.service.gov.uk/api_reference/#pagination)",
+ example = "1")
+ private String pageNumber;
+
+ @QueryParam("display_size")
+ @Parameter(name = "display_size",
+ description = "The number of agreements returned per results page. Defaults to `500`. " +
+ "Maximum value is `500`. You can [read about search pagination](https://docs.payments.service.gov.uk/api_reference/#pagination)",
+ example = "50")
+ private String displaySize;
+
+ public AgreementSearchParams() {
+ // Framework gubbins
+ }
+
+ public AgreementSearchParams(String reference, String status, String pageNumber, String displaySize) {
+ this.reference = reference;
+ this.status = status;
+ this.pageNumber = pageNumber;
+ this.displaySize = displaySize;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public void setReference(String reference) {
+ this.reference = reference;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getPageNumber() {
+ return pageNumber;
+ }
+
+ public void setPageNumber(String pageNumber) {
+ this.pageNumber = pageNumber;
+ }
+
+ public String getDisplaySize() {
+ return displaySize;
+ }
+
+ public void setDisplaySize(String displaySize) {
+ this.displaySize = displaySize;
+ }
+
+ public Map getQueryMap() {
+ var queryParams = new HashMap();
+ Optional.ofNullable(reference).ifPresent(reference -> queryParams.put(REFERENCE_KEY, reference));
+ Optional.ofNullable(status).ifPresent(status -> queryParams.put(STATUS_KEY, status));
+ Optional.ofNullable(pageNumber).ifPresent(pageNumber -> queryParams.put(PAGE, pageNumber));
+ Optional.ofNullable(displaySize).ifPresent(displaySize -> queryParams.put(DISPLAY_SIZE, displaySize));
+ return Map.copyOf(queryParams);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/SearchResults.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/SearchResults.java
new file mode 100644
index 000000000..487bb18a7
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/SearchResults.java
@@ -0,0 +1,58 @@
+package uk.gov.pay.api.ledger.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class SearchResults implements SearchPagination {
+
+ private int total;
+ private int count;
+ private int page;
+ private List results;
+ SearchNavigationLinks links;
+
+ public SearchResults() {
+ }
+
+ public SearchResults(int total, int count, int page, List results,
+ SearchNavigationLinks links) {
+ this.total = total;
+ this.count = count;
+ this.page = page;
+ this.results = results;
+ this.links = links;
+ }
+
+ @Override
+ public int getTotal() {
+ return total;
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+
+ public int getPage() {
+ return page;
+ }
+
+ @JsonProperty("results")
+ public List getResults() {
+ return results;
+ }
+
+ @Override
+ @JsonProperty("_links")
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/TransactionSearchParams.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/TransactionSearchParams.java
new file mode 100644
index 000000000..d8fac11ac
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/model/TransactionSearchParams.java
@@ -0,0 +1,127 @@
+package uk.gov.pay.api.ledger.model;
+
+import javax.ws.rs.QueryParam;
+import java.util.HashMap;
+import java.util.Map;
+
+import static uk.gov.pay.api.common.SearchConstants.CARDHOLDER_NAME_KEY;
+import static uk.gov.pay.api.common.SearchConstants.CARD_BRAND_KEY;
+import static uk.gov.pay.api.common.SearchConstants.DISPLAY_SIZE;
+import static uk.gov.pay.api.common.SearchConstants.EMAIL_KEY;
+import static uk.gov.pay.api.common.SearchConstants.FIRST_DIGITS_CARD_NUMBER_KEY;
+import static uk.gov.pay.api.common.SearchConstants.FROM_DATE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.FROM_SETTLED_DATE;
+import static uk.gov.pay.api.common.SearchConstants.GATEWAY_ACCOUNT_ID;
+import static uk.gov.pay.api.common.SearchConstants.LAST_DIGITS_CARD_NUMBER_KEY;
+import static uk.gov.pay.api.common.SearchConstants.PAGE;
+import static uk.gov.pay.api.common.SearchConstants.REFERENCE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.STATE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.TO_DATE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.TO_SETTLED_DATE;
+
+public class TransactionSearchParams {
+
+ private String accountId;
+ @QueryParam("reference")
+ private String reference;
+ @QueryParam("email")
+ private String email;
+ @QueryParam("state")
+ private String state;
+ @QueryParam("card_brand")
+ private String cardBrand;
+ @QueryParam("from_date")
+ private String fromDate;
+ @QueryParam("to_date")
+ private String toDate;
+ @QueryParam("page")
+ private String pageNumber;
+ @QueryParam("display_size")
+ private String displaySize;
+ @QueryParam("cardholder_name")
+ private String cardHolderName;
+ @QueryParam("first_digits_card_number")
+ private String firstDigitsCardNumber;
+ @QueryParam("last_digits_card_number")
+ private String lastDigitsCardNumber;
+ @QueryParam("from_settled_date")
+ private String fromSettledDate;
+ @QueryParam("to_settled_date")
+ private String toSettledDate;
+
+ public void setAccountId(String accountId) {
+ this.accountId = accountId;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public String getCardBrand() {
+ return cardBrand;
+ }
+
+ public String getFromDate() {
+ return fromDate;
+ }
+
+ public String getToDate() {
+ return toDate;
+ }
+
+ public String getPageNumber() {
+ return pageNumber;
+ }
+
+ public String getDisplaySize() {
+ return displaySize;
+ }
+
+ public String getCardHolderName() {
+ return cardHolderName;
+ }
+
+ public String getFirstDigitsCardNumber() {
+ return firstDigitsCardNumber;
+ }
+
+ public String getLastDigitsCardNumber() {
+ return lastDigitsCardNumber;
+ }
+
+ public String getFromSettledDate() {
+ return fromSettledDate;
+ }
+
+ public String getToSettledDate() {
+ return toSettledDate;
+ }
+
+ public Map getQueryMap() {
+ Map queryParams = new HashMap<>();
+ queryParams.put(GATEWAY_ACCOUNT_ID, accountId);
+ queryParams.put(REFERENCE_KEY, reference);
+ queryParams.put(EMAIL_KEY, email);
+ queryParams.put(STATE_KEY, state);
+ queryParams.put(CARD_BRAND_KEY, cardBrand);
+ queryParams.put(CARDHOLDER_NAME_KEY, cardHolderName);
+ queryParams.put(FIRST_DIGITS_CARD_NUMBER_KEY, firstDigitsCardNumber);
+ queryParams.put(LAST_DIGITS_CARD_NUMBER_KEY, lastDigitsCardNumber);
+ queryParams.put(FROM_DATE_KEY, fromDate);
+ queryParams.put(TO_DATE_KEY, toDate);
+ queryParams.put(PAGE, pageNumber);
+ queryParams.put(DISPLAY_SIZE, displaySize);
+ queryParams.put(FROM_SETTLED_DATE, fromSettledDate);
+ queryParams.put(TO_SETTLED_DATE, toSettledDate);
+
+ return queryParams;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/resource/TransactionsResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/resource/TransactionsResource.java
new file mode 100644
index 000000000..51aea9032
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/resource/TransactionsResource.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.ledger.resource;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.ledger.model.SearchResults;
+import uk.gov.pay.api.ledger.model.TransactionSearchParams;
+import uk.gov.pay.api.ledger.service.TransactionSearchService;
+import uk.gov.pay.api.model.search.card.PaymentForSearchResult;
+
+import javax.inject.Inject;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path("/")
+@Produces({"application/json"})
+public class TransactionsResource {
+
+ private final TransactionSearchService transactionSearchService;
+
+ @Inject
+ public TransactionsResource(TransactionSearchService transactionSearchService) {
+ this.transactionSearchService = transactionSearchService;
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/transactions")
+ @Produces(APPLICATION_JSON)
+ public SearchResults getTransactions(@Auth Account account, @BeanParam TransactionSearchParams searchParams) {
+ return transactionSearchService.doSearch(account, searchParams);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/LedgerUriGenerator.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/LedgerUriGenerator.java
new file mode 100644
index 000000000..d68e7f168
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/LedgerUriGenerator.java
@@ -0,0 +1,79 @@
+package uk.gov.pay.api.ledger.service;
+
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.UriBuilder;
+import java.util.Map;
+
+import static java.lang.String.format;
+
+public class LedgerUriGenerator {
+ private final PublicApiConfig configuration;
+
+ @Inject
+ public LedgerUriGenerator(PublicApiConfig configuration) {
+ this.configuration = configuration;
+ }
+
+ public String transactionsURIWithParams(Map queryParams) {
+ queryParams.put("status_version", "1");
+ return buildLedgerUri("/v1/transaction", queryParams);
+ }
+
+ private String buildLedgerUri(String path, Map params) {
+ var ledgerUrl = configuration.getLedgerUrl();
+ UriBuilder builder = UriBuilder.fromPath(ledgerUrl).path(path);
+ params.entrySet().stream()
+ .filter(k -> k.getValue() != null)
+ .forEach(k -> builder.queryParam(k.getKey(), k.getValue()));
+ return builder.toString();
+ }
+
+ public String transactionURI(Account gatewayAccountId, String paymentId, String transactionType) {
+ String path = format("/v1/transaction/%s", paymentId);
+ return buildLedgerUri(path, Map.of(
+ "account_id", gatewayAccountId.getAccountId(),
+ "transaction_type", transactionType,
+ "status_version", "1"
+ ));
+ }
+
+ public String transactionURI(Account gatewayAccountId, String refundId, String transactionType, String paymentId) {
+ String path = format("/v1/transaction/%s", refundId);
+ return buildLedgerUri(path, Map.of(
+ "account_id", gatewayAccountId.getAccountId(),
+ "transaction_type", transactionType,
+ "parent_external_id", paymentId,
+ "status_version", "1")
+ );
+ }
+
+ public String transactionEventsURI(Account gatewayAccountId, String paymentId) {
+ String path = format("/v1/transaction/%s/event", paymentId);
+ return buildLedgerUri(path, Map.of(
+ "gateway_account_id", gatewayAccountId.getAccountId(),
+ "status_version", "1")
+ );
+ }
+
+ public String transactionsForTransactionURI(String gatewayAccountId, String paymentId, String transactionType) {
+ String path = format("/v1/transaction/%s/transaction", paymentId);
+ return buildLedgerUri(path,
+ Map.of("gateway_account_id", gatewayAccountId,
+ "transaction_type", transactionType)
+ );
+ }
+
+ public String agreementURI(Account account, String agreementId) {
+ String path = format("/v1/agreement/%s", agreementId);
+ return buildLedgerUri(path, Map.of(
+ "account_id", account.getAccountId()
+ ));
+ }
+
+ public String agreementsURIWithParams(Map queryParams) {
+ return buildLedgerUri("/v1/agreement", queryParams);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/TransactionSearchService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/TransactionSearchService.java
new file mode 100644
index 000000000..6c2ed40d4
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/ledger/service/TransactionSearchService.java
@@ -0,0 +1,169 @@
+package uk.gov.pay.api.ledger.service;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.BadRequestException;
+import uk.gov.pay.api.exception.SearchPaymentsException;
+import uk.gov.pay.api.exception.SearchTransactionsException;
+import uk.gov.pay.api.ledger.model.SearchResults;
+import uk.gov.pay.api.ledger.model.TransactionSearchParams;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.TransactionResponse;
+import uk.gov.pay.api.model.links.Link;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.card.PaymentForSearchResult;
+import uk.gov.pay.api.model.search.card.PaymentSearchResponse;
+import uk.gov.pay.api.service.PaymentUriGenerator;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.stream.Collectors.toList;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.http.HttpStatus.SC_OK;
+import static uk.gov.pay.api.common.SearchConstants.CARDHOLDER_NAME_KEY;
+import static uk.gov.pay.api.common.SearchConstants.CARD_BRAND_KEY;
+import static uk.gov.pay.api.common.SearchConstants.DISPLAY_SIZE;
+import static uk.gov.pay.api.common.SearchConstants.EMAIL_KEY;
+import static uk.gov.pay.api.common.SearchConstants.FIRST_DIGITS_CARD_NUMBER_KEY;
+import static uk.gov.pay.api.common.SearchConstants.FROM_DATE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.LAST_DIGITS_CARD_NUMBER_KEY;
+import static uk.gov.pay.api.common.SearchConstants.PAGE;
+import static uk.gov.pay.api.common.SearchConstants.REFERENCE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.STATE_KEY;
+import static uk.gov.pay.api.common.SearchConstants.TO_DATE_KEY;
+import static uk.gov.pay.api.validation.PaymentSearchValidator.validateSearchParameters;
+
+public class TransactionSearchService {
+
+ private final String baseUrl;
+ private final PaymentUriGenerator paymentApiUriGenerator;
+ private final Client client;
+ private final LedgerUriGenerator ledgerUriGenerator;
+ private final Logger LOGGER = LoggerFactory.getLogger(TransactionSearchService.class);
+
+ @Inject
+ public TransactionSearchService(Client client,
+ PublicApiConfig configuration,
+ LedgerUriGenerator ledgerUriGenerator,
+ PaymentUriGenerator paymentApiUriGenerator) {
+ this.client = client;
+ this.ledgerUriGenerator = ledgerUriGenerator;
+ this.paymentApiUriGenerator = paymentApiUriGenerator;
+ this.baseUrl = configuration.getBaseUrl();
+ }
+
+ public SearchResults doSearch(Account account, TransactionSearchParams searchParams) {
+ validateSearchParameters(searchParams.getState(), searchParams.getReference(),
+ searchParams.getEmail(), searchParams.getCardBrand(), searchParams.getFromDate(),
+ searchParams.getToDate(), searchParams.getPageNumber(), searchParams.getDisplaySize(),
+ searchParams.getFirstDigitsCardNumber(), searchParams.getLastDigitsCardNumber(),
+ searchParams.getFromSettledDate(), searchParams.getToSettledDate());
+ validateSupportedSearchParams(searchParams.getQueryMap());
+
+ searchParams.setAccountId(account.getAccountId());
+ String url = ledgerUriGenerator.transactionsURIWithParams(searchParams.getQueryMap());
+ Response ledgerResponse = client
+ .target(url)
+ .request()
+ .header(HttpHeaders.ACCEPT, APPLICATION_JSON)
+ .get();
+ LOGGER.info("response from ledger for transaction search: {}", ledgerResponse);
+ if (ledgerResponse.getStatus() == SC_OK) {
+ return processResponse(ledgerResponse);
+ }
+ throw new SearchTransactionsException(ledgerResponse);
+ }
+
+ private SearchResults processResponse(Response connectorResponse) {
+ PaymentSearchResponse response;
+ try {
+ response = connectorResponse.readEntity(new GenericType<>() {
+ });
+ } catch (ProcessingException ex) {
+ throw new SearchTransactionsException(ex);
+ }
+
+ List chargeFromResponses = response.getPayments()
+ .stream()
+ .map(this::getPaymentForSearchResult)
+ .collect(toList());
+
+ return new SearchResults<>(
+ response.getTotal(),
+ response.getCount(),
+ response.getPage(),
+ chargeFromResponses,
+ transformLinks(response.getLinks())
+ );
+ }
+
+ private PaymentForSearchResult getPaymentForSearchResult(TransactionResponse transactionResponse) {
+ return PaymentForSearchResult.valueOf(
+ transactionResponse,
+ paymentApiUriGenerator.getPaymentURI(baseUrl, transactionResponse.getTransactionId()),
+ paymentApiUriGenerator.getPaymentEventsURI(baseUrl, transactionResponse.getTransactionId()),
+ paymentApiUriGenerator.getPaymentCancelURI(baseUrl, transactionResponse.getTransactionId()),
+ paymentApiUriGenerator.getPaymentRefundsURI(baseUrl, transactionResponse.getTransactionId()),
+ paymentApiUriGenerator.getPaymentCaptureURI(baseUrl, transactionResponse.getTransactionId()));
+ }
+
+ private SearchNavigationLinks transformLinks(SearchNavigationLinks links) {
+ final String path = "/v1/transactions";
+
+ try {
+ return new SearchNavigationLinks()
+ .withSelfLink(transformIntoPublicUri(baseUrl, links.getSelf(), path))
+ .withFirstLink(transformIntoPublicUri(baseUrl, links.getFirstPage(), path))
+ .withLastLink(transformIntoPublicUri(baseUrl, links.getLastPage(), path))
+ .withPrevLink(transformIntoPublicUri(baseUrl, links.getPrevPage(), path))
+ .withNextLink(transformIntoPublicUri(baseUrl, links.getNextPage(), path));
+ } catch (URISyntaxException ex) {
+ throw new SearchPaymentsException(ex);
+ }
+ }
+
+ private String transformIntoPublicUri(String baseUrl, Link link, String path) throws URISyntaxException {
+ if (link == null)
+ return null;
+
+ return UriBuilder.fromUri(baseUrl)
+ .path(path)
+ .replaceQuery(new URI(link.getHref()).getQuery())
+ .replaceQueryParam("account_id", (Object[]) null)
+ .build()
+ .toString();
+ }
+
+ private void validateSupportedSearchParams(Map queryParams) {
+ queryParams.entrySet().stream()
+ .filter(this::isUnsupportedParamWithNonBlankValue)
+ .findFirst()
+ .ifPresent(invalidParam -> {
+ throw new BadRequestException(RequestError
+ .aRequestError(RequestError.Code.SEARCH_PAYMENTS_VALIDATION_ERROR, invalidParam.getKey()));
+ });
+ }
+
+ private boolean isUnsupportedParamWithNonBlankValue(Map.Entry queryParam) {
+ return !getSupportedSearchParams().contains(queryParam.getKey()) && isNotBlank(queryParam.getValue());
+ }
+
+ private Set getSupportedSearchParams() {
+ return ImmutableSet.of(REFERENCE_KEY, EMAIL_KEY, STATE_KEY, CARD_BRAND_KEY, CARDHOLDER_NAME_KEY, FIRST_DIGITS_CARD_NUMBER_KEY, LAST_DIGITS_CARD_NUMBER_KEY, FROM_DATE_KEY, TO_DATE_KEY, PAGE, DISPLAY_SIZE);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/managed/RedisClientManager.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/managed/RedisClientManager.java
new file mode 100644
index 000000000..31b7892f2
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/managed/RedisClientManager.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.managed;
+
+import io.dropwizard.lifecycle.Managed;
+import io.lettuce.core.RedisClient;
+import io.lettuce.core.api.StatefulRedisConnection;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class RedisClientManager implements Managed {
+ private RedisClient redisClient;
+ private StatefulRedisConnection statefulRedisConnection;
+
+ @Inject
+ public RedisClientManager(RedisClient redisClient) {
+ this.redisClient = redisClient;
+ }
+
+ public StatefulRedisConnection getRedisConnection() {
+ if (statefulRedisConnection == null) {
+ statefulRedisConnection = redisClient.connect();
+ }
+ return statefulRedisConnection;
+ }
+
+ @Override
+ public void start() throws Exception {}
+
+ @Override
+ public void stop() throws Exception {
+ if (statefulRedisConnection != null) {
+ statefulRedisConnection.close();
+ }
+ redisClient.shutdown();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Address.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Address.java
new file mode 100644
index 000000000..5e1dc0c08
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Address.java
@@ -0,0 +1,85 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.validation.constraints.Size;
+import java.util.Objects;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(NON_NULL)
+@Schema(name = "Address", description = "A structure representing the billing address of a card")
+public class Address {
+
+ @Size(max = 255, message = "Must be less than or equal to {max} characters length")
+ private String line1;
+
+ @Size(max = 255, message = "Must be less than or equal to {max} characters length")
+ private String line2;
+
+ @Size(max = 25, message = "Must be less than or equal to {max} characters length")
+ private String postcode;
+
+ @Size(max = 255, message = "Must be less than or equal to {max} characters length")
+ private String city;
+
+ private String country;
+
+ public Address(@JsonProperty("line1") String line1,
+ @JsonProperty("line2") String line2,
+ @JsonProperty("postcode") String postcode,
+ @JsonProperty("city") String city,
+ @JsonProperty("country") String country) {
+ this.line1 = line1;
+ this.line2 = line2;
+ this.postcode = postcode;
+ this.city = city;
+ this.country = country;
+ }
+
+ @Schema(example = "address line 1", description = "The first line of the paying user’s address.")
+ public String getLine1() {
+ return line1;
+ }
+
+ @Schema(example = "address line 2", description = "The second line of the paying user’s address.")
+ public String getLine2() {
+ return line2;
+ }
+
+ @Schema(example = "AB1 2CD", description = "The paying user's postcode.")
+ public String getPostcode() {
+ return postcode;
+ }
+
+ @Schema(example = "address city", description="The paying user's city.")
+ public String getCity() {
+ return city;
+ }
+
+ @Schema(example = "GB", description = "The paying user’s country, displayed as a 2-character ISO-3166-1-alpha-2 code.")
+ public String getCountry() {
+ return country;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Address address = (Address) o;
+ return Objects.equals(line1, address.line1) &&
+ Objects.equals(line2, address.line2) &&
+ Objects.equals(postcode, address.postcode) &&
+ Objects.equals(city, address.city) &&
+ Objects.equals(country, address.country);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(line1, line2, postcode, city, country);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationRequest.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationRequest.java
new file mode 100644
index 000000000..2fb706ec7
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationRequest.java
@@ -0,0 +1,66 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+@Schema(name = "AuthorisationRequest", description = "Contains the user's payment information. This information will be sent to the payment service provider to authorise the payment.")
+public class AuthorisationRequest {
+ @JsonProperty("one_time_token")
+ @NotBlank
+ private String oneTimeToken;
+ @JsonProperty("card_number")
+ @NotNull
+ @Size(min = 12, max = 19, message = "Must be between 12 and 19 characters long")
+ private String cardNumber;
+ @JsonProperty("cvc")
+ @NotNull
+ @Size(min = 3, max = 4, message = "Must be between 3 and 4 characters long")
+ private String cvc;
+ @JsonProperty("expiry_date")
+ @NotNull
+ @Size(min = 5, max = 5, message = "Must be a valid date with the format MM/YY")
+ private String expiryDate;
+ @JsonProperty("cardholder_name")
+ @NotBlank
+ @Size(max = 255, message = "Must be less than or equal to {max} characters long")
+ private String cardholderName;
+
+ public AuthorisationRequest() {
+ }
+
+ public AuthorisationRequest(String oneTimeToken, String cardNumber, String cvc, String expiryDate, String cardholderName) {
+ this.oneTimeToken = oneTimeToken;
+ this.cardNumber = cardNumber;
+ this.cvc = cvc;
+ this.expiryDate = expiryDate;
+ this.cardholderName = cardholderName;
+ }
+
+ @Schema(description = "This single use token authorises your request and matches it to a payment. GOV.UK Pay generated the `one_time_token` when the payment was created.", required = true, example = "12345-edsfr-6789-gtyu")
+ public String getOneTimeToken() {
+ return oneTimeToken;
+ }
+
+ @Schema(description = "The full card number from the paying user's card.", required = true, minLength = 12, maxLength = 19, example = "4242424242424242")
+ public String getCardNumber() {
+ return cardNumber;
+ }
+
+ @Schema(description = "The card verification code (CVC) or card verification value (CVV) on the paying user's card.", required = true, minLength = 3, maxLength = 4, example = "123")
+ public String getCvc() {
+ return cvc;
+ }
+
+ @Schema(description = "The expiry date of the paying user's card. This value must be in `MM/YY` format.", required = true, minLength = 5, maxLength = 5, example = "09/22")
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ @Schema(description = "The name on the paying user's card.", required = true, maxLength = 255, example = "J. Citizen")
+ public String getCardholderName() {
+ return cardholderName;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationSummary.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationSummary.java
new file mode 100644
index 000000000..4a317e181
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/AuthorisationSummary.java
@@ -0,0 +1,26 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(name = "AuthorisationSummary", description = "Object containing information about the authentication of the payment.")
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AuthorisationSummary {
+
+ @JsonProperty("three_d_secure")
+ private ThreeDSecure threeDSecure;
+
+ public AuthorisationSummary() {
+ }
+
+ public AuthorisationSummary(ThreeDSecure threeDSecure) {
+ this.threeDSecure = threeDSecure;
+ }
+
+ public ThreeDSecure getThreeDSecure() {
+ return threeDSecure;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetails.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetails.java
new file mode 100644
index 000000000..961f9c359
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetails.java
@@ -0,0 +1,118 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Optional;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.ALWAYS;
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(ALWAYS)
+@Schema(name = "CardDetails", description = "A structure representing the payment card")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CardDetails {
+
+ @JsonProperty("last_digits_card_number")
+ private final String lastDigitsCardNumber;
+ @JsonProperty("first_digits_card_number")
+ private final String firstDigitsCardNumber;
+ @JsonProperty("cardholder_name")
+ private final String cardHolderName;
+ @JsonProperty("expiry_date")
+ private final String expiryDate;
+ @JsonProperty("billing_address")
+ private final Address billingAddress;
+ @JsonProperty("card_brand")
+ private final String cardBrand;
+ @JsonProperty("card_type")
+ private final String cardType;
+ @JsonProperty("wallet_type")
+ private String walletType;
+
+ public CardDetails(String lastDigitsCardNumber,
+ String firstDigitsCardNumber,
+ String cardHolderName,
+ String expiryDate,
+ Address billingAddress,
+ String cardBrand,
+ String cardType,
+ String walletType
+ ) {
+ this.lastDigitsCardNumber = lastDigitsCardNumber;
+ this.firstDigitsCardNumber = firstDigitsCardNumber;
+ this.cardHolderName = cardHolderName;
+ this.expiryDate = expiryDate;
+ this.billingAddress = billingAddress;
+ this.cardBrand = cardBrand;
+ this.cardType = cardType;
+ this.walletType = walletType;
+ }
+
+ public static CardDetails from(CardDetailsFromResponse cardDetailsFromResponse, String walletType) {
+ if (cardDetailsFromResponse == null) {
+ return null;
+ } else {
+ return new CardDetails(
+ cardDetailsFromResponse.getLastDigitsCardNumber(),
+ cardDetailsFromResponse.getFirstDigitsCardNumber(),
+ cardDetailsFromResponse.getCardHolderName(),
+ cardDetailsFromResponse.getExpiryDate(),
+ cardDetailsFromResponse.getBillingAddress().orElse(null),
+ cardDetailsFromResponse.getCardBrand(),
+ cardDetailsFromResponse.getCardType(),
+ walletType
+ );
+ }
+
+ }
+
+
+ @Schema(example = "1234", accessMode = READ_ONLY)
+ public String getLastDigitsCardNumber() {
+ return lastDigitsCardNumber;
+ }
+
+ @Schema(example = "123456", accessMode = READ_ONLY)
+ public String getFirstDigitsCardNumber() {
+ return firstDigitsCardNumber;
+ }
+
+ @Schema(example = "Mr. Card holder")
+ public String getCardHolderName() {
+ return cardHolderName;
+ }
+
+ @Schema(description = "The expiry date of the card the user paid with in `MM/YY` format.", example = "04/24", accessMode = READ_ONLY)
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ public Optional getBillingAddress() {
+ return Optional.ofNullable(billingAddress);
+ }
+
+ @Schema(example = "Visa", description = "The brand of card the user paid with.", accessMode = READ_ONLY)
+ public String getCardBrand() {
+ return cardBrand;
+ }
+
+ @Schema(description = "The type of card the user paid with." +
+ "`null` means your user paid with Google Pay or " +
+ "we did not recognise which type of card they paid with.",
+ allowableValues = {"debit", "credit", "null"}, example = "debit", accessMode = READ_ONLY)
+ public String getCardType() {
+ return cardType;
+ }
+
+ public void setWalletType(String walletType) {
+ this.walletType = walletType;
+ }
+
+ @Schema(example = "Apple Pay", description = "The digital wallet type that the user paid with", allowableValues = {"Apple Pay", "Google Pay"})
+ public Optional getWalletType() {
+ return Optional.ofNullable(walletType);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetailsFromResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetailsFromResponse.java
new file mode 100644
index 000000000..82d0e7e5b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardDetailsFromResponse.java
@@ -0,0 +1,80 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Optional;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.ALWAYS;
+
+@JsonInclude(ALWAYS)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CardDetailsFromResponse {
+
+ @JsonProperty("last_digits_card_number")
+ private final String lastDigitsCardNumber;
+
+ @JsonProperty("first_digits_card_number")
+ private final String firstDigitsCardNumber;
+
+ @JsonProperty("cardholder_name")
+ private final String cardHolderName;
+
+ @JsonProperty("expiry_date")
+ private final String expiryDate;
+
+ @JsonProperty("billing_address")
+ private final Address billingAddress;
+
+ @JsonProperty("card_brand")
+ private final String cardBrand;
+
+ @JsonProperty("card_type")
+ private final String cardType;
+
+ public CardDetailsFromResponse(@JsonProperty("last_digits_card_number") String lastDigitsCardNumber,
+ @JsonProperty("first_digits_card_number") String firstDigitsCardNumber,
+ @JsonProperty("cardholder_name") String cardHolderName,
+ @JsonProperty("expiry_date") String expiryDate,
+ @JsonProperty("billing_address") Address billingAddress,
+ @JsonProperty("card_brand") String cardBrand,
+ @JsonProperty("card_type") String cardType) {
+ this.lastDigitsCardNumber = lastDigitsCardNumber;
+ this.firstDigitsCardNumber = firstDigitsCardNumber;
+ this.cardHolderName = cardHolderName;
+ this.expiryDate = expiryDate;
+ this.billingAddress = billingAddress;
+ this.cardBrand = cardBrand;
+ this.cardType = cardType;
+ }
+
+ public String getLastDigitsCardNumber() {
+ return lastDigitsCardNumber;
+ }
+
+ public String getFirstDigitsCardNumber() {
+ return firstDigitsCardNumber;
+ }
+
+ public String getCardHolderName() {
+ return cardHolderName;
+ }
+
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ public Optional getBillingAddress() {
+ return Optional.ofNullable(billingAddress);
+ }
+
+ public String getCardBrand() {
+ return cardBrand;
+ }
+
+
+ public String getCardType() {
+ return cardType;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardPayment.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardPayment.java
new file mode 100644
index 000000000..194b49355
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CardPayment.java
@@ -0,0 +1,311 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.service.payments.commons.api.json.ExternalMetadataSerialiser;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.util.Optional;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
+@Schema(name = "CardPayment")
+public class CardPayment {
+
+ public static final String LINKS_JSON_ATTRIBUTE = "_links";
+
+ @JsonProperty("payment_id")
+ protected String paymentId;
+
+ @JsonProperty("payment_provider")
+ protected String paymentProvider;
+
+ protected long amount;
+
+ protected String description;
+
+ protected String reference;
+
+ @JsonProperty("created_date")
+ protected String createdDate;
+ @JsonProperty("refund_summary")
+ private RefundSummary refundSummary;
+
+ @JsonProperty("settlement_summary")
+ private PaymentSettlementSummary settlementSummary;
+
+ @JsonProperty("card_details")
+ private CardDetails cardDetails;
+
+ @JsonSerialize(using = ToStringSerializer.class)
+ private SupportedLanguage language;
+
+ @JsonProperty("delayed_capture")
+ private boolean delayedCapture;
+
+ @JsonProperty("moto")
+ private boolean moto;
+
+ @JsonProperty("corporate_card_surcharge")
+ private Long corporateCardSurcharge;
+
+ @JsonProperty("total_amount")
+ private Long totalAmount;
+
+ @JsonProperty("fee")
+ private Long fee;
+
+ @JsonProperty("net_amount")
+ private Long netAmount;
+
+ @JsonProperty("provider_id")
+ @Schema(example = "reference-from-payment-gateway",
+ description = "The unique ID your payment service provider generated for this payment. " +
+ "This is not the same as the `payment_id`.",
+ accessMode = READ_ONLY)
+ private String providerId;
+
+ @JsonSerialize(using = ExternalMetadataSerialiser.class)
+ @Schema(name = "metadata", example = "{\"property1\": \"value1\", \"property2\": \"value2\"}\"")
+ private ExternalMetadata metadata;
+
+ @JsonProperty("return_url")
+ protected String returnUrl;
+
+ protected String email;
+
+ protected PaymentState state;
+
+ //Used by Swagger to document the right model in the PaymentsResource
+ @JsonIgnore
+ protected String paymentType;
+
+ @JsonProperty("authorisation_summary")
+ private AuthorisationSummary authorisationSummary;
+
+ @JsonProperty("agreement_id")
+ private String agreementId;
+
+ @JsonProperty("authorisation_mode")
+ private AuthorisationMode authorisationMode;
+
+ public CardPayment(String chargeId, long amount, PaymentState state, String returnUrl, String description,
+ String reference, String email, String paymentProvider, String createdDate,
+ RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails,
+ SupportedLanguage language, boolean delayedCapture, boolean moto, Long corporateCardSurcharge, Long totalAmount,
+ String providerId, ExternalMetadata metadata, Long fee, Long netAmount, AuthorisationSummary authorisationSummary, String agreementId,
+ AuthorisationMode authorisationMode) {
+ this.paymentId = chargeId;
+ this.amount = amount;
+ this.description = description;
+ this.reference = reference;
+ this.paymentProvider = paymentProvider;
+ this.createdDate = createdDate;
+ this.state = state;
+ this.refundSummary = refundSummary;
+ this.settlementSummary = settlementSummary;
+ this.cardDetails = cardDetails;
+ this.providerId = providerId;
+ this.metadata = metadata;
+ this.paymentType = TokenPaymentType.CARD.getFriendlyName();
+ this.language = language;
+ this.delayedCapture = delayedCapture;
+ this.moto = moto;
+ this.corporateCardSurcharge = corporateCardSurcharge;
+ this.totalAmount = totalAmount;
+ this.fee = fee;
+ this.netAmount = netAmount;
+ this.email = email;
+ this.returnUrl = returnUrl;
+ this.authorisationSummary = authorisationSummary;
+ this.agreementId = agreementId;
+ this.authorisationMode = authorisationMode;
+ }
+
+ /**
+ * card brand is no longer a top level charge property. It is now at `card_details.card_brand` attribute
+ * We still need to support `v1` clients with a top level card brand attribute to keep support their integrations.
+ *
+ * @return
+ */
+ @Schema(description = "This attribute is deprecated. Please use `card_details.card_brand` instead.",
+ example = "Visa", accessMode = READ_ONLY, deprecated = true)
+ @JsonProperty("card_brand")
+ @Deprecated
+ public String getCardBrand() {
+ return cardDetails != null ? cardDetails.getCardBrand() : null;
+ }
+
+ public ExternalMetadata getMetadata() {
+ return metadata;
+ }
+
+ public Optional getRefundSummary() {
+ return Optional.ofNullable(refundSummary);
+ }
+
+ public Optional getSettlementSummary() {
+ return Optional.ofNullable(settlementSummary);
+ }
+
+ public Optional getCardDetails() {
+ return Optional.ofNullable(cardDetails);
+ }
+
+ @Schema(name = "language", example = "en",
+ description = "The ISO-6391 Alpha-2 code of the [language of the user's payment page]" +
+ "(https://docs.payments.service.gov.uk/optional_features/welsh_language).")
+ public SupportedLanguage getLanguage() {
+ return language;
+ }
+
+ @Schema(description = "`delayed_capture` is `true` if you’re " +
+ "[controlling how long it takes GOV.UK Pay to take (‘capture’) a payment]" +
+ "(https://docs.payments.service.gov.uk/delayed_capture).",
+ example = "false", accessMode = READ_ONLY)
+ public boolean getDelayedCapture() {
+ return delayedCapture;
+ }
+
+ @Schema(description = "Indicates if this payment is a [Mail Order / Telephone Order (MOTO) payment]" +
+ "(https://docs.payments.service.gov.uk/moto_payments).", example = "false")
+ public boolean getMoto() { return moto; }
+
+ @Schema(example = "250", description = "The [corporate card surcharge]" +
+ "(https://docs.payments.service.gov.uk/corporate_card_surcharges/#add-corporate-card-fees) " +
+ "amount in pence.",
+ accessMode = READ_ONLY)
+ public Optional getCorporateCardSurcharge() {
+ return Optional.ofNullable(corporateCardSurcharge);
+ }
+
+ @Schema(example = "5", description = "The [payment service provider’s (PSP) transaction fee]" +
+ "(https://docs.payments.service.gov.uk/reporting/#psp-fees), in pence. " +
+ "`fee` only appears when we have taken (‘captured’) the payment from the user " +
+ "or if their payment fails after they submitted their card details. " +
+ "`fee` will not appear if your PSP is Worldpay or you are using an API key from a test service.",
+ accessMode = READ_ONLY)
+ public Optional getFee() {
+ return Optional.ofNullable(fee);
+ }
+
+ @Schema(example = "1195",
+ description = "The amount, in pence, that will be paid into your bank account " +
+ "after your payment service provider takes the `fee`.",
+ accessMode = READ_ONLY)
+ public Optional getNetAmount() {
+ return Optional.ofNullable(netAmount);
+ }
+
+ @Schema(example = "1450",
+ description = "Amount your user paid in pence, including corporate card fees. " +
+ "`total_amount` only appears if you [added a corporate card surcharge to the payment]" +
+ "(https://docs.payments.service.gov.uk/corporate_card_surcharges/#add-corporate-card-fees).",
+ accessMode = READ_ONLY)
+ public Optional getTotalAmount() {
+ return Optional.ofNullable(totalAmount);
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ @Schema(example = "http://your.service.domain/your-reference",
+ description = "The URL you direct the paying user to after their payment journey on GOV.UK Pay ends.",
+ accessMode = READ_ONLY)
+ public Optional getReturnUrl() {
+ return Optional.ofNullable(returnUrl);
+ }
+
+ @Schema(example = "The paying user’s email address.")
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ @Schema(description = "Object containing information about the authentication of the payment.")
+ public AuthorisationSummary getAuthorisationSummary() {
+ return authorisationSummary;
+ }
+
+ @Schema(hidden = true)
+ public String getAgreementId() {
+ return agreementId;
+ }
+
+ @Schema(type = "String", description = "How the payment will be authorised. Payments created in `web` mode require the paying user to visit the `next_url` to complete the payment.",
+ allowableValues = {"web", "moto_api", "external"})
+ public AuthorisationMode getAuthorisationMode() {
+ return authorisationMode;
+ }
+
+ @Schema(example = "2016-01-21T17:15:00.000Z", accessMode = READ_ONLY)
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93",
+ description = "The unique ID GOV.UK Pay automatically associated " +
+ "with this payment when you created it.",
+ accessMode = READ_ONLY)
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ @Schema(example = "1200", description = "The description assigned to the payment when it was created.")
+ public long getAmount() {
+ return amount;
+ }
+
+ @Schema(example = "Your Service Description", description = "The description assigned to the payment when it was created.")
+ public String getDescription() {
+ return description;
+ }
+
+ @Schema(example = "your-reference",
+ description = "The reference associated with the payment when it was created. " +
+ "`reference` is not unique - multiple payments can have the same `reference` value.")
+ public String getReference() {
+ return reference;
+ }
+
+ @Schema(example = "worldpay",
+ description = "The payment service provider that processed this payment.",
+ accessMode = READ_ONLY)
+ public String getPaymentProvider() {
+ return paymentProvider;
+ }
+
+ @Override
+ public String toString() {
+ // Don't include:
+ // description - some services include PII
+ // reference - can come from user input for payment links, in the past they have mistakenly entered card numbers
+ // return url - services can include identifiers that are incorrectly flagged as PII or card numbers
+ return "Card Payment{" +
+ "paymentId='" + paymentId + '\'' +
+ ", paymentProvider='" + paymentProvider + '\'' +
+ ", cardBrandLabel='" + getCardBrand() + '\'' +
+ ", amount=" + amount +
+ ", fee=" + fee +
+ ", netAmount=" + netAmount +
+ ", corporateCardSurcharge='" + corporateCardSurcharge + '\'' +
+ ", state='" + state + '\'' +
+ ", language='" + language.toString() + '\'' +
+ ", delayedCapture=" + delayedCapture +
+ ", moto=" + moto +
+ ", createdDate='" + createdDate + '\'' +
+ ", agreementId='" + agreementId + '\'' +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Charge.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Charge.java
new file mode 100644
index 000000000..2ab0a340f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Charge.java
@@ -0,0 +1,262 @@
+package uk.gov.pay.api.model;
+
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.util.List;
+import java.util.Optional;
+
+public class Charge {
+
+ private String chargeId;
+
+ private String returnUrl;
+
+ private String paymentProvider;
+
+ private List links;
+
+ private RefundSummary refundSummary;
+
+ private PaymentSettlementSummary settlementSummary;
+
+ private CardDetails cardDetails;
+
+ private Long amount;
+
+ private PaymentState state;
+
+ private String description;
+
+ private String reference;
+
+ private String email;
+
+ private SupportedLanguage language;
+
+ private boolean delayedCapture;
+
+ private boolean moto;
+
+ private String agreementId;
+
+ private Long corporateCardSurcharge;
+
+ private Long totalAmount;
+
+ private Long fee;
+
+ private Long netAmount;
+
+ private String createdDate;
+
+ private String gatewayTransactionId;
+
+ private ExternalMetadata metadata;
+
+ private AuthorisationSummary authorisationSummary;
+
+ private AuthorisationMode authorisationMode;
+
+ public Charge(String chargeId, Long amount, PaymentState state, String returnUrl, String description,
+ String reference, String email, String paymentProvider, String createdDate,
+ SupportedLanguage language, boolean delayedCapture, boolean moto, RefundSummary refundSummary,
+ PaymentSettlementSummary settlementSummary, CardDetails cardDetails,
+ List links, Long corporateCardSurcharge, Long totalAmount,
+ String gatewayTransactionId, ExternalMetadata metadata, Long fee, Long netAmount,
+ AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode) {
+ this.chargeId = chargeId;
+ this.amount = amount;
+ this.state = state;
+ this.returnUrl = returnUrl;
+ this.description = description;
+ this.reference = reference;
+ this.email = email;
+ this.paymentProvider = paymentProvider;
+ this.createdDate = createdDate;
+ this.language = language;
+ this.delayedCapture = delayedCapture;
+ this.moto = moto;
+ this.refundSummary = refundSummary;
+ this.settlementSummary = settlementSummary;
+ this.cardDetails = cardDetails;
+ this.links = links;
+ this.corporateCardSurcharge = corporateCardSurcharge;
+ this.totalAmount = totalAmount;
+ this.gatewayTransactionId = gatewayTransactionId;
+ this.metadata = metadata;
+ this.fee = fee;
+ this.netAmount = netAmount;
+ this.authorisationSummary = authorisationSummary;
+ this.agreementId = agreementId;
+ this.authorisationMode = authorisationMode;
+ }
+
+ public static Charge from(ChargeFromResponse chargeFromResponse) {
+ return new Charge(
+ chargeFromResponse.getChargeId(),
+ chargeFromResponse.getAmount(),
+ chargeFromResponse.getState(),
+ chargeFromResponse.getReturnUrl(),
+ chargeFromResponse.getDescription(),
+ chargeFromResponse.getReference(),
+ chargeFromResponse.getEmail(),
+ chargeFromResponse.getPaymentProvider(),
+ chargeFromResponse.getCreatedDate(),
+ chargeFromResponse.getLanguage(),
+ chargeFromResponse.getDelayedCapture(),
+ chargeFromResponse.isMoto(),
+ chargeFromResponse.getRefundSummary(),
+ chargeFromResponse.getSettlementSummary(),
+ chargeFromResponse.getWalletType()
+ .map(wallet -> CardDetails.from(chargeFromResponse.getCardDetailsFromResponse(), wallet.getTitleCase()))
+ .orElse(CardDetails.from(chargeFromResponse.getCardDetailsFromResponse(), null)),
+ chargeFromResponse.getLinks(),
+ chargeFromResponse.getCorporateCardSurcharge(),
+ chargeFromResponse.getTotalAmount(),
+ chargeFromResponse.getGatewayTransactionId(),
+ chargeFromResponse.getMetadata().orElse(null),
+ chargeFromResponse.getFee(),
+ chargeFromResponse.getNetAmount(),
+ chargeFromResponse.getAuthorisationSummary(),
+ chargeFromResponse.getAgreementId(),
+ chargeFromResponse.getAuthorisationMode()
+ );
+ }
+
+ public static Charge from(TransactionResponse transactionResponse) {
+ return new Charge(
+ transactionResponse.getTransactionId(),
+ transactionResponse.getAmount(),
+ transactionResponse.getState(),
+ transactionResponse.getReturnUrl(),
+ transactionResponse.getDescription(),
+ transactionResponse.getReference(),
+ transactionResponse.getEmail(),
+ transactionResponse.getPaymentProvider(),
+ transactionResponse.getCreatedDate(),
+ transactionResponse.getLanguage(),
+ transactionResponse.getDelayedCapture(),
+ transactionResponse.isMoto(),
+ transactionResponse.getRefundSummary(),
+ transactionResponse.getSettlementSummary(),
+ transactionResponse.getWalletType()
+ .map(wallet -> CardDetails.from(transactionResponse.getCardDetailsFromResponse(), wallet.getTitleCase()))
+ .orElse(CardDetails.from(transactionResponse.getCardDetailsFromResponse(), null)),
+ transactionResponse.getLinks(),
+ transactionResponse.getCorporateCardSurcharge(),
+ transactionResponse.getTotalAmount(),
+ transactionResponse.getGatewayTransactionId(),
+ transactionResponse.getMetadata().orElse(null),
+ transactionResponse.getFee(),
+ transactionResponse.getNetAmount(),
+ transactionResponse.getAuthorisationSummary(),
+ null,
+ transactionResponse.getAuthorisationMode());
+ }
+
+ public Optional getMetadata() {
+ return Optional.ofNullable(metadata);
+ }
+
+ public String getChargeId() {
+ return chargeId;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public String getReturnUrl() {
+ return returnUrl;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public SupportedLanguage getLanguage() {
+ return language;
+ }
+
+ public boolean getDelayedCapture() {
+ return delayedCapture;
+ }
+
+ public boolean isMoto() {
+ return moto;
+ }
+
+ public Long getCorporateCardSurcharge() {
+ return corporateCardSurcharge;
+ }
+
+ public Long getTotalAmount() {
+ return totalAmount;
+ }
+
+ public Long getFee() {
+ return fee;
+ }
+
+ public Long getNetAmount() {
+ return netAmount;
+ }
+
+ public String getPaymentProvider() {
+ return paymentProvider;
+ }
+
+ public String getCardBrand() {
+ return cardDetails != null ? cardDetails.getCardBrand() : "";
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public List getLinks() {
+ return links;
+ }
+
+ public RefundSummary getRefundSummary() {
+ return refundSummary;
+ }
+
+ public PaymentSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ public CardDetails getCardDetails() {
+ return cardDetails;
+ }
+
+ public String getGatewayTransactionId() {
+ return gatewayTransactionId;
+ }
+
+ public AuthorisationSummary getAuthorisationSummary() {
+ return authorisationSummary;
+ }
+
+ public String getAgreementId() {
+ return agreementId;
+ }
+
+ public AuthorisationMode getAuthorisationMode() {
+ return authorisationMode;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java
new file mode 100644
index 000000000..baa959f39
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ChargeFromResponse.java
@@ -0,0 +1,235 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.model.telephone.PaymentOutcome;
+import uk.gov.pay.api.utils.CustomSupportedLanguageDeserializer;
+import uk.gov.pay.api.utils.WalletDeserializer;
+import uk.gov.service.payments.commons.api.json.ExternalMetadataDeserialiser;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class ChargeFromResponse {
+
+ private String chargeId;
+
+ private String returnUrl;
+
+ private String paymentProvider;
+
+ private List links = new ArrayList<>();
+
+ private RefundSummary refundSummary;
+
+ private PaymentSettlementSummary settlementSummary;
+
+ @JsonProperty("card_details")
+ private CardDetailsFromResponse cardDetails;
+
+ private Long amount;
+
+ private PaymentState state;
+
+ private String description;
+
+ private String reference;
+
+ private String email;
+
+ private String telephoneNumber;
+
+ @JsonProperty("agreement_id")
+ private String agreementId;
+
+ @JsonDeserialize(using = CustomSupportedLanguageDeserializer.class)
+ private SupportedLanguage language;
+
+ private boolean delayedCapture;
+
+ private boolean moto;
+
+ private Long corporateCardSurcharge;
+
+ private Long totalAmount;
+
+ private Long fee;
+
+ private Long netAmount;
+
+ private String createdDate;
+
+ private String authorisedDate;
+
+ private String processorId;
+
+ private String providerId;
+
+ private String authCode;
+
+ private PaymentOutcome paymentOutcome;
+
+ private String gatewayTransactionId;
+
+ @JsonDeserialize(using = ExternalMetadataDeserialiser.class)
+ private ExternalMetadata metadata;
+
+ private AuthorisationSummary authorisationSummary;
+
+ private AuthorisationMode authorisationMode;
+
+ // wallet_type is a top level charge property but is returned as part of the card_details object
+ @JsonProperty("wallet_type")
+ @JsonDeserialize(using = WalletDeserializer.class)
+ private Wallet walletType;
+
+ public Optional getMetadata() {
+ return Optional.ofNullable(metadata);
+ }
+
+ public String getChargeId() {
+ return chargeId;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public String getReturnUrl() {
+ return returnUrl;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getTelephoneNumber() {
+ return telephoneNumber;
+ }
+
+ public SupportedLanguage getLanguage() {
+ return language;
+ }
+
+ public boolean getDelayedCapture() {
+ return delayedCapture;
+ }
+
+ public boolean isMoto() {
+ return moto;
+ }
+
+ public String getAgreementId() {
+ return agreementId;
+ }
+
+ public Long getCorporateCardSurcharge() {
+ return corporateCardSurcharge;
+ }
+
+ public Long getTotalAmount() {
+ return totalAmount;
+ }
+
+ public Long getFee() {
+ return fee;
+ }
+
+ public Long getNetAmount() {
+ return netAmount;
+ }
+
+ public String getPaymentProvider() {
+ return paymentProvider;
+ }
+
+ /**
+ * card brand is no longer a top level charge property. It is now at `card_details.card_brand` attribute
+ * We still need to support `v1` clients with a top level card brand attribute to keep support their integrations.
+ *
+ * @return
+ */
+ @JsonProperty("card_brand")
+ public String getCardBrand() {
+ return cardDetails != null ? cardDetails.getCardBrand() : "";
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public String getAuthorisedDate() {
+ return authorisedDate;
+ }
+
+ public String getProcessorId() {
+ return processorId;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public String getAuthCode() {
+ return authCode;
+ }
+
+ public PaymentOutcome getPaymentOutcome() {
+ return paymentOutcome;
+ }
+
+ public List getLinks() {
+ return links;
+ }
+
+ public RefundSummary getRefundSummary() {
+ return refundSummary;
+ }
+
+ public PaymentSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ public CardDetailsFromResponse getCardDetailsFromResponse() {
+ return cardDetails;
+ }
+
+ public String getGatewayTransactionId() {
+ return gatewayTransactionId;
+ }
+
+ public AuthorisationSummary getAuthorisationSummary() {
+ return authorisationSummary;
+ };
+
+ public AuthorisationMode getAuthorisationMode() {
+ return authorisationMode;
+ }
+
+ public Optional getWalletType() {
+ return Optional.ofNullable(walletType);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateAgreementRequestBuilder.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateAgreementRequestBuilder.java
new file mode 100644
index 000000000..23a059785
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateAgreementRequestBuilder.java
@@ -0,0 +1,43 @@
+package uk.gov.pay.api.model;
+
+import uk.gov.pay.api.agreement.model.CreateAgreementRequest;
+
+public class CreateAgreementRequestBuilder {
+ private String reference;
+ private String description;
+ private String userIdentifier;
+ public static CreateAgreementRequestBuilder builder() {
+ return new CreateAgreementRequestBuilder();
+ }
+
+ public CreateAgreementRequest build() {
+ return new CreateAgreementRequest(this);
+ }
+
+ public CreateAgreementRequestBuilder reference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public CreateAgreementRequestBuilder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public CreateAgreementRequestBuilder userIdentifier(String userIdentifier) {
+ this.userIdentifier = userIdentifier;
+ return this;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getUserIdentifier() {
+ return userIdentifier;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequest.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequest.java
new file mode 100644
index 000000000..540d2d801
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequest.java
@@ -0,0 +1,287 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.hibernate.validator.constraints.Length;
+import uk.gov.pay.api.utils.JsonStringBuilder;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Size;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+@Schema(description = "The create payment request body")
+public class CreateCardPaymentRequest {
+
+ public static final int EMAIL_MAX_LENGTH = 254;
+ public static final String AMOUNT_FIELD_NAME = "amount";
+ public static final String REFERENCE_FIELD_NAME = "reference";
+ public static final String DESCRIPTION_FIELD_NAME = "description";
+ public static final String LANGUAGE_FIELD_NAME = "language";
+ public static final String EMAIL_FIELD_NAME = "email";
+ public static final int REFERENCE_MAX_LENGTH = 255;
+ public static final int AMOUNT_MAX_VALUE = 10000000;
+ public static final int AMOUNT_MIN_VALUE = 0;
+ public static final int DESCRIPTION_MAX_LENGTH = 255;
+ public static final String RETURN_URL_FIELD_NAME = "return_url";
+ public static final int URL_MAX_LENGTH = 2000;
+ public static final String PREFILLED_CARDHOLDER_DETAILS_FIELD_NAME = "prefilled_cardholder_details";
+ public static final String PREFILLED_CARDHOLDER_NAME_FIELD_NAME = "cardholder_name";
+ public static final String PREFILLED_BILLING_ADDRESS_FIELD_NAME = "billing_address";
+ public static final String PREFILLED_ADDRESS_LINE1_FIELD_NAME = "line1";
+ public static final String PREFILLED_ADDRESS_LINE2_FIELD_NAME = "line2";
+ public static final String PREFILLED_ADDRESS_CITY_FIELD_NAME = "city";
+ public static final String PREFILLED_ADDRESS_POSTCODE_FIELD_NAME = "postcode";
+ public static final String PREFILLED_ADDRESS_COUNTRY_FIELD_NAME = "country";
+ public static final String DELAYED_CAPTURE_FIELD_NAME = "delayed_capture";
+ public static final String MOTO_FIELD_NAME = "moto";
+ public static final String SET_UP_AGREEMENT_FIELD_NAME = "set_up_agreement";
+ public static final String AGREEMENT_ID_FIELD_NAME = "agreement_id";
+ public static final String SOURCE_FIELD_NAME = "source";
+ public static final String METADATA = "metadata";
+ public static final String INTERNAL = "internal";
+ public static final String AUTHORISATION_MODE = "authorisation_mode";
+ private static final String PREFILLED_CARDHOLDER_DETAILS = "prefilled_cardholder_details";
+ private static final String BILLING_ADDRESS = "billing_address";
+
+ // Even though the minimum is 0, this is only allowed for accounts this is enabled for and is a hidden feature
+ // so the validation error message will always state that the minimum is 1 for consistency.
+ @JsonProperty("amount")
+ @Min(value = AMOUNT_MIN_VALUE, message = "Must be greater than or equal to 1")
+ @Max(value = AMOUNT_MAX_VALUE, message = "Must be less than or equal to {value}")
+ private int amount;
+
+ @JsonProperty("reference")
+ @Size(max = REFERENCE_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ private String reference;
+
+ @JsonProperty("description")
+ @Size(max = DESCRIPTION_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ private String description;
+
+ @JsonProperty("language")
+ private SupportedLanguage language;
+
+ @JsonProperty("email")
+ @Length(max = EMAIL_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ private String email;
+
+ @Size(max = URL_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ @JsonProperty("return_url")
+ private final String returnUrl;
+
+ private final Boolean delayedCapture;
+
+ private final Boolean moto;
+
+ @Schema(name = "metadata", example = "{\"property1\": \"value1\", \"property2\": \"value2\"}\"")
+ private final ExternalMetadata metadata;
+
+ private final Internal internal;
+
+ @JsonProperty("set_up_agreement")
+ @Size(min=26, max=26, message = "Field [set_up_agreement] length must be 26")
+ private String setUpAgreement;
+
+ @JsonProperty("agreement_id")
+ @Size(min=26, max=26, message = "Field [agreement_id] length must be 26")
+ private String agreementId;
+
+ @Valid
+ private final PrefilledCardholderDetails prefilledCardholderDetails;
+
+ private final AuthorisationMode authorisationMode;
+
+ public CreateCardPaymentRequest(CreateCardPaymentRequestBuilder builder) {
+ this.amount = builder.getAmount();
+ this.reference = builder.getReference();
+ this.description = builder.getDescription();
+ this.language = builder.getLanguage();
+ this.email = builder.getEmail();
+ this.returnUrl = builder.getReturnUrl();
+ this.delayedCapture = builder.getDelayedCapture();
+ this.moto = builder.isMoto();
+ this.metadata = builder.getMetadata();
+ this.prefilledCardholderDetails = builder.getPrefilledCardholderDetails();
+ this.internal = builder.getInternal();
+ this.setUpAgreement = builder.getSetUpAgreement();
+ this.authorisationMode = builder.getAuthorisationMode();
+ this.agreementId = builder.getAgreementId();
+ }
+
+ @Schema(description = "Sets the amount the user will pay, in pence.", required = true, minimum = "1", maximum = "10000000", example = "12000")
+ public int getAmount() {
+ return amount;
+ }
+
+ @Schema(description = "Associate a reference with this payment. " +
+ "`reference` is not unique - multiple payments can have identical `reference` values.",
+ required = true, example = "12345")
+ public String getReference() {
+ return reference;
+ }
+
+ @Schema(description = "A human-readable description of the payment you’re creating. " +
+ "Paying users see this description on the payment pages. " +
+ "Service staff see the description in the GOV.UK Pay admin tool",
+ required = true, example = "New passport application")
+ public String getDescription() {
+ return description;
+ }
+
+ @Schema(description = "[Sets the language of the user’s payment page]" +
+ "(https://docs.payments.service.gov.uk/optional_features/welsh_language) " +
+ "with an ISO-6391 Alpha-2 code of a supported language.", example = "en")
+ @JsonProperty(LANGUAGE_FIELD_NAME)
+ public Optional getLanguage() {
+ return Optional.ofNullable(language);
+ }
+
+ @Schema(name = "email", example = "Joe.Bogs@example.org", description = "email")
+ @JsonProperty(EMAIL_FIELD_NAME)
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ @Schema(description = "The URL [the paying user is directed to after their payment journey on GOV.UK Pay ends]" +
+ "(https://docs.payments.service.gov.uk/making_payments/#choose-the-return-url-and-match-your-users-to-payments).",
+ required = true, example = "https://service-name.gov.uk/transactions/12345")
+ public String getReturnUrl() {
+ return returnUrl;
+ }
+
+ @Schema(description = "prefilled_cardholder_details")
+ @JsonProperty(CreateCardPaymentRequest.PREFILLED_CARDHOLDER_DETAILS_FIELD_NAME)
+ public Optional getPrefilledCardholderDetails() {
+ return Optional.ofNullable(prefilledCardholderDetails);
+ }
+
+ @Schema(description = "You can use this parameter to " +
+ "[delay taking a payment from the paying user’s bank account]" +
+ "(https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment). " +
+ "For example, you might want to do your own anti-fraud checks on payments, " +
+ "or check that users are eligible for your service. Defaults to `false`.",
+ example = "false")
+ @JsonProperty(DELAYED_CAPTURE_FIELD_NAME)
+ public Optional getDelayedCapture() {
+ return Optional.ofNullable(delayedCapture);
+ }
+
+ @JsonProperty(MOTO_FIELD_NAME)
+ @Schema(description = "You can use this parameter to " +
+ "[designate a payment as a Mail Order / Telephone Order (MOTO) payment]" +
+ "(https://docs.payments.service.gov.uk/moto_payments).", example = "false")
+ public Optional getMoto() {
+ return Optional.ofNullable(moto);
+ }
+
+ @JsonProperty("metadata")
+ @Schema(description = "Additional metadata - up to 10 name/value pairs - on the payment. " +
+ "Each key must be between 1 and 30 characters long. " +
+ "The value, if a string, must be no greater than 50 characters long. " +
+ "Other permissible value types: boolean, number.",
+ example = "{\"ledger_code\":\"123\", \"reconciled\": true}")
+ public Optional getMetadata() {
+ return Optional.ofNullable(metadata);
+ }
+
+ @JsonProperty("internal")
+ @Schema(hidden = true)
+ public Optional getInternal() {
+ return Optional.ofNullable(internal);
+ }
+
+ @JsonProperty("set_up_agreement")
+ @Schema(description = "Use this parameter to set up an existing agreement for recurring payments. " +
+ "The `set_up_agreement` value you send must be a valid `agreement_id`.",
+ required = false, example = "abcefghjklmnopqr1234567890")
+ public Optional getSetUpAgreement() {
+ return Optional.ofNullable(setUpAgreement);
+ }
+
+ @JsonProperty("agreement_id")
+ @Schema(description = "The unique ID GOV.UK Pay automatically associated with a recurring payments agreement. " +
+ "Including `agreement_id` in your request tells the API to take this payment using the card details that are associated with this agreement. " +
+ "`agreement_id` must match an active agreement ID. " +
+ "You must set `authorisation_mode` to `agreement` for the API to accept `agreement_id`.",
+ required = false, example = "abcefghjklmnopqr1234567890")
+ public Optional getAgreementId() {
+ return Optional.ofNullable(agreementId);
+ }
+
+ @JsonProperty("authorisation_mode")
+ @Schema(description = "Sets how you intend to authorise the payment. Defaults to `web`. " +
+ "Payments created with `web` mode follow the [standard GOV.UK Pay payment journey](https://docs.payments.service.gov.uk/payment_flow/). " +
+ "Paying users visit the `next_url` in the response to complete their payment. " +
+ "Payments created with `agreement` mode are authorised with an agreement for recurring payments. " +
+ "If you create an `agreement` payment, you must also send an active `agreement_id`. " +
+ "You must not send `return_url`, `email`, or `prefilled_cardholder_details` or your request will fail. " +
+ "Payments created with `moto_api` mode return an `auth_url_post` object and a `one_time_token`. " +
+ "You can use `auth_url_post` and `one_time_token` to send the paying user’s card details through the API and complete the payment. " +
+ "If you create a `moto_api` payment, do not send a `return_url` in your request.",
+ type = "String", allowableValues = {"web", "agreement", "moto_api"})
+ public Optional getAuthorisationMode() {
+ return Optional.ofNullable(authorisationMode);
+ }
+
+ public String toConnectorPayload() {
+ JsonStringBuilder request = new JsonStringBuilder()
+ .add("amount", this.getAmount())
+ .add("reference", this.getReference())
+ .add("description", this.getDescription())
+ .add("return_url", this.getReturnUrl());
+ getLanguage().ifPresent(language -> request.add("language", language.toString()));
+ getDelayedCapture().ifPresent(delayedCapture -> request.add("delayed_capture", delayedCapture));
+ getMoto().ifPresent(moto -> request.add("moto", moto));
+ getMetadata().ifPresent(metadata -> request.add("metadata", metadata.getMetadata()));
+ getEmail().ifPresent(email -> request.add("email", email));
+ getInternal().flatMap(Internal::getSource).ifPresent(source -> request.add("source", source));
+ getAuthorisationMode().ifPresent(authorisationMode -> request.add("authorisation_mode", authorisationMode.getName()));
+ getAgreementId().ifPresent(agreementId -> request.add("agreement_id", agreementId));
+ getSetUpAgreement().ifPresent(setUpAgreement -> {
+ request.add("agreement_id", setUpAgreement);
+ request.add("save_payment_instrument_to_agreement", true);
+ });
+
+ getPrefilledCardholderDetails().ifPresent(prefilledDetails -> {
+ prefilledDetails.getCardholderName().ifPresent(name -> request.addToMap(PREFILLED_CARDHOLDER_DETAILS, "cardholder_name", name));
+ prefilledDetails.getBillingAddress().ifPresent(address -> {
+ request.addToNestedMap("line1", address.getLine1(), PREFILLED_CARDHOLDER_DETAILS, BILLING_ADDRESS);
+ request.addToNestedMap("line2", address.getLine2(), PREFILLED_CARDHOLDER_DETAILS, BILLING_ADDRESS);
+ request.addToNestedMap("postcode", address.getPostcode(), PREFILLED_CARDHOLDER_DETAILS, BILLING_ADDRESS);
+ request.addToNestedMap("city", address.getCity(), PREFILLED_CARDHOLDER_DETAILS, BILLING_ADDRESS);
+ request.addToNestedMap("country", address.getCountry(), PREFILLED_CARDHOLDER_DETAILS, BILLING_ADDRESS);
+ });
+ });
+
+ return request.build();
+ }
+
+ /**
+ * This looks JSONesque but is not identical to the received request
+ */
+ @Override
+ public String toString() {
+ // Don't include:
+ // description - some services include PII
+ // reference - can come from user input for payment links, in the past they have mistakenly entered card numbers
+ StringJoiner joiner = new StringJoiner(", ", "{", "}");
+ joiner.add("amount: " + getAmount());
+ joiner.add("return_url: " + returnUrl);
+ getInternal().flatMap(Internal::getSource).ifPresent(source -> joiner.add("source: " + source));
+ getLanguage().ifPresent(value -> joiner.add("language: " + value));
+ getDelayedCapture().ifPresent(value -> joiner.add("delayed_capture: " + value));
+ getMoto().ifPresent(value -> joiner.add("moto: " + value));
+ getMetadata().ifPresent(value -> joiner.add("metadata: " + value));
+ getAuthorisationMode().ifPresent(authorisationMode -> joiner.add("authorisation_mode: " + authorisationMode));
+ getSetUpAgreement().ifPresent(setUpAgreement -> joiner.add("set_up_agreement: " + setUpAgreement));
+ getAgreementId().ifPresent(agreementId -> joiner.add("agreement_id: " + agreementId));
+
+ return joiner.toString();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequestBuilder.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequestBuilder.java
new file mode 100644
index 000000000..5aafab9ed
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreateCardPaymentRequestBuilder.java
@@ -0,0 +1,229 @@
+package uk.gov.pay.api.model;
+
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.Source;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+public class CreateCardPaymentRequestBuilder {
+ private ExternalMetadata metadata;
+ private int amount;
+ private String returnUrl;
+ private String reference;
+ private String description;
+ private SupportedLanguage language;
+ private Boolean delayedCapture;
+ private Boolean moto;
+ private String email;
+ private String cardholderName;
+ private String addressLine1;
+ private String addressLine2;
+ private String city;
+ private String postcode;
+ private String country;
+ private PrefilledCardholderDetails prefilledCardholderDetails;
+ private Source source;
+ private Internal internal;
+ private String setUpAgreement;
+ private AuthorisationMode authorisationMode;
+ private String agreementId;
+
+ public static CreateCardPaymentRequestBuilder builder() {
+ return new CreateCardPaymentRequestBuilder();
+ }
+
+ public CreateCardPaymentRequest build() {
+ return new CreateCardPaymentRequest(this);
+ }
+
+ public CreateCardPaymentRequestBuilder amount(int amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder returnUrl(String returnUrl) {
+ this.returnUrl = returnUrl;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder reference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder language(SupportedLanguage language) {
+ this.language = language;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder delayedCapture(Boolean delayedCapture) {
+ this.delayedCapture = delayedCapture;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder moto(Boolean moto) {
+ this.moto = moto;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder metadata(ExternalMetadata metadata) {
+ this.metadata = metadata;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder email(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder cardholderName(String cardHolderName) {
+ this.cardholderName = cardHolderName;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder addressLine1(String addressLine1) {
+ this.addressLine1 = addressLine1;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder addressLine2(String addressLine2) {
+ this.addressLine2 = addressLine2;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder city(String city) {
+ this.city = city;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder postcode(String postcode) {
+ this.postcode = postcode;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder country(String country) {
+ this.country = country;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder source(Source source) {
+ this.source = source;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder authorisationMode(AuthorisationMode authorisationMode) {
+ this.authorisationMode = authorisationMode;
+ return this;
+ }
+
+ public CreateCardPaymentRequestBuilder agreementId(String agreementId) {
+ this.agreementId = agreementId;
+ return this;
+ }
+
+ public PrefilledCardholderDetails getPrefilledCardholderDetails() {
+ if (cardholderName != null) {
+ this.prefilledCardholderDetails = new PrefilledCardholderDetails();
+ this.prefilledCardholderDetails.setCardholderName(cardholderName);
+ }
+ if (addressLine1 != null || addressLine2 != null ||
+ postcode != null || city != null || country != null) {
+ if (this.prefilledCardholderDetails == null) {
+ this.prefilledCardholderDetails = new PrefilledCardholderDetails();
+ }
+ this.prefilledCardholderDetails.setAddress(addressLine1, addressLine2, postcode, city, country);
+ }
+ return prefilledCardholderDetails;
+ }
+
+ public ExternalMetadata getMetadata() {
+ return metadata;
+ }
+
+ public Boolean isMoto() {
+ return moto;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public String getReturnUrl() {
+ return returnUrl;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public SupportedLanguage getLanguage() {
+ return language;
+ }
+
+ public Boolean getDelayedCapture() {
+ return delayedCapture;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getCardholderName() {
+ return cardholderName;
+ }
+
+ public String getAddressLine1() {
+ return addressLine1;
+ }
+
+ public String getAddressLine2() {
+ return addressLine2;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public String getPostcode() {
+ return postcode;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public Internal getInternal() {
+ if (source != null) {
+ this.internal = new Internal();
+ this.internal.setSource(source);
+ }
+
+ return internal;
+ }
+
+ public void setUpAgreement(String setUpAgreement) {
+ this.setUpAgreement = setUpAgreement;
+
+ }
+
+ public AuthorisationMode getAuthorisationMode() {
+ return authorisationMode;
+ }
+
+ public String getSetUpAgreement() {
+ return setUpAgreement;
+ }
+
+ public String getAgreementId() {
+ return agreementId;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentRefundRequest.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentRefundRequest.java
new file mode 100644
index 000000000..d71d2fb6c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentRefundRequest.java
@@ -0,0 +1,51 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Optional;
+
+@Schema(name = "PaymentRefundRequest", description = "The Payment Refund Request Payload")
+public class CreatePaymentRefundRequest {
+
+ public static final String REFUND_AMOUNT_AVAILABLE = "refund_amount_available";
+ public static final int REFUND_MIN_VALUE = 1;
+
+ @Schema(description = "The amount you want to [refund to your user]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/) in pence.", required = true,
+ example = "150000", minimum = "1", maximum = "10000000")
+ private int amount;
+ @JsonProperty("refund_amount_available")
+ @Schema(description = "Amount in pence. Total amount still available before issuing the refund", required = false,
+ example = "200000", minimum = "1", maximum = "10000000")
+ private Integer refundAmountAvailable;
+
+ public CreatePaymentRefundRequest() {
+ }
+
+ public CreatePaymentRefundRequest(int amount, Integer refundAmountAvailable) {
+ this.amount = amount;
+ this.refundAmountAvailable = refundAmountAvailable;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ /**
+ * This field should be made compulsory at a later stage
+ *
+ * @return
+ */
+ public Optional getRefundAmountAvailable() {
+ return Optional.ofNullable(refundAmountAvailable);
+ }
+
+ @Override
+ public String toString() {
+ return "CreatePaymentRefundRequest{" +
+ "amount=" + amount +
+ ", refundAmountAvailable=" + refundAmountAvailable +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentResult.java
new file mode 100644
index 000000000..0e7d1bfc2
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatePaymentResult.java
@@ -0,0 +1,97 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.PaymentLinks;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+import static uk.gov.pay.api.model.CardPayment.LINKS_JSON_ATTRIBUTE;
+
+/**
+ * Defines swagger specs for create payment
+ */
+public class CreatePaymentResult {
+
+ @JsonProperty
+ @Schema(name = "amount", description = "The amount, in pence, the user has paid or will pay. " +
+ "`amount` will match the value you sent in the request body.", example = "1200")
+ private long amount;
+
+ @JsonProperty
+ private PaymentState state;
+
+ @JsonProperty
+ @Schema(description = "The description you sent in the request body when creating this payment.", example = "New passport application")
+ private String description;
+
+ @JsonProperty
+ @Schema(description = "The reference number you associated with this payment.", example = "12345")
+ private String reference;
+
+ @JsonProperty
+ @Schema(name = "language", description = "The language of the user’s payment page.", example = "en")
+ private SupportedLanguage language;
+
+ @JsonProperty
+ @Schema(name = "payment_id", description = "The unique ID GOV.UK Pay automatically associated " +
+ "with this payment when you created it.", example = "hu20sqlact5260q2nanm0q8u93")
+ private String paymentId;
+
+ @JsonProperty
+ @Schema(name = "payment_provider", example = "worldpay")
+ private String paymentProvider;
+
+ @JsonProperty
+ @Schema(name = "return_url", description = "The URL you direct the paying user to " +
+ "after their payment journey on GOV.UK Pay ends.",
+ example = "https://service-name.gov.uk/transactions/12345")
+ private String returnUrl;
+
+ @JsonProperty
+ @Schema(name = "created_date", description = "The date you created the payment.", example = "2016-01-21T17:15:00.000Z")
+ private String createdDate;
+
+ @JsonProperty
+ @Schema(name = "delayed_capture", description = "`delayed_capture` is `true` if you’re controlling " +
+ "[when GOV.UK Pay takes (‘captures’) the payment from the paying user’s bank account]" +
+ "(https://docs.payments.service.gov.uk/delayed_capture).", example = "false", accessMode = READ_ONLY)
+ private boolean delayedCapture;
+
+ @JsonProperty
+ @Schema(description = "Indicates if this payment is a " +
+ "[Mail Order / Telephone Order (MOTO) payment]" +
+ "(https://docs.payments.service.gov.uk/moto_payments).", example = "false")
+ private boolean moto;
+
+ @JsonProperty("refund_summary")
+ private RefundSummary refundSummary;
+
+ @JsonProperty("settlement_summary")
+ private PaymentSettlementSummary settlementSummary;
+
+ @JsonProperty
+ @Schema(name = LINKS_JSON_ATTRIBUTE, description = "API endpoints related to the payment.")
+ private PaymentLinks links;
+
+ @JsonProperty
+ @Schema(name = "provider_id", description = "The reference number your " +
+ "payment service provider associated with the payment.", example = "null")
+ private String providerId;
+
+ @JsonProperty
+ @Schema(name = "metadata", description = "[Custom metadata](https://docs.payments.service.gov.uk/optional_features/custom_metadata/) you added to the payment.")
+ private ExternalMetadata metadata;
+
+ @JsonProperty
+ @Schema(name = "email", description = "The paying user’s email address. " +
+ "The paying user’s email field will be prefilled with this value when they make their payment. " +
+ "`email` does not appear if you did not include it in the request body.",
+ example = "citizen@example.org")
+ private String email;
+
+ @JsonProperty(value = "card_details")
+ @Schema(name = "card_details")
+ private CardDetailsFromResponse cardDetails;
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatedPaymentWithAllLinks.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatedPaymentWithAllLinks.java
new file mode 100644
index 000000000..519c248ba
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/CreatedPaymentWithAllLinks.java
@@ -0,0 +1,31 @@
+package uk.gov.pay.api.model;
+
+import uk.gov.pay.api.model.links.PaymentWithAllLinks;
+
+public class CreatedPaymentWithAllLinks {
+
+ public enum WhenCreated {
+ BRAND_NEW,
+ EXISTING
+ }
+
+ private final PaymentWithAllLinks payment;
+ private final WhenCreated whenCreated;
+
+ private CreatedPaymentWithAllLinks(PaymentWithAllLinks payment, WhenCreated whenCreated) {
+ this.payment = payment;
+ this.whenCreated = whenCreated;
+ }
+
+ public static CreatedPaymentWithAllLinks of(PaymentWithAllLinks payment, WhenCreated whenCreated) {
+ return new CreatedPaymentWithAllLinks(payment, whenCreated);
+ }
+
+ public PaymentWithAllLinks getPayment() {
+ return payment;
+ }
+
+ public WhenCreated getWhenCreated() {
+ return whenCreated;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Internal.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Internal.java
new file mode 100644
index 000000000..73c1fb75f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Internal.java
@@ -0,0 +1,22 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.service.payments.commons.model.Source;
+
+import java.util.Optional;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Internal {
+
+ @JsonProperty("source")
+ private Source source;
+
+ public Optional getSource() {
+ return Optional.ofNullable(source);
+ }
+
+ public void setSource(Source source) {
+ this.source = source;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentConnectorResponseLink.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentConnectorResponseLink.java
new file mode 100644
index 000000000..c9d2dcd0d
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentConnectorResponseLink.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.Map;
+import java.util.Objects;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PaymentConnectorResponseLink {
+
+ private String rel;
+ private String href;
+ private String method;
+ private String type;
+ private Map params;
+
+ // required for Jackson
+ private PaymentConnectorResponseLink() {
+ }
+
+ public PaymentConnectorResponseLink(String rel,
+ String href,
+ String method,
+ String type,
+ Map params) {
+ this.rel = rel;
+ this.href = href;
+ this.method = method;
+ this.type = type;
+ this.params = params;
+ }
+
+ public String getRel() {
+ return rel;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Map getParams() {
+ return params;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PaymentConnectorResponseLink that = (PaymentConnectorResponseLink) o;
+ return Objects.equals(rel, that.rel) &&
+ Objects.equals(href, that.href) &&
+ Objects.equals(method, that.method) &&
+ Objects.equals(type, that.type) &&
+ Objects.equals(params, that.params);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rel, href, method, type, params);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvent.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvent.java
new file mode 100644
index 000000000..4b54791b6
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvent.java
@@ -0,0 +1,32 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PaymentEvent {
+
+ @JsonProperty("state")
+ private PaymentState state;
+
+ @JsonProperty("updated")
+ private String updated;
+
+ public PaymentEvent() {}
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public String getUpdated() {
+ return updated;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentEvent{" +
+ "state='" + state + '\'' +
+ ", updated=" + updated +
+ "}";
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventResponse.java
new file mode 100644
index 000000000..553b25b41
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventResponse.java
@@ -0,0 +1,72 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.PaymentEventLink;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@Schema(name = "PaymentEvent", description = "A List of Payment Events information")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PaymentEventResponse {
+ @JsonProperty("payment_id")
+ private String paymentId;
+
+ @JsonProperty("state")
+ private PaymentState state;
+
+ @JsonProperty("updated")
+ private String updated;
+
+ @JsonProperty("_links")
+ private PaymentEventLink paymentLink;
+
+ public static PaymentEventResponse from(PaymentEvent paymentEvent, String paymentId, String paymentLink) {
+ return new PaymentEventResponse(paymentId, paymentEvent.getState(), paymentEvent.getUpdated(), paymentLink);
+ }
+
+ public static PaymentEventResponse from(TransactionEvent event, String paymentId, String paymentLink) {
+ return new PaymentEventResponse(paymentId, event.getState(), event.getTimestamp(), paymentLink);
+ }
+
+ private PaymentEventResponse(String paymentId, PaymentState state, String updated, String paymentLink) {
+ this.paymentId = paymentId;
+ this.state = state;
+ this.updated = updated;
+ this.paymentLink = new PaymentEventLink(paymentLink);
+ }
+
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93",
+ description = "The unique ID GOV.UK Pay automatically associated with this payment when you created it.",
+ accessMode = READ_ONLY)
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ @Schema(description = "state")
+ public PaymentState getState() {
+ return state;
+ }
+
+ @Schema(description = "When this payment’s state changed. " +
+ "This value uses Coordinated Universal Time (UTC) and ISO-8601 format - `YYYY-MM-DDThh:mm:ss.SSSZ`.",
+ example = "2017-01-10T16:44:48.646Z", accessMode = READ_ONLY)
+ public String getUpdated() {
+ return updated;
+ }
+
+ public PaymentEventLink getPaymentLink() {
+ return paymentLink;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentEvent{" +
+ "paymentId='" + paymentId + '\'' +
+ ", state='" + state + '\'' +
+ ", updated=" + updated +
+ ", paymentLink=" + paymentLink +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvents.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvents.java
new file mode 100644
index 000000000..c77382134
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEvents.java
@@ -0,0 +1,32 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PaymentEvents {
+ @JsonProperty("charge_id")
+ private String chargeId;
+
+ private List events;
+
+ public PaymentEvents() {}
+
+ public String getChargeId() {
+ return chargeId;
+ }
+
+ public List getEvents() {
+ return events;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentEvents{" +
+ "chargeId='" + chargeId + '\'' +
+ ", events=" + events +
+ "}";
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventsResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventsResponse.java
new file mode 100644
index 000000000..9ba89d870
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentEventsResponse.java
@@ -0,0 +1,70 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.PaymentLinksForEvents;
+
+import java.net.URI;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@Schema(name = "PaymentEvents", description = "A List of Payment Events information")
+public class PaymentEventsResponse {
+ @JsonProperty("payment_id")
+ private final String paymentId;
+
+ private final List events;
+
+ @JsonProperty("_links")
+ private PaymentLinksForEvents links;
+
+ private PaymentEventsResponse(String paymentId, List events, PaymentLinksForEvents links) {
+ this.paymentId = paymentId;
+ this.events = events;
+ this.links = links;
+ }
+
+ public static PaymentEventsResponse from(PaymentEvents paymentEvents, URI paymentEventsLink, URI eventsLink) {
+ List events = paymentEvents.getEvents().stream()
+ .map(paymentEvent -> PaymentEventResponse.from(paymentEvent, paymentEvents.getChargeId(), paymentEventsLink.toString()))
+ .collect(Collectors.toList());
+ PaymentLinksForEvents paymentLinksForEvents = new PaymentLinksForEvents();
+ paymentLinksForEvents.addSelf(eventsLink.toString());
+ return new PaymentEventsResponse(paymentEvents.getChargeId(), events, paymentLinksForEvents);
+ }
+
+ public static PaymentEventsResponse from(TransactionEvents transactionEvents, URI paymentEventsLink, URI eventsLink) {
+ List events = transactionEvents.getEvents().stream()
+ .map(paymentEvent -> PaymentEventResponse.from(paymentEvent, transactionEvents.getTransactionId(), paymentEventsLink.toString()))
+ .collect(Collectors.toList());
+ PaymentLinksForEvents paymentLinksForEvents = new PaymentLinksForEvents();
+ paymentLinksForEvents.addSelf(eventsLink.toString());
+ return new PaymentEventsResponse(transactionEvents.getTransactionId(), events, paymentLinksForEvents);
+ }
+
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93",
+ description = "The unique ID GOV.UK Pay automatically associated with this payment when you created it.",
+ accessMode = READ_ONLY)
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public List getEvents() {
+ return events;
+ }
+
+ public PaymentLinksForEvents getLinks() {
+ return links;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentEvents{" +
+ "paymentId='" + paymentId + '\'' +
+ ", events=" + events +
+ ", links=" + links +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentSettlementSummary.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentSettlementSummary.java
new file mode 100644
index 000000000..f74fe65be
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentSettlementSummary.java
@@ -0,0 +1,52 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Schema(name = "PaymentSettlementSummary", description = "A structure representing information about a settlement")
+public class PaymentSettlementSummary {
+
+ @JsonProperty("capture_submit_time")
+ private String captureSubmitTime;
+
+ @JsonProperty("captured_date")
+ private String capturedDate;
+
+ @JsonProperty("settled_date")
+ private String settledDate;
+
+ public PaymentSettlementSummary() {}
+
+ public PaymentSettlementSummary(String captureSubmitTime, String capturedDate, String settledDate) {
+ this.captureSubmitTime = captureSubmitTime;
+ this.capturedDate = capturedDate;
+ this.settledDate = settledDate;
+ }
+
+ @Schema(description = "The date and time GOV.UK Pay asked your payment service provider " +
+ "to take the payment from your user’s account. " +
+ "This value uses Coordinated Universal Time (UTC) and ISO 8601 format - `YYYY-MM-DDThh:mm:ss.SSSZ`",
+ example = "2016-01-21T17:15:00.000Z", accessMode = READ_ONLY)
+ public String getCaptureSubmitTime() {
+ return captureSubmitTime;
+ }
+
+ @Schema(description = "The date your payment service provider took the payment from your user. " +
+ "This value uses ISO 8601 format - `YYYY-MM-DD`",
+ example = "2016-01-21", accessMode = READ_ONLY)
+ public String getCapturedDate() {
+ return capturedDate;
+ }
+
+ @Schema(description = "The date that the transaction was paid into the service's account.", example = "2016-01-21",
+ accessMode = READ_ONLY)
+ public String getSettledDate() {
+ return settledDate;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentState.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentState.java
new file mode 100644
index 000000000..94ee25ad9
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PaymentState.java
@@ -0,0 +1,122 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Objects;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Schema(name = "PaymentState", description = "A structure representing the current state of the payment in its lifecycle.")
+public class PaymentState {
+ @JsonProperty("status")
+ private String status;
+
+ @JsonProperty("finished")
+ private boolean finished;
+
+ @JsonProperty("message")
+ private String message;
+
+ @JsonProperty("code")
+ private String code;
+
+ @JsonProperty("can_retry")
+ private Boolean canRetry;
+
+
+ public static PaymentState createPaymentState(JsonNode node) {
+ return new PaymentState(
+ node.get("status").asText(),
+ node.get("finished").asBoolean(),
+ node.has("message") ? node.get("message").asText() : null,
+ node.has("code") ? node.get("code").asText() : null,
+ node.has("can_retry") ? node.get("can_retry").asBoolean() : null
+ );
+ }
+
+ public PaymentState() {
+ }
+
+ public PaymentState(String status, boolean finished) {
+ this(status, finished, null, null);
+ }
+
+ public PaymentState(String status, boolean finished, String message, String code) {
+ this(status, finished, message, code, null);
+ }
+
+ public PaymentState(String status, boolean finished, String message, String code, Boolean canRetry) {
+ this.status = status;
+ this.finished = finished;
+ this.message = message;
+ this.code = code;
+ this.canRetry = canRetry;
+ }
+
+ @Schema(description = "Where the payment is in [the payment status lifecycle]" +
+ "(https://docs.payments.service.gov.uk/api_reference/#payment-status-meanings).",
+ example = "created", accessMode = READ_ONLY)
+ public String getStatus() {
+ return status;
+ }
+
+ @Schema(description = "Indicates whether a payment journey is finished.", accessMode = READ_ONLY)
+ public boolean isFinished() {
+ return finished;
+ }
+
+ @Schema(description = "A description of what went wrong with this payment. `message` only appears if the payment failed.",
+ example = "User cancelled the payment", accessMode = READ_ONLY)
+ public String getMessage() {
+ return message;
+ }
+
+ @Schema(description = "An [API error code](https://docs.payments.service.gov.uk/api_reference/#gov-uk-pay-api-error-codes)" +
+ "that explains why the payment failed. `code` only appears if the payment failed.", example = "P010",
+ accessMode = READ_ONLY)
+ public String getCode() {
+ return code;
+ }
+
+ @Schema(description = "If `can_retry` is `true`, you can use this agreement to try to take another recurring payment. " +
+ "If `can_retry` is `false`, you cannot take another recurring payment with this agreement. " +
+ "`can_retry` only appears on failed payments that were attempted using an agreement for recurring payments.",
+ nullable = true, accessMode = READ_ONLY)
+ public Boolean getCanRetry() {
+ return canRetry;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentState{" +
+ "status='" + status + '\'' +
+ ", finished='" + finished + '\'' +
+ ", message=" + message +
+ ", code=" + code +
+ (canRetry != null ? ", canRetry=" +canRetry : "") +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PaymentState that = (PaymentState) o;
+ return finished == that.finished &&
+ Objects.equals(status, that.status) &&
+ Objects.equals(message, that.message) &&
+ Objects.equals(code, that.code) &&
+ Objects.equals(canRetry, that.canRetry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, finished, message, code, canRetry);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PrefilledCardholderDetails.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PrefilledCardholderDetails.java
new file mode 100644
index 000000000..47449dd5a
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/PrefilledCardholderDetails.java
@@ -0,0 +1,56 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Size;
+import java.util.Objects;
+import java.util.Optional;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class PrefilledCardholderDetails {
+
+ @JsonProperty("cardholder_name")
+ @Schema(name = "cardholder_name",
+ description = "The cardholder name you prefilled when you created this payment.",
+ example = "J. Bogs")
+ @Size(max = 255, message = "Must be less than or equal to {max} characters length")
+ private String cardholderName;
+
+ @Schema(name = "billing_address", description = "prefilled billing address")
+ @JsonProperty("billing_address")
+ @Valid
+ private Address billingAddress;
+
+ public Optional getBillingAddress() {
+ return Optional.ofNullable(billingAddress);
+ }
+
+ public Optional getCardholderName() {
+ return Optional.ofNullable(cardholderName);
+ }
+
+ public void setCardholderName(String cardholderName) {
+ this.cardholderName = cardholderName;
+ }
+
+ public void setAddress(String addressLine1, String addressLine2, String postcode, String city, String country) {
+ this.billingAddress = new Address(addressLine1, addressLine2, postcode, city, country);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrefilledCardholderDetails that = (PrefilledCardholderDetails) o;
+ return Objects.equals(cardholderName, that.cardholderName) &&
+ Objects.equals(billingAddress, that.billingAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cardholderName, billingAddress);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundFromConnector.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundFromConnector.java
new file mode 100644
index 000000000..874b6a2ab
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundFromConnector.java
@@ -0,0 +1,43 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefundFromConnector {
+
+ @JsonProperty(value = "refund_id")
+ private String refundId;
+
+ @JsonProperty(value = "created_date")
+ private String createdDate;
+
+ private Long amount;
+ private String status;
+
+ public String getRefundId() {
+ return refundId;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public String toString() {
+ return "RefundFromConnector{" +
+ "refundId='" + refundId + '\'' +
+ ", createdDate='" + createdDate + '\'' +
+ ", amount=" + amount +
+ ", status='" + status + '\'' +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundResponse.java
new file mode 100644
index 000000000..3c1520341
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundResponse.java
@@ -0,0 +1,125 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.RefundLinksForSearch;
+import uk.gov.pay.api.model.ledger.RefundTransactionFromLedger;
+
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@Schema(name = "Refund")
+public class RefundResponse {
+
+ @Schema(example = "act4c33g40j3edfmi8jknab84x",
+ description = "The unique ID GOV.UK Pay automatically associated with this refund when you created it.",
+ accessMode = READ_ONLY)
+ private String refundId;
+ @Schema(example = "2017-01-10T16:52:07.855Z",
+ description = "The date and time you created this refund. " +
+ "This value uses Coordinated Universal Time (UTC) and ISO 8601 format - `YYYY-MM-DDThh:mm:ss.SSSZ`",
+ accessMode = READ_ONLY)
+ private String createdDate;
+ @Schema(example = "120", description = "The amount refunded to the user in pence.", accessMode = READ_ONLY)
+ private Long amount;
+ @JsonProperty("_links")
+ private RefundLinksForSearch links;
+ @Schema(example = "success",
+ description = "The [status of the refund](https://docs.payments.service.gov.uk/refunding_payments/#checking-the-status-of-a-refund-status).",
+ allowableValues = {"submitted", "success", "error"}, accessMode = READ_ONLY)
+ private String status;
+ @Schema(accessMode = READ_ONLY)
+ private RefundSettlementSummary settlementSummary;
+
+ private RefundResponse(RefundFromConnector refund, URI selfLink, URI paymentLink) {
+ this.refundId = refund.getRefundId();
+ this.amount = refund.getAmount();
+ this.status = refund.getStatus();
+ this.createdDate = refund.getCreatedDate();
+ this.links = new RefundLinksForSearch();
+ this.settlementSummary = new RefundSettlementSummary();
+
+ links.addSelf(selfLink.toString());
+ links.addPayment(paymentLink.toString());
+ }
+
+ private RefundResponse(RefundTransactionFromLedger refund, URI selfLink, URI paymentLink) {
+ this.refundId = refund.getTransactionId();
+ this.amount = refund.getAmount();
+ this.status = refund.getState().getStatus();
+ this.createdDate = refund.getCreatedDate();
+ this.settlementSummary = refund.getSettlementSummary();
+ this.links = new RefundLinksForSearch();
+
+ links.addSelf(selfLink.toString());
+ links.addPayment(paymentLink.toString());
+ }
+
+ private RefundResponse(String refundId, Long amount, String status,
+ String createdDate, URI selfLink, URI paymentLink) {
+ this.refundId = refundId;
+ this.amount = amount;
+ this.status = status;
+ this.createdDate = createdDate;
+ this.links = new RefundLinksForSearch();
+
+ links.addSelf(selfLink.toString());
+ links.addPayment(paymentLink.toString());
+ }
+
+ public static RefundResponse from(RefundFromConnector refund, URI selfLink, URI paymentLink) {
+ return new RefundResponse(refund, selfLink, paymentLink);
+ }
+
+ public static RefundResponse from(RefundTransactionFromLedger refund, URI selfLink, URI paymentLink) {
+ return new RefundResponse(refund, selfLink, paymentLink);
+ }
+
+ //todo: remove after full refactoring of PaymentRefundsResource (to use service layer)
+ public static RefundResponse valueOf(RefundFromConnector refundEntity, String paymentId, String baseUrl) {
+ URI selfLink = UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/refunds/{refundId}")
+ .build(paymentId, refundEntity.getRefundId());
+
+ URI paymentLink = UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}")
+ .build(paymentId);
+
+ return new RefundResponse(
+ refundEntity.getRefundId(),
+ refundEntity.getAmount(),
+ refundEntity.getStatus(),
+ refundEntity.getCreatedDate(),
+ selfLink,
+ paymentLink);
+ }
+
+ public String getRefundId() {
+ return refundId;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public RefundLinksForSearch getLinks() {
+ return links;
+ }
+
+ public RefundSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSettlementSummary.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSettlementSummary.java
new file mode 100644
index 000000000..475636f39
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSettlementSummary.java
@@ -0,0 +1,32 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Schema(name = "RefundSettlementSummary", description = "A structure representing information about a settlement for refunds")
+public class RefundSettlementSummary {
+
+ @JsonProperty("settled_date")
+ private String settledDate;
+
+ public RefundSettlementSummary() {}
+
+ public RefundSettlementSummary(String settledDate) {
+ this.settledDate = settledDate;
+ }
+
+ @Schema(description = "The date Stripe took the refund from a payout to your bank account. " +
+ "`settled_date` only appears if Stripe has taken the refund. " +
+ "This value uses Coordinated Universal Time (UTC) and ISO 8601 format - `YYYY-MM-DD`.",
+ example = "2016-01-21",
+ accessMode = READ_ONLY)
+ public String getSettledDate() {
+ return settledDate;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSummary.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSummary.java
new file mode 100644
index 000000000..4e34ef088
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundSummary.java
@@ -0,0 +1,45 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@Schema(name = "RefundSummary", description = "A structure representing the refunds availability")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefundSummary {
+
+ private String status;
+
+ @JsonProperty("amount_available")
+ private long amountAvailable;
+
+ @JsonProperty("amount_submitted")
+ private long amountSubmitted;
+
+ public RefundSummary() {}
+
+ public RefundSummary(String status, long amountAvailable, long amountSubmitted) {
+ this.status = status;
+ this.amountAvailable = amountAvailable;
+ this.amountSubmitted = amountSubmitted;
+ }
+
+ @Schema(description = "Whether you can [refund the payment]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/#checking-the-status-of-a-refund-status).",
+ example = "available")
+ public String getStatus() {
+ return status;
+ }
+
+ @Schema(description = "How much you can refund to the user, in pence.", example = "100", accessMode = READ_ONLY)
+ public long getAmountAvailable() {
+ return amountAvailable;
+ }
+
+ @Schema(description = "How much you’ve already refunded to the user, in pence.", accessMode = READ_ONLY)
+ public long getAmountSubmitted() {
+ return amountSubmitted;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsFromConnector.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsFromConnector.java
new file mode 100644
index 000000000..63c56b474
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsFromConnector.java
@@ -0,0 +1,53 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefundsFromConnector {
+
+ public class Embedded {
+ private List refunds;
+
+ public Embedded() {
+ }
+
+ public List getRefunds() {
+ return refunds;
+ }
+
+ @Override
+ public String toString() {
+ return "Embedded{" +
+ "refunds=" + refunds +
+ '}';
+ }
+ }
+
+ @JsonProperty(value = "payment_id")
+ private String paymentId;
+
+ @JsonProperty(value = "_embedded")
+ private Embedded embedded;
+
+ public RefundsFromConnector() {
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public Embedded getEmbedded() {
+ return embedded;
+ }
+
+ @Override
+ public String toString() {
+ return "RefundsFromConnector{" +
+ "paymentId='" + paymentId + '\'' +
+ ", " + embedded +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsResponse.java
new file mode 100644
index 000000000..7fc863f28
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RefundsResponse.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.RefundLinksForSearch;
+
+import java.util.List;
+
+public class RefundsResponse {
+
+ @JsonProperty("payment_id")
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93", description = "The unique ID GOV.UK Pay associated with this payment when you created it.")
+ private String paymentId;
+ @JsonProperty("_links")
+ private RefundLinksForSearch links;
+ @JsonProperty("_embedded")
+ @Schema(name = "_embedded")
+ private EmbeddedRefunds embedded;
+
+ private RefundsResponse(String paymentId, List refundsForPayment, String selfLink, String paymentLink) {
+ this.paymentId = paymentId;
+
+ embedded = new EmbeddedRefunds();
+ embedded.refunds = refundsForPayment;
+
+ this.links = new RefundLinksForSearch();
+ this.links.addPayment(paymentLink);
+ this.links.addSelf(selfLink);
+ }
+
+ public static RefundsResponse from(String paymentId,
+ List refundsForPayment,
+ String selfLink,
+ String paymentLink) {
+ return new RefundsResponse(paymentId, refundsForPayment, selfLink, paymentLink);
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public RefundLinksForSearch getLinks() {
+ return links;
+ }
+
+ @Schema(hidden = true)
+ public EmbeddedRefunds getEmbedded() {
+ return embedded;
+ }
+
+ @Schema(hidden = true)
+ public class EmbeddedRefunds {
+ private List refunds;
+
+ public EmbeddedRefunds() {
+ }
+
+ public List getRefunds() {
+ return refunds;
+ }
+
+ @Override
+ public String toString() {
+ return "Embedded{" +
+ "refunds=" + refunds +
+ '}';
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RequestError.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RequestError.java
new file mode 100644
index 000000000..510f48ba5
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/RequestError.java
@@ -0,0 +1,185 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+import static com.google.common.collect.ObjectArrays.concat;
+import static java.lang.String.format;
+
+@Schema(name = "RequestError", description = "A Request Error response")
+@JsonInclude(NON_NULL)
+public class RequestError {
+
+ public enum Code {
+
+ CREATE_PAYMENT_ACCOUNT_ERROR("P0199", "There is an error with this account. Contact support with your error code - https://www.payments.service.gov.uk/support/ ."),
+ CREATE_PAYMENT_CONNECTOR_ERROR("P0198", "Downstream system error"),
+ CREATE_PAYMENT_PARSING_ERROR("P0197", "Unable to parse JSON"),
+ CREATE_PAYMENT_MOTO_NOT_ENABLED("P0196", "MOTO payments are not enabled for this account. Please contact support if you would like to process MOTO payments - https://www.payments.service.gov.uk/support/ ."),
+ CREATE_PAYMENT_AUTHORISATION_API_NOT_ENABLED("P0195","Using authorisation_mode of moto_api is not allowed for this account"),
+ CREATE_PAYMENT_AGREEMENT_ID_ERROR("P0103", "Invalid attribute value: set_up_agreement. Agreement ID does not exist"),
+ CREATE_PAYMENT_CARD_NUMBER_IN_PAYMENT_LINK_REFERENCE_ERROR("P0105", "%s"),
+
+ GENERIC_MISSING_FIELD_ERROR_MESSAGE_FROM_CONNECTOR("P0101", "%s"),
+ GENERIC_VALIDATION_EXCEPTION_MESSAGE_FROM_CONNECTOR("P0102", "%s"),
+ GENERIC_UNEXPECTED_FIELD_ERROR_MESSAGE_FROM_CONNECTOR("P0104", "%s"),
+
+ CREATE_PAYMENT_MISSING_FIELD_ERROR("P0101", "Missing mandatory attribute: %s"),
+ CREATE_PAYMENT_UNEXPECTED_FIELD_ERROR("P0104", "Unexpected attribute: %s"),
+ CREATE_PAYMENT_VALIDATION_ERROR("P0102", "Invalid attribute value: %s. %s"),
+ CREATE_PAYMENT_HEADER_VALIDATION_ERROR("P0102", "%s"),
+ CREATE_PAYMENT_IDEMPOTENCY_KEY_ALREADY_USED("P0191", "The `Idempotency-Key` you sent in the request header has already been used to create a payment."),
+
+ GET_PAYMENT_NOT_FOUND_ERROR("P0200", "Not found"),
+ GET_PAYMENT_CONNECTOR_ERROR("P0298", "Downstream system error"),
+
+ GET_PAYMENT_EVENTS_NOT_FOUND_ERROR("P0300", "Not found"),
+ GET_PAYMENT_EVENTS_CONNECTOR_ERROR("P0398", "Downstream system error"),
+
+ SEARCH_PAYMENTS_VALIDATION_ERROR("P0401", "Invalid parameters: %s. See Public API documentation for the correct data formats"),
+ SEARCH_PAYMENTS_NOT_FOUND("P0402", "Page not found"),
+ SEARCH_PAYMENTS_CONNECTOR_ERROR("P0498", "Downstream system error"),
+
+ SEARCH_AGREEMENTS_VALIDATION_ERROR("P2401", "Invalid parameters: %s. See Public API documentation for the correct data formats"),
+ SEARCH_AGREEMENTS_NOT_FOUND("P2402", "Page not found"),
+ SEARCH_AGREEMENTS_LEDGER_ERROR("P2498", "Downstream system error"),
+
+ CANCEL_PAYMENT_NOT_FOUND_ERROR("P0500", "Not found"),
+ CANCEL_PAYMENT_CONNECTOR_BAD_REQUEST_ERROR("P0501", "Cancellation of payment failed"),
+ CANCEL_PAYMENT_CONNECTOR_CONFLICT_ERROR("P0502", "Cancellation of payment failed"),
+ CANCEL_PAYMENT_CONNECTOR_ERROR("P0598", "Downstream system error"),
+
+ CAPTURE_PAYMENT_NOT_FOUND_ERROR("P1000", "Not found"),
+ CAPTURE_PAYMENT_CONNECTOR_BAD_REQUEST_ERROR("P1001", "Capture of payment failed"),
+ CAPTURE_PAYMENT_CONNECTOR_CONFLICT_ERROR("P1003", "Payment cannot be captured"),
+ CAPTURE_PAYMENT_CONNECTOR_ERROR("P1098", "Downstream system error"),
+
+ CREATE_PAYMENT_REFUND_CONNECTOR_ERROR("P0698", "Downstream system error"),
+ CREATE_PAYMENT_REFUND_PARSING_ERROR("P0697", "Unable to parse JSON"),
+ CREATE_PAYMENT_REFUND_NOT_FOUND_ERROR("P0600", "Not found"),
+ CREATE_PAYMENT_REFUND_MISSING_FIELD_ERROR("P0601", "Missing mandatory attribute: %s"),
+ CREATE_PAYMENT_REFUND_VALIDATION_ERROR("P0602", "Invalid attribute value: %s. %s"),
+ CREATE_PAYMENT_REFUND_NOT_AVAILABLE("P0603", "The payment is not available for refund. Payment refund status: %s"),
+ CREATE_PAYMENT_REFUND_NOT_AVAILABLE_DUE_TO_DISPUTE("P0603", "The payment is disputed and cannot be refunded"),
+ CREATE_PAYMENT_REFUND_AMOUNT_AVAILABLE_MISMATCH("P0604", "Refund amount available mismatch."),
+
+ GET_PAYMENT_REFUND_NOT_FOUND_ERROR("P0700", "Not found"),
+ GET_PAYMENT_REFUND_CONNECTOR_ERROR("P0798", "Downstream system error"),
+
+ GET_PAYMENT_REFUNDS_NOT_FOUND_ERROR("P0800", "Not found"),
+ GET_PAYMENT_REFUNDS_CONNECTOR_ERROR("P0898", "Downstream system error"),
+
+ TOO_MANY_REQUESTS_ERROR("P0900", "Too many requests"),
+ REQUEST_DENIED_ERROR("P0920", "Request blocked by security rules. Please consult API documentation for more information."),
+ RESOURCE_ACCESS_FORBIDDEN("P0930", "Access to this resource is not enabled for this account. Contact support with your error code - https://www.payments.service.gov.uk/support/ ."),
+ ACCOUNT_NOT_LINKED_WITH_PSP("P0940", "Account is not fully configured. Please refer to documentation to setup your account or contact support with your error code - https://www.payments.service.gov.uk/support/ ."),
+ ACCOUNT_DISABLED("P0941", "GOV.UK Pay has disabled payment and refund creation on this account. Contact support with your error code - https://www.payments.service.gov.uk/support/ ."),
+ RECURRING_CARD_PAYMENTS_NOT_ALLOWED_ERROR("P0942", "Recurring card payments are currently disabled for this service. Contact support with your error code - https://www.payments.service.gov.uk/support/"),
+
+ SEARCH_REFUNDS_VALIDATION_ERROR("P1101", "Invalid parameters: %s. See Public API documentation for the correct data formats"),
+ SEARCH_REFUNDS_NOT_FOUND("P1100", "Page not found"),
+ SEARCH_REFUNDS_CONNECTOR_ERROR("P1898", "Downstream system error"),
+
+ AUTHORISATION_CARD_NUMBER_REJECTED_ERROR("P0010", "%s"),
+ AUTHORISATION_REJECTED_ERROR("P0010", "%s"),
+ AUTHORISATION_ERROR("P0050", "%s"),
+ AUTHORISATION_TIMEOUT_ERROR("P0050", "%s"),
+ AUTHORISATION_ONE_TIME_TOKEN_ALREADY_USED_ERROR("P1212", "%s"),
+ AUTHORISATION_ONE_TIME_TOKEN_INVALID_ERROR("P1211", "%s"),
+
+ CREATE_AGREEMENT_CONNECTOR_ERROR("P2198", "Downstream system error"),
+ CREATE_AGREEMENT_PARSING_ERROR("P2197", "Unable to parse JSON"),
+
+ CREATE_AGREEMENT_MISSING_FIELD_ERROR("P2101", "Missing mandatory attribute: %s"),
+ CREATE_AGREEMENT_VALIDATION_ERROR("P2102", "Invalid attribute value: %s. %s"),
+ GET_AGREEMENT_NOT_FOUND_ERROR("P2200", "Not found"),
+ GET_AGREEMENT_LEDGER_ERROR("P2298", "Downstream system error"),
+
+ CANCEL_AGREEMENT_NOT_FOUND_ERROR("P2500", "Not found"),
+ CANCEL_AGREEMENT_CONNECTOR_BAD_REQUEST_ERROR("P2501", "Cancellation of agreement failed"),
+ CANCEL_AGREEMENT_CONNECTOR_ERROR("P2598", "Downstream system error"),
+
+ SEARCH_DISPUTES_VALIDATION_ERROR("P0401", "Invalid parameters: %s. See Public API documentation for the correct data formats"),
+ GET_DISPUTE_LEDGER_ERROR("P0498", "Downstream system error"),
+ SEARCH_DISPUTES_NOT_FOUND("P0402", "Page not found");
+
+ private String value;
+ private String format;
+
+ Code(String value, String format) {
+ this.value = value;
+ this.format = format;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+ }
+
+ private String field;
+ private String header;
+ private final Code code;
+ private final String description;
+
+ public static RequestError aRequestError(Code code, Object... parameters) {
+ return new RequestError(code, parameters);
+ }
+
+ public static RequestError aRequestError(String fieldName, Code code, Object... parameters) {
+ return new RequestError(null, fieldName, code, parameters);
+ }
+
+ public static RequestError aHeaderRequestError(String header, Code code, Object... parameters) {
+ return new RequestError(header, null, code, parameters);
+ }
+
+ private RequestError(Code code, Object... parameters) {
+ this.code = code;
+ this.description = format(code.getFormat(), parameters);
+ }
+
+ private RequestError(String header, String fieldName, Code code, Object... parameters) {
+ this.header = header;
+ this.field = fieldName;
+ this.code = code;
+ this.description = format(code.getFormat(), fieldName != null ? concat(fieldName, parameters) : parameters);
+ }
+
+ @Schema(example = "amount", description = "The parameter in your request that's causing the error.")
+ public String getField() {
+ return field;
+ }
+
+ @Schema(example = "Idempotency-Key", description = "The header in your request that's causing the error.")
+ public String getHeader() {
+ return header;
+ }
+
+ @Schema(example = "P0102",
+ description = "An [API error code](https://docs.payments.service.gov.uk/api_reference/#gov-uk-pay-api-error-codes)" +
+ "that explains why the payment failed.
`code` only appears if the payment failed.")
+ public String getCode() {
+ return code.value();
+ }
+
+ @Schema(example = "Invalid attribute value: amount. Must be less than or equal to 10000000",
+ description = "Additional details about the error.")
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return "RequestError{" +
+ "field=" + field +
+ ", code=" + code.value() +
+ ", name=" + code +
+ ", description='" + description + '\'' +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ThreeDSecure.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ThreeDSecure.java
new file mode 100644
index 000000000..a14df8afb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ThreeDSecure.java
@@ -0,0 +1,27 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(name = "ThreeDSecure", description = "Object containing information about the 3D Secure authentication of the payment.")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ThreeDSecure {
+
+ @JsonProperty("required")
+ @Schema(name = "required", description = "Indicates if this payment was authorised with 3D Secure authentication. " +
+ "`required` is `true` if the payment required 3D Secure authentication.")
+ private boolean required;
+
+ public ThreeDSecure() {
+ }
+
+ public ThreeDSecure(boolean required) {
+ this.required = required;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TokenPaymentType.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TokenPaymentType.java
new file mode 100644
index 000000000..c0b9f309c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TokenPaymentType.java
@@ -0,0 +1,16 @@
+package uk.gov.pay.api.model;
+
+//to be shared between Public Auth and Public Api
+public enum TokenPaymentType {
+ CARD("Card Payment");
+
+ private String friendlyName;
+
+ TokenPaymentType(String friendlyName) {
+ this.friendlyName = friendlyName;
+ }
+
+ public String getFriendlyName() {
+ return this.friendlyName;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvent.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvent.java
new file mode 100644
index 000000000..3b13b27ee
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvent.java
@@ -0,0 +1,20 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TransactionEvent {
+
+ private PaymentState state;
+ private String timestamp;
+
+ public TransactionEvent() {}
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvents.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvents.java
new file mode 100644
index 000000000..8e39ad941
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionEvents.java
@@ -0,0 +1,25 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class TransactionEvents {
+
+ private String transactionId;
+ private List events;
+
+ public TransactionEvents() {}
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public List getEvents() {
+ return events;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionResponse.java
new file mode 100644
index 000000000..db36fa48d
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/TransactionResponse.java
@@ -0,0 +1,202 @@
+package uk.gov.pay.api.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import uk.gov.pay.api.utils.CustomSupportedLanguageDeserializer;
+import uk.gov.pay.api.utils.WalletDeserializer;
+import uk.gov.service.payments.commons.api.json.ExternalMetadataDeserialiser;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TransactionResponse {
+
+ @JsonProperty("transaction_id")
+ private String transactionId;
+
+ @JsonProperty("return_url")
+ private String returnUrl;
+
+ @JsonProperty("payment_provider")
+ private String paymentProvider;
+
+ @JsonProperty("links")
+ private List links = new ArrayList<>();
+
+ @JsonProperty(value = "refund_summary")
+ private RefundSummary refundSummary;
+
+ @JsonProperty(value = "settlement_summary")
+ private PaymentSettlementSummary settlementSummary;
+
+ @JsonProperty(value = "card_details")
+ private CardDetailsFromResponse cardDetails;
+
+ private Long amount;
+
+ private PaymentState state;
+
+ private String description;
+
+ private String reference;
+
+ private String email;
+
+ @JsonDeserialize(using = CustomSupportedLanguageDeserializer.class)
+ private SupportedLanguage language;
+
+ @JsonProperty(value = "delayed_capture")
+ private boolean delayedCapture;
+
+ private boolean moto;
+
+ @JsonProperty("corporate_card_surcharge")
+ private Long corporateCardSurcharge;
+
+ @JsonProperty("total_amount")
+ private Long totalAmount;
+
+ @JsonProperty("fee")
+ private Long fee;
+
+ @JsonProperty("net_amount")
+ private Long netAmount;
+
+ @JsonProperty(value = "created_date")
+ private String createdDate;
+
+ @JsonProperty(value = "gateway_transaction_id")
+ private String gatewayTransactionId;
+
+ @JsonDeserialize(using = ExternalMetadataDeserialiser.class)
+ private ExternalMetadata metadata;
+
+ @JsonProperty("authorisation_summary")
+ private AuthorisationSummary authorisationSummary;
+
+ @JsonProperty("authorisation_mode")
+ private AuthorisationMode authorisationMode;
+
+ @JsonProperty("wallet_type")
+ @JsonDeserialize(using = WalletDeserializer.class)
+ private Wallet walletType;
+
+ public Optional getMetadata() {
+ return Optional.ofNullable(metadata);
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public String getReturnUrl() {
+ return returnUrl;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public SupportedLanguage getLanguage() {
+ return language;
+ }
+
+ public boolean getDelayedCapture() {
+ return delayedCapture;
+ }
+
+ public boolean isMoto() {
+ return moto;
+ }
+
+ public Long getCorporateCardSurcharge() {
+ return corporateCardSurcharge;
+ }
+
+ public Long getTotalAmount() {
+ return totalAmount;
+ }
+
+ public Long getFee() {
+ return fee;
+ }
+
+ public Long getNetAmount() {
+ return netAmount;
+ }
+
+ public String getPaymentProvider() {
+ return paymentProvider;
+ }
+
+ /**
+ * card brand is no longer a top level charge property. It is now at `card_details.card_brand` attribute
+ * We still need to support `v1` clients with a top level card brand attribute to keep support their integrations.
+ *
+ * @return
+ */
+ @JsonProperty("card_brand")
+ public String getCardBrand() {
+ return cardDetails != null ? cardDetails.getCardBrand() : "";
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public List getLinks() {
+ return links;
+ }
+
+ public RefundSummary getRefundSummary() {
+ return refundSummary;
+ }
+
+ public PaymentSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ public CardDetailsFromResponse getCardDetailsFromResponse() {
+ return cardDetails;
+ }
+
+ public String getGatewayTransactionId() {
+ return gatewayTransactionId;
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public AuthorisationSummary getAuthorisationSummary() {
+ return authorisationSummary;
+ }
+
+ public AuthorisationMode getAuthorisationMode() {
+ return authorisationMode;
+ }
+
+ public Optional getWalletType() {
+ return Optional.ofNullable(walletType);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Wallet.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Wallet.java
new file mode 100644
index 000000000..c708be054
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/Wallet.java
@@ -0,0 +1,17 @@
+package uk.gov.pay.api.model;
+
+public enum Wallet {
+
+ APPLE_PAY("Apple Pay"),
+ GOOGLE_PAY("Google Pay");
+
+ private final String titleCase;
+
+ Wallet(String titleCase) {
+ this.titleCase = titleCase;
+ }
+
+ public String getTitleCase() {
+ return titleCase;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeSettlementSummary.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeSettlementSummary.java
new file mode 100644
index 000000000..c0c3ecf7f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeSettlementSummary.java
@@ -0,0 +1,33 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Schema(name = "SettlementSummary", description = "Contains information about when a lost dispute was settled. A dispute is settled when your payment service provider takes it from a payout to your bank account. 'settlement_summary' only appears if you lost the dispute.")
+public class DisputeSettlementSummary {
+
+ @JsonProperty("settled_date")
+ private String settledDate;
+
+ public DisputeSettlementSummary() {}
+
+ public DisputeSettlementSummary(String settledDate) {
+ this.settledDate = settledDate;
+ }
+
+ @Schema(description = "The date your payment service provider took the disputed payment " +
+ "and dispute fee from a payout to your bank account. " +
+ "This value appears in ISO 8601 format - `YYYY-MM-DD`. " +
+ "`settled_date` only appears if you lost the dispute.",
+ example = "2022-07-28",
+ accessMode = READ_ONLY)
+ public String getSettledDate() {
+ return settledDate;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeTransactionFromLedger.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeTransactionFromLedger.java
new file mode 100644
index 000000000..eb502d612
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/DisputeTransactionFromLedger.java
@@ -0,0 +1,60 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DisputeTransactionFromLedger {
+ private Long amount;
+ private String createdDate;
+ private String transactionId;
+ private String evidenceDueDate;
+ private Long fee;
+ private Long netAmount;
+ private String parentTransactionId;
+ private String reason;
+ private DisputeSettlementSummary settlementSummary;
+ private TransactionState state;
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public TransactionState getState() {
+ return state;
+ }
+
+ public Long getNetAmount() {
+ return netAmount;
+ }
+
+ public Long getFee() {
+ return fee;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public DisputeSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ public String getParentTransactionId() {
+ return parentTransactionId;
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public String getEvidenceDueDate() {
+ return evidenceDueDate;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundTransactionFromLedger.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundTransactionFromLedger.java
new file mode 100644
index 000000000..7b5178e57
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundTransactionFromLedger.java
@@ -0,0 +1,56 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.model.RefundSettlementSummary;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefundTransactionFromLedger {
+
+ Long amount;
+ String description;
+ String reference;
+ String createdDate;
+ String refundedBy;
+ String transactionId;
+ String parentTransactionId;
+ TransactionState state;
+ RefundSettlementSummary settlementSummary;
+ public Long getAmount() {
+ return amount;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public String getRefundedBy() {
+ return refundedBy;
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ public String getParentTransactionId() {
+ return parentTransactionId;
+ }
+
+ public TransactionState getState() {
+ return state;
+ }
+
+ public RefundSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundsFromLedger.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundsFromLedger.java
new file mode 100644
index 000000000..09bcfcc0f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/RefundsFromLedger.java
@@ -0,0 +1,23 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RefundsFromLedger {
+
+ String parentTransactionId;
+ List transactions;
+
+ public String getParentTransactionId() {
+ return parentTransactionId;
+ }
+
+ public List getTransactions() {
+ return transactions;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchDisputesResponseFromLedger.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchDisputesResponseFromLedger.java
new file mode 100644
index 000000000..933e31ebb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchDisputesResponseFromLedger.java
@@ -0,0 +1,49 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SearchDisputesResponseFromLedger implements SearchPagination {
+
+ @JsonProperty("total")
+ private int total;
+
+ @JsonProperty("count")
+ private int count;
+
+ @JsonProperty("page")
+ private int page;
+
+ @JsonProperty("results")
+ private List disputes;
+
+ @JsonProperty("_links")
+ private SearchNavigationLinks links = new SearchNavigationLinks();
+
+ public int getTotal() {
+ return total;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+
+ public List getDisputes() {
+ return disputes;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchRefundsResponseFromLedger.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchRefundsResponseFromLedger.java
new file mode 100644
index 000000000..bf9b101af
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/SearchRefundsResponseFromLedger.java
@@ -0,0 +1,49 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SearchRefundsResponseFromLedger implements SearchPagination {
+
+ @JsonProperty("total")
+ private int total;
+
+ @JsonProperty("count")
+ private int count;
+
+ @JsonProperty("page")
+ private int page;
+
+ @JsonProperty("results")
+ private List refunds;
+
+ @JsonProperty("_links")
+ private SearchNavigationLinks links = new SearchNavigationLinks();
+
+ public List getRefunds() {
+ return refunds;
+ }
+
+ public int getTotal() {
+ return total;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/TransactionState.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/TransactionState.java
new file mode 100644
index 000000000..4cf21f326
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/ledger/TransactionState.java
@@ -0,0 +1,39 @@
+package uk.gov.pay.api.model.ledger;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TransactionState {
+
+ private String status;
+ private boolean finished;
+ private String message;
+ private String code;
+
+ public String getStatus() {
+ return status;
+ }
+
+ public boolean isFinished() {
+ return finished;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public TransactionState() {
+ }
+
+ public TransactionState(String status, boolean finished) {
+ this.status = status;
+ this.finished = finished;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/DisputeLinksForSearch.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/DisputeLinksForSearch.java
new file mode 100644
index 000000000..7d6742f65
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/DisputeLinksForSearch.java
@@ -0,0 +1,21 @@
+package uk.gov.pay.api.model.links;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static javax.ws.rs.HttpMethod.GET;
+
+@Schema(name = "DisputeLinksForSearch", description = "links for search dispute resource")
+public class DisputeLinksForSearch {
+
+ private static final String PAYMENT = "payment";
+
+ private Link payment;
+
+ public Link getPayment() {
+ return payment;
+ }
+
+ public void addPayment(String href) {
+ this.payment = new Link(href, GET);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/Link.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/Link.java
new file mode 100644
index 000000000..f20c985cb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/Link.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Objects;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include;
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@Schema(name = "Link", description = "A link related to a payment")
+@JsonInclude(Include.NON_NULL)
+public class Link {
+
+ @JsonProperty(value = "href")
+ @Schema(example = "https://an.example.link/from/payment/platform",
+ description = "A URL that lets you perform additional actions to this payment " +
+ "when combined with the associated `method`.",
+ accessMode = READ_ONLY)
+ private String href;
+ @JsonProperty(value = "method")
+ @Schema(example = "GET",
+ description = "An API method that lets you perform additional actions to this payment" +
+ "when combined with the associated `href`.",
+ accessMode = READ_ONLY)
+ private String method;
+
+ public Link(String href, String method) {
+ this.href = href;
+ this.method = method;
+ }
+
+ public Link(String href) {
+ this.href = href;
+ }
+
+ public Link() {}
+
+ public String getHref() {
+ return href;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ @Override
+ public String toString() {
+ return "Link{" +
+ "href='" + href + '\'' +
+ ", method='" + method + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Link link = (Link) o;
+ return Objects.equals(href, link.href) &&
+ Objects.equals(method, link.method);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(href, method);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentEventLink.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentEventLink.java
new file mode 100644
index 000000000..484a619eb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentEventLink.java
@@ -0,0 +1,35 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static javax.ws.rs.HttpMethod.GET;
+
+@Schema(name = "PaymentEventLink", description = "Resource link for a payment of a payment event")
+public class PaymentEventLink {
+
+ public static final String PAYMENT_LINK = "payment_url";
+
+ @JsonProperty(value = PAYMENT_LINK)
+ private Link paymentLink;
+
+
+ public PaymentEventLink(String href) {
+ this.paymentLink = new Link(href, GET);
+ }
+
+ @Schema(name = PAYMENT_LINK)
+ public Link getPaymentLink() {
+ return paymentLink;
+ }
+
+
+ @Override
+ public String toString() {
+ return "PaymentEventLink{" +
+ "paymentLink=" + paymentLink +
+ '}';
+ }
+
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinks.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinks.java
new file mode 100644
index 000000000..38f7f9dac
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinks.java
@@ -0,0 +1,136 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.PaymentConnectorResponseLink;
+import uk.gov.pay.api.service.PublicApiUriGenerator;
+
+import java.net.URI;
+import java.util.List;
+
+import static javax.ws.rs.HttpMethod.GET;
+import static javax.ws.rs.HttpMethod.POST;
+
+@Schema(name = "PaymentLinks", description = "links for payment")
+public class PaymentLinks {
+
+ private static final String SELF_FIELD = "self";
+ private static final String NEXT_URL_FIELD = "next_url";
+ private static final String NEXT_URL_POST_FIELD = "next_url_post";
+ private static final String AUTH_URL_POST_FIELD = "auth_url_post";
+ private static final String EVENTS_FIELD = "events";
+ private static final String CANCEL_FIELD = "cancel";
+ private static final String REFUNDS_FIELD = "refunds";
+ private static final String CAPTURE_FIELD = "capture";
+
+ @JsonProperty(value = SELF_FIELD)
+ private Link self;
+
+ @JsonProperty(NEXT_URL_FIELD)
+ private Link nextUrl;
+
+ @JsonProperty(NEXT_URL_POST_FIELD)
+ private PostLink nextUrlPost;
+
+ @JsonProperty(AUTH_URL_POST_FIELD)
+ private PostLink authUrlPost;
+
+ @JsonProperty(value = EVENTS_FIELD)
+ private Link events;
+
+ @JsonProperty(value = REFUNDS_FIELD)
+ private Link refunds;
+
+ @JsonProperty(value = CANCEL_FIELD)
+ private PostLink cancel;
+
+ @JsonProperty(value = CAPTURE_FIELD)
+ private PostLink capture;
+
+ public Link getSelf() {
+ return self;
+ }
+
+ public Link getNextUrl() {
+ return nextUrl;
+ }
+
+ public PostLink getNextUrlPost() {
+ return nextUrlPost;
+ }
+
+ public PostLink getAuthUrlPost() {
+ return authUrlPost;
+ }
+
+ public Link getEvents() {
+ return events;
+ }
+
+ public Link getRefunds() {
+ return refunds;
+ }
+
+ public PostLink getCancel() {
+ return cancel;
+ }
+
+ public PostLink getCapture() {
+ return capture;
+ }
+
+ public void addKnownLinksValueOf(List chargeLinks, URI paymentAuthorisationUri) {
+ addNextUrlIfPresent(chargeLinks);
+ addNextUrlPostIfPresent(chargeLinks);
+ addAuthUrlPostIfPresent(chargeLinks, paymentAuthorisationUri);
+ addCaptureUrlIfPresent(chargeLinks);
+ }
+
+ public void addSelf(String href) {
+ this.self = new Link(href, GET);
+ }
+
+ public void addEvents(String href) {
+ this.events = new Link(href, GET);
+ }
+
+ public void addRefunds(String href) {
+ this.refunds = new Link(href, GET);
+ }
+
+ public void addCancel(String href) {
+ this.cancel = new PostLink(href, POST);
+ }
+
+ public void addCapture(String href) {
+ this.capture = new PostLink(href, POST);
+ }
+
+ private void addAuthUrlPostIfPresent(List chargeLinks, URI paymentAuthorisationUri) {
+ chargeLinks.stream()
+ .filter(links -> AUTH_URL_POST_FIELD.equals(links.getRel()))
+ .findFirst()
+ .ifPresent(links -> this.authUrlPost = new PostLink(paymentAuthorisationUri.toString(), links.getMethod(), links.getType(), links.getParams()));
+ }
+
+ private void addNextUrlPostIfPresent(List chargeLinks) {
+ chargeLinks.stream()
+ .filter(chargeLink -> NEXT_URL_POST_FIELD.equals(chargeLink.getRel()))
+ .findFirst()
+ .ifPresent(chargeLink -> this.nextUrlPost = new PostLink(chargeLink.getHref(), chargeLink.getMethod(), chargeLink.getType(), chargeLink.getParams()));
+ }
+
+ private void addNextUrlIfPresent(List chargeLinks) {
+ chargeLinks.stream()
+ .filter(chargeLink -> NEXT_URL_FIELD.equals(chargeLink.getRel()))
+ .findFirst()
+ .ifPresent(chargeLink -> this.nextUrl = new Link(chargeLink.getHref(), chargeLink.getMethod()));
+ }
+
+ private void addCaptureUrlIfPresent(List chargeLinks) {
+ chargeLinks.stream()
+ .filter(chargeLink -> CAPTURE_FIELD.equals(chargeLink.getRel()))
+ .findFirst()
+ .ifPresent(chargeLink -> this.capture = new PostLink(chargeLink.getHref(), chargeLink.getMethod()));
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForEvents.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForEvents.java
new file mode 100644
index 000000000..f67395ace
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForEvents.java
@@ -0,0 +1,25 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static javax.ws.rs.HttpMethod.GET;
+
+@Schema(name = "PaymentLinksForEvents", description = "links for events resource")
+public class PaymentLinksForEvents {
+
+ public static final String SELF = "self";
+
+ @JsonProperty(value = SELF)
+ private Link self;
+
+ @Schema(description = SELF)
+ public Link getSelf() {
+ return self;
+ }
+
+ public void addSelf(String href) {
+ this.self = new Link(href, GET);
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForSearch.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForSearch.java
new file mode 100644
index 000000000..94d2fdfeb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentLinksForSearch.java
@@ -0,0 +1,73 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static javax.ws.rs.HttpMethod.GET;
+import static javax.ws.rs.HttpMethod.POST;
+
+@Schema(name = "PaymentLinksForSearch", description = "links for search payment resource")
+public class PaymentLinksForSearch {
+
+ private static final String SELF = "self";
+ private static final String EVENTS = "events";
+ private static final String CANCEL = "cancel";
+ private static final String REFUNDS = "refunds";
+ private static final String CAPTURE = "capture";
+
+ @JsonProperty(value = SELF)
+ private Link self;
+
+ @JsonProperty(value = CANCEL)
+ private PostLink cancel;
+
+ @JsonProperty(value = EVENTS)
+ private Link events;
+
+ @JsonProperty(value = REFUNDS)
+ private Link refunds;
+
+ @JsonProperty(value = CAPTURE)
+ private PostLink capture;
+
+ public Link getSelf() {
+ return self;
+ }
+
+ public PostLink getCancel() {
+ return cancel;
+ }
+
+ public Link getEvents() {
+ return events;
+ }
+
+ public Link getRefunds() {
+ return refunds;
+ }
+
+ @Schema(name = CAPTURE, implementation = PostLink.class)
+ public Link getCapture() {
+ return capture;
+ }
+
+ public void addSelf(String href) {
+ this.self = new Link(href, GET);
+ }
+
+ public void addEvents(String href) {
+ this.events = new Link(href, GET);
+ }
+
+ public void addCancel(String href) {
+ this.cancel = new PostLink(href, POST);
+ }
+
+ public void addRefunds(String href) {
+ this.refunds = new Link(href, GET);
+ }
+
+ public void addCapture(String href) {
+ this.capture = new PostLink(href, POST);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java
new file mode 100644
index 000000000..98f39ae0b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PaymentWithAllLinks.java
@@ -0,0 +1,301 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.pay.api.model.AuthorisationSummary;
+import uk.gov.pay.api.model.CardDetails;
+import uk.gov.pay.api.model.CardPayment;
+import uk.gov.pay.api.model.Charge;
+import uk.gov.pay.api.model.PaymentConnectorResponseLink;
+import uk.gov.pay.api.model.PaymentSettlementSummary;
+import uk.gov.pay.api.model.PaymentState;
+import uk.gov.pay.api.model.RefundSummary;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.net.URI;
+import java.util.List;
+
+public class PaymentWithAllLinks extends CardPayment {
+
+ @JsonProperty(CardPayment.LINKS_JSON_ATTRIBUTE)
+ private PaymentLinks links = new PaymentLinks();
+
+ public PaymentLinks getLinks() {
+ return links;
+ }
+
+ private PaymentWithAllLinks(String chargeId, long amount, PaymentState state, String returnUrl, String description,
+ String reference, String email, String paymentProvider, String createdDate, SupportedLanguage language,
+ boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails,
+ List paymentConnectorResponseLinks, URI selfLink, URI paymentEventsUri, URI paymentCancelUri,
+ URI paymentRefundsUri, URI paymentCaptureUri, URI paymentAuthorisationUri, Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata metadata,
+ Long fee, Long netAmount, AuthorisationSummary authorisationSummary, String agreementId, AuthorisationMode authorisationMode) {
+ super(chargeId, amount, state, returnUrl, description, reference, email, paymentProvider, createdDate,
+ refundSummary, settlementSummary, cardDetails, language, delayedCapture, moto, corporateCardSurcharge, totalAmount,
+ providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode);
+ this.links.addSelf(selfLink.toString());
+ this.links.addKnownLinksValueOf(paymentConnectorResponseLinks, paymentAuthorisationUri);
+ this.links.addEvents(paymentEventsUri.toString());
+ this.links.addRefunds(paymentRefundsUri.toString());
+
+ if (!state.isFinished() && authorisationMode != AuthorisationMode.AGREEMENT) {
+ this.links.addCancel(paymentCancelUri.toString());
+ }
+
+ if (paymentConnectorResponseLinks.stream().anyMatch(link -> "capture".equals(link.getRel()))) {
+ this.links.addCapture(paymentCaptureUri.toString());
+ }
+ }
+
+ public static PaymentWithAllLinks valueOf(Charge paymentConnector,
+ URI selfLink,
+ URI paymentEventsUri,
+ URI paymentCancelUri,
+ URI paymentRefundsUri,
+ URI paymentsCaptureUri,
+ URI paymentAuthorisationUri) {
+ return new PaymentWithAllLinksBuilder()
+ .withChargeId(paymentConnector.getChargeId())
+ .withAmount(paymentConnector.getAmount())
+ .withState(paymentConnector.getState())
+ .withReturnUrl(paymentConnector.getReturnUrl())
+ .withDescription(paymentConnector.getDescription())
+ .withReference(paymentConnector.getReference())
+ .withEmail(paymentConnector.getEmail())
+ .withPaymentProvider(paymentConnector.getPaymentProvider())
+ .withCreatedDate(paymentConnector.getCreatedDate())
+ .withLanguage(paymentConnector.getLanguage())
+ .withDelayedCapture(paymentConnector.getDelayedCapture())
+ .withMoto(paymentConnector.isMoto())
+ .withRefundSummary(paymentConnector.getRefundSummary())
+ .withSettlementSummary(paymentConnector.getSettlementSummary())
+ .withCardDetails(paymentConnector.getCardDetails())
+ .withPaymentConnectorResponseLinks(paymentConnector.getLinks())
+ .withSelfLink(selfLink)
+ .withPaymentEventsUri(paymentEventsUri)
+ .withPaymentCancelUri(paymentCancelUri)
+ .withPaymentRefundsUri(paymentRefundsUri)
+ .withPaymentCaptureUri(paymentsCaptureUri)
+ .withPaymentAuthorisationUri(paymentAuthorisationUri)
+ .withCorporateCardSurcharge(paymentConnector.getCorporateCardSurcharge())
+ .withTotalAmount(paymentConnector.getTotalAmount())
+ .withProviderId(paymentConnector.getGatewayTransactionId())
+ .withMetadata(paymentConnector.getMetadata().orElse(null))
+ .withFee(paymentConnector.getFee())
+ .withNetAmount(paymentConnector.getNetAmount())
+ .withAuthorisationSummary(paymentConnector.getAuthorisationSummary())
+ .withAgreementId(paymentConnector.getAgreementId())
+ .withAuthorisationMode(paymentConnector.getAuthorisationMode())
+ .build();
+ }
+
+ public static PaymentWithAllLinks getPaymentWithLinks(
+ Charge paymentConnector,
+ URI selfLink,
+ URI paymentEventsUri,
+ URI paymentCancelUri,
+ URI paymentRefundsUri,
+ URI paymentsCaptureUri,
+ URI paymentAuthorisationUri) {
+
+ return PaymentWithAllLinks.valueOf(paymentConnector, selfLink, paymentEventsUri, paymentCancelUri, paymentRefundsUri, paymentsCaptureUri, paymentAuthorisationUri);
+ }
+
+ public static class PaymentWithAllLinksBuilder {
+ private String chargeId;
+ private long amount;
+ private PaymentState state;
+ private String returnUrl;
+ private String description;
+ private String reference;
+ private String email;
+ private String paymentProvider;
+ private String createdDate;
+ private SupportedLanguage language;
+ private boolean delayedCapture;
+ private boolean moto;
+ private RefundSummary refundSummary;
+ private PaymentSettlementSummary settlementSummary;
+ private CardDetails cardDetails;
+ private List paymentConnectorResponseLinks;
+ private URI selfLink;
+ private URI paymentEventsUri;
+ private URI paymentCancelUri;
+ private URI paymentRefundsUri;
+ private URI paymentCaptureUri;
+ private URI paymentAuthorisationUri;
+ private Long corporateCardSurcharge;
+ private Long totalAmount;
+ private String providerId;
+ private ExternalMetadata metadata;
+ private Long fee;
+ private Long netAmount;
+ private AuthorisationSummary authorisationSummary;
+ private String agreementId;
+ private AuthorisationMode authorisationMode;
+
+ public PaymentWithAllLinksBuilder withChargeId(String chargeId) {
+ this.chargeId = chargeId;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withAmount(long amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withState(PaymentState state) {
+ this.state = state;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withReturnUrl(String returnUrl) {
+ this.returnUrl = returnUrl;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withReference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentProvider(String paymentProvider) {
+ this.paymentProvider = paymentProvider;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withCreatedDate(String createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withLanguage(SupportedLanguage language) {
+ this.language = language;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withDelayedCapture(boolean delayedCapture) {
+ this.delayedCapture = delayedCapture;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withMoto(boolean moto) {
+ this.moto = moto;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withRefundSummary(RefundSummary refundSummary) {
+ this.refundSummary = refundSummary;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withSettlementSummary(PaymentSettlementSummary settlementSummary) {
+ this.settlementSummary = settlementSummary;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withCardDetails(CardDetails cardDetails) {
+ this.cardDetails = cardDetails;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentConnectorResponseLinks(List paymentConnectorResponseLinks) {
+ this.paymentConnectorResponseLinks = paymentConnectorResponseLinks;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withSelfLink(URI selfLink) {
+ this.selfLink = selfLink;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentEventsUri(URI paymentEventsUri) {
+ this.paymentEventsUri = paymentEventsUri;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentCancelUri(URI paymentCancelUri) {
+ this.paymentCancelUri = paymentCancelUri;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentRefundsUri(URI paymentRefundsUri) {
+ this.paymentRefundsUri = paymentRefundsUri;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentCaptureUri(URI paymentCaptureUri) {
+ this.paymentCaptureUri = paymentCaptureUri;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withPaymentAuthorisationUri(URI paymentAuthorisationUri) {
+ this.paymentAuthorisationUri = paymentAuthorisationUri;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withCorporateCardSurcharge(Long corporateCardSurcharge) {
+ this.corporateCardSurcharge = corporateCardSurcharge;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withTotalAmount(Long totalAmount) {
+ this.totalAmount = totalAmount;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withProviderId(String providerId) {
+ this.providerId = providerId;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withMetadata(ExternalMetadata metadata) {
+ this.metadata = metadata;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withFee(Long fee) {
+ this.fee = fee;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withNetAmount(Long netAmount) {
+ this.netAmount = netAmount;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withAuthorisationSummary(AuthorisationSummary authorisationSummary) {
+ this.authorisationSummary = authorisationSummary;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withAgreementId(String agreementId) {
+ this.agreementId = agreementId;
+ return this;
+ }
+
+ public PaymentWithAllLinksBuilder withAuthorisationMode(AuthorisationMode authorisationMode) {
+ this.authorisationMode = authorisationMode;
+ return this;
+ }
+
+ public PaymentWithAllLinks build() {
+ return new PaymentWithAllLinks(chargeId, amount, state, returnUrl, description, reference, email,
+ paymentProvider, createdDate, language, delayedCapture, moto, refundSummary, settlementSummary,
+ cardDetails, paymentConnectorResponseLinks, selfLink, paymentEventsUri, paymentCancelUri,
+ paymentRefundsUri, paymentCaptureUri, paymentAuthorisationUri, corporateCardSurcharge, totalAmount,
+ providerId, metadata, fee, netAmount, authorisationSummary, agreementId, authorisationMode);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PostLink.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PostLink.java
new file mode 100644
index 000000000..a6abd38ef
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/PostLink.java
@@ -0,0 +1,68 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.util.Map;
+import java.util.Objects;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include;
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@Schema(name = "PostLink", description = "A POST link related to a payment")
+@JsonInclude(Include.NON_NULL)
+public class PostLink extends Link {
+
+ private String type;
+ private Map params;
+
+ public PostLink(String href, String method, String type, Map params) {
+ super(href, method);
+ this.type = type;
+ this.params = params;
+ }
+
+ public PostLink(String href, String method) {
+ super(href, method);
+ }
+
+ @Schema(example = "POST", accessMode = READ_ONLY)
+ public String getMethod() {
+ return super.getMethod();
+ }
+
+ @Schema(example = "application/x-www-form-urlencoded")
+ public String getType() {
+ return type;
+ }
+
+ @Schema(example = "{\"description\": \"This is a value for a parameter called description\"}")
+ public Map getParams() {
+ return params;
+ }
+
+ @Override
+ public String toString() {
+ return "Link{" +
+ "href='" + getHref() + '\'' +
+ ", method='" + getMethod() + '\'' +
+ ", type='" + type + '\'' +
+ ", params=" + params +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ PostLink postLink = (PostLink) o;
+ return Objects.equals(type, postLink.type) &&
+ Objects.equals(params, postLink.params);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), type, params);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/RefundLinksForSearch.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/RefundLinksForSearch.java
new file mode 100644
index 000000000..592261706
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/RefundLinksForSearch.java
@@ -0,0 +1,36 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static javax.ws.rs.HttpMethod.GET;
+
+@Schema(name = "RefundLinksForSearch", description = "links for search refunds resource")
+public class RefundLinksForSearch {
+
+ private static final String SELF = "self";
+ private static final String PAYMENT = "payment";
+
+ private Link self;
+ private Link payment;
+
+ @Schema(description = "self")
+ @JsonProperty(value = SELF)
+ public Link getSelf() {
+ return self;
+ }
+
+ @Schema(description = "payment")
+ @JsonProperty(value = PAYMENT)
+ public Link getPayment() {
+ return payment;
+ }
+
+ public void addSelf(String href) {
+ this.self = new Link(href, GET);
+ }
+
+ public void addPayment(String href) {
+ this.payment = new Link(href, GET);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/SearchNavigationLinks.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/SearchNavigationLinks.java
new file mode 100644
index 000000000..31b68c043
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/links/SearchNavigationLinks.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.model.links;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(name = "SearchNavigationLinks", description = "Links to navigate through pages of your search.")
+public class SearchNavigationLinks {
+
+ @JsonProperty(value = "self")
+ private Link self;
+
+ @JsonProperty(value = "first_page")
+ private Link firstPage;
+
+ @JsonProperty(value = "last_page")
+ private Link lastPage;
+
+ @JsonProperty(value = "prev_page")
+ private Link prevPage;
+
+ @JsonProperty(value = "next_page")
+ private Link nextPage;
+
+ @Schema(description = "Use this URL ('href') to run the same search again.")
+ public Link getSelf() {
+ return self;
+ }
+
+ @Schema(description = "Use this URL ('href') to get the first page of results.")
+ public Link getFirstPage() {
+ return firstPage;
+ }
+
+ @Schema(description = "Use this URL ('href') to get the last page of results.")
+ public Link getLastPage() {
+ return lastPage;
+ }
+
+ @Schema(description = "Use this URL ('href') to get the previous page of results.")
+ public Link getPrevPage() {
+ return prevPage;
+ }
+
+ @Schema(description = "Use this URL ('href') to get the next page of results.")
+ public Link getNextPage() {
+ return nextPage;
+ }
+
+ public SearchNavigationLinks withSelfLink(String href) {
+ this.self = new Link(href);
+ return this;
+ }
+ public SearchNavigationLinks withPrevLink(String href) {
+ this.prevPage = new Link(href);
+ return this;
+ }
+ public SearchNavigationLinks withNextLink(String href) {
+ this.nextPage = new Link(href);
+ return this;
+ }
+ public SearchNavigationLinks withFirstLink(String href) {
+ this.firstPage = new Link(href);
+ return this;
+ }
+ public SearchNavigationLinks withLastLink(String href) {
+ this.lastPage = new Link(href);
+ return this;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/publicauth/AuthResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/publicauth/AuthResponse.java
new file mode 100644
index 000000000..09eef8cfb
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/publicauth/AuthResponse.java
@@ -0,0 +1,36 @@
+package uk.gov.pay.api.model.publicauth;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.model.TokenPaymentType;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AuthResponse {
+
+ private String accountId;
+ private String tokenLink;
+ private TokenPaymentType tokenType;
+
+ public AuthResponse() {
+ }
+
+ public AuthResponse(String accountId, String tokenLink, TokenPaymentType tokenType) {
+ this.accountId = accountId;
+ this.tokenLink = tokenLink;
+ this.tokenType = tokenType;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public String getTokenLink() {
+ return tokenLink;
+ }
+
+ public TokenPaymentType getTokenType() {
+ return tokenType;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/PaginationDecorator.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/PaginationDecorator.java
new file mode 100644
index 000000000..68c54c650
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/PaginationDecorator.java
@@ -0,0 +1,105 @@
+package uk.gov.pay.api.model.search;
+
+import black.door.hate.HalRepresentation;
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.exception.SearchPaymentsException;
+import uk.gov.pay.api.model.links.Link;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Optional;
+
+public class PaginationDecorator {
+
+ private final String baseUrl;
+ private final List queryParametersToBeExcluded
+ = List.of("account_id", "gateway_account_id", "transaction_type", "status_version");
+
+ @Inject
+ public PaginationDecorator(PublicApiConfig config) {
+ baseUrl = config.getBaseUrl();
+ }
+
+ public HalRepresentation.HalRepresentationBuilder decoratePagination(HalRepresentation.HalRepresentationBuilder halRepresentationBuilder,
+ SearchPagination pagination, String path) {
+
+ HalRepresentation.HalRepresentationBuilder builder = addPaginationProperties(halRepresentationBuilder, pagination);
+ SearchNavigationLinks links = pagination.getLinks();
+ try {
+ addLink(builder, "self", transformIntoPublicUri(baseUrl, links.getSelf(), path));
+ addLink(builder, "first_page", transformIntoPublicUri(baseUrl, links.getFirstPage(), path));
+ addLink(builder, "last_page", transformIntoPublicUri(baseUrl, links.getLastPage(), path));
+ addLink(builder, "prev_page", transformIntoPublicUri(baseUrl, links.getPrevPage(), path));
+ addLink(builder, "next_page", transformIntoPublicUri(baseUrl, links.getNextPage(), path));
+ } catch (URISyntaxException ex) {
+ throw new SearchPaymentsException(ex);
+ }
+ return builder;
+ }
+
+ public SearchNavigationLinks transformLinksToPublicApiUri(
+ SearchNavigationLinks links, String path) {
+ try {
+ return links.withSelfLink(transformIntoPublicUriAsString(baseUrl, links.getSelf(), path))
+ .withFirstLink(transformIntoPublicUriAsString(baseUrl, links.getFirstPage(), path))
+ .withLastLink(transformIntoPublicUriAsString(baseUrl, links.getLastPage(), path))
+ .withPrevLink(transformIntoPublicUriAsString(baseUrl, links.getPrevPage(), path))
+ .withNextLink(transformIntoPublicUriAsString(baseUrl, links.getNextPage(), path));
+ } catch (URISyntaxException ex) {
+ throw new SearchPaymentsException(ex);
+ }
+ }
+
+ private HalRepresentation.HalRepresentationBuilder addPaginationProperties(HalRepresentation.HalRepresentationBuilder halRepresentationBuilder,
+ SearchPagination pagination) {
+ halRepresentationBuilder
+ .addProperty("count", pagination.getCount())
+ .addProperty("total", pagination.getTotal())
+ .addProperty("page", pagination.getPage());
+ return halRepresentationBuilder;
+ }
+
+ private void addLink(HalRepresentation.HalRepresentationBuilder halRepresentationBuilder, String name, URI uri) {
+ if (uri != null) {
+ halRepresentationBuilder.addLink(name, uri);
+ }
+ }
+
+ private String transformIntoPublicUriAsString(String baseUrl, Link link, String path) throws URISyntaxException {
+ UriBuilder uriBuilder = getUriBuilder(baseUrl, link, path);
+
+ return Optional.ofNullable(uriBuilder)
+ .map(builder -> {
+ // breaks the order of query parameters
+ queryParametersToBeExcluded.forEach(queryParam ->
+ uriBuilder.replaceQueryParam(queryParam, (Object[]) null));
+ return builder.build().toString();
+ })
+ .orElse(null);
+ }
+
+ private URI transformIntoPublicUri(String baseUrl, Link link, String path) throws URISyntaxException {
+ UriBuilder uriBuilder = getUriBuilder(baseUrl, link, path);
+ return Optional.ofNullable(uriBuilder)
+ .map(builder -> {
+ // breaks the order of query parameters
+ queryParametersToBeExcluded.forEach(queryParam ->
+ uriBuilder.replaceQueryParam(queryParam, (Object[]) null));
+ return builder.build();
+ })
+ .orElse(null);
+ }
+
+ private UriBuilder getUriBuilder(String baseUrl, Link link, String path) throws URISyntaxException {
+ if (link == null)
+ return null;
+
+ return UriBuilder.fromUri(baseUrl)
+ .path(path)
+ .replaceQuery(new URI(link.getHref()).getQuery());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/SearchPagination.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/SearchPagination.java
new file mode 100644
index 000000000..f3388fbb3
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/SearchPagination.java
@@ -0,0 +1,14 @@
+package uk.gov.pay.api.model.search;
+
+
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+
+public interface SearchPagination {
+ int getCount();
+
+ int getTotal();
+
+ int getPage();
+
+ SearchNavigationLinks getLinks();
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java
new file mode 100644
index 000000000..38a98aa59
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentForSearchResult.java
@@ -0,0 +1,95 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.AuthorisationSummary;
+import uk.gov.pay.api.model.CardDetails;
+import uk.gov.pay.api.model.CardDetailsFromResponse;
+import uk.gov.pay.api.model.CardPayment;
+import uk.gov.pay.api.model.PaymentConnectorResponseLink;
+import uk.gov.pay.api.model.PaymentSettlementSummary;
+import uk.gov.pay.api.model.PaymentState;
+import uk.gov.pay.api.model.RefundSummary;
+import uk.gov.pay.api.model.TransactionResponse;
+import uk.gov.pay.api.model.links.PaymentLinksForSearch;
+import uk.gov.service.payments.commons.model.AuthorisationMode;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+import uk.gov.service.payments.commons.model.charge.ExternalMetadata;
+
+import java.net.URI;
+import java.util.List;
+
+@Schema(name = "PaymentDetailForSearch")
+public class PaymentForSearchResult extends CardPayment {
+
+ @JsonProperty(LINKS_JSON_ATTRIBUTE)
+ private PaymentLinksForSearch links = new PaymentLinksForSearch();
+
+ public PaymentForSearchResult(String chargeId, long amount, PaymentState state, String returnUrl, String description,
+ String reference, String email, String paymentProvider, String createdDate, SupportedLanguage language,
+ boolean delayedCapture, boolean moto, RefundSummary refundSummary, PaymentSettlementSummary settlementSummary, CardDetails cardDetails,
+ List links, URI selfLink, URI paymentEventsLink, URI paymentCancelLink, URI paymentRefundsLink, URI paymentCaptureUri,
+ Long corporateCardSurcharge, Long totalAmount, String providerId, ExternalMetadata externalMetadata,
+ Long fee, Long netAmount, AuthorisationSummary authorisationSummary, AuthorisationMode authorisationMode) {
+
+ super(chargeId, amount, state, returnUrl, description, reference, email, paymentProvider,
+ createdDate, refundSummary, settlementSummary, cardDetails, language, delayedCapture, moto, corporateCardSurcharge, totalAmount, providerId, externalMetadata,
+ fee, netAmount, authorisationSummary, null, authorisationMode);
+ this.links.addSelf(selfLink.toString());
+ this.links.addEvents(paymentEventsLink.toString());
+ this.links.addRefunds(paymentRefundsLink.toString());
+
+ if (!state.isFinished() && authorisationMode != AuthorisationMode.AGREEMENT) {
+ this.links.addCancel(paymentCancelLink.toString());
+ }
+ if (links.stream().anyMatch(link -> "capture".equals(link.getRel()))) {
+ this.links.addCapture(paymentCaptureUri.toString());
+ }
+ }
+
+ public static PaymentForSearchResult valueOf(
+ TransactionResponse paymentResult,
+ URI selfLink,
+ URI paymentEventsLink,
+ URI paymentCancelLink,
+ URI paymentRefundsLink,
+ URI paymentCaptureUri) {
+
+ return new PaymentForSearchResult(
+ paymentResult.getTransactionId(),
+ paymentResult.getAmount(),
+ paymentResult.getState(),
+ paymentResult.getReturnUrl(),
+ paymentResult.getDescription(),
+ paymentResult.getReference(),
+ paymentResult.getEmail(),
+ paymentResult.getPaymentProvider(),
+ paymentResult.getCreatedDate(),
+ paymentResult.getLanguage(),
+ paymentResult.getDelayedCapture(),
+ paymentResult.isMoto(),
+ paymentResult.getRefundSummary(),
+ paymentResult.getSettlementSummary(),
+ paymentResult.getWalletType()
+ .map(wallet -> CardDetails.from(paymentResult.getCardDetailsFromResponse(), wallet.getTitleCase()))
+ .orElse(CardDetails.from(paymentResult.getCardDetailsFromResponse(), null)),
+ paymentResult.getLinks(),
+ selfLink,
+ paymentEventsLink,
+ paymentCancelLink,
+ paymentRefundsLink,
+ paymentCaptureUri,
+ paymentResult.getCorporateCardSurcharge(),
+ paymentResult.getTotalAmount(),
+ paymentResult.getGatewayTransactionId(),
+ paymentResult.getMetadata().orElse(null),
+ paymentResult.getFee(),
+ paymentResult.getNetAmount(),
+ paymentResult.getAuthorisationSummary(),
+ paymentResult.getAuthorisationMode());
+ }
+
+ public PaymentLinksForSearch getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResponse.java
new file mode 100644
index 000000000..74df5d7d5
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResponse.java
@@ -0,0 +1,47 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class PaymentSearchResponse implements SearchPagination {
+
+ @JsonProperty("total")
+ private int total;
+
+ @JsonProperty("count")
+ private int count;
+
+ @JsonProperty("page")
+ private int page;
+
+ @JsonProperty("results")
+ private List payments;
+
+ @JsonProperty("_links")
+ private SearchNavigationLinks links = new SearchNavigationLinks();
+
+ public List getPayments() {
+ return payments;
+ }
+ @Override
+ public int getTotal() {
+ return total;
+ }
+ @Override
+ public int getCount() {
+ return count;
+ }
+ @Override
+ public int getPage() {
+ return page;
+ }
+
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResults.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResults.java
new file mode 100644
index 000000000..ef8b7ef08
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/PaymentSearchResults.java
@@ -0,0 +1,52 @@
+package uk.gov.pay.api.model.search.card;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+/**
+ * Used to define swagger specs for search results.
+ */
+public class PaymentSearchResults implements SearchPagination {
+
+ @Schema(name = "total", example = "100", description = "Total number of payments matching your search criteria.")
+ private int total;
+ @Schema(name = "count", example = "20", description = "Number of payments on the current page of search results.")
+ private int count;
+ @Schema(name = "page", example = "1",
+ description = "The [page of results you’re viewing]" +
+ "(https://docs.payments.service.gov.uk/api_reference/#pagination). " +
+ "To view other pages, make this request again using the `page` parameter.")
+ private int page;
+ private List results;
+ @Schema(name = "_links")
+ SearchNavigationLinks links;
+
+ @Override
+ public int getTotal() {
+ return total;
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+
+ public int getPage() {
+ return page;
+ }
+
+ @Schema(name = "results", description = "Contains payments matching your search criteria.")
+ public List getPayments() {
+ return results;
+ }
+
+ @Override
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchRefundsResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchRefundsResult.java
new file mode 100644
index 000000000..b76a18afc
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchRefundsResult.java
@@ -0,0 +1,132 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.RefundSettlementSummary;
+import uk.gov.pay.api.model.ledger.RefundTransactionFromLedger;
+import uk.gov.pay.api.model.links.RefundLinksForSearch;
+
+import java.net.URI;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Schema(name = "RefundDetailForSearch")
+public class RefundForSearchRefundsResult {
+
+ @JsonProperty("refund_id")
+ @Schema(example = "act4c33g40j3edfmi8jknab84x",
+ description = "The unique ID GOV.UK Pay automatically associated with this refund when you created it.",
+ accessMode = READ_ONLY)
+ private String refundId;
+
+ @JsonProperty("created_date")
+ @Schema(example = "2017-01-10T16:52:07.855Z",
+ description = "The date and time you created this refund. " +
+ "This value uses Coordinated Universal Time (UTC) and ISO 8601 format - `YYYY-MM-DDThh:mm:ss.SSSZ`.",
+ accessMode = READ_ONLY)
+ private String createdDate;
+
+ private String chargeId;
+
+ private Long amount;
+
+ private RefundLinksForSearch links = new RefundLinksForSearch();
+
+ @JsonProperty("status")
+ @Schema(example = "success",
+ description = "The [status of the refund]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/#checking-the-status-of-a-refund-status).",
+ allowableValues = {"submitted", "success", "error"}, accessMode = READ_ONLY)
+ private String status;
+
+ @Schema(accessMode = READ_ONLY)
+ private RefundSettlementSummary settlementSummary;
+
+ public RefundForSearchRefundsResult() {
+ }
+
+ public RefundForSearchRefundsResult(String refundId, String createdDate, String status,
+ String chargeId, Long amount, URI paymentURI, URI refundsURI,
+ RefundSettlementSummary settlementSummary) {
+ this.refundId = refundId;
+ this.createdDate = createdDate;
+ this.status = status;
+ this.chargeId = chargeId;
+ this.amount = amount;
+ this.links.addSelf(refundsURI.toString());
+ this.links.addPayment(paymentURI.toString());
+ this.settlementSummary = settlementSummary;
+ }
+
+ public String getRefundId() {
+ return refundId;
+ }
+
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ @JsonProperty("payment_id")
+ @Schema(hidden = true)
+ public String getChargeId() {
+ return chargeId;
+ }
+
+ @JsonProperty("charge_id")
+ @Schema(hidden = true)
+ public void setChargeId(String chargeId) {
+ this.chargeId = chargeId;
+ }
+
+ @JsonProperty("amount_submitted")
+ @Schema(hidden = true)
+ public void setAmount(Long amount) {
+ this.amount = amount;
+ }
+
+ @JsonProperty("amount")
+ @Schema(example = "120", description = "The amount refunded to the user in pence.", accessMode = READ_ONLY)
+ public Long getAmount() {
+ return amount;
+ }
+
+ @JsonProperty("_links")
+ public RefundLinksForSearch getLinks() {
+ return links;
+ }
+
+ @JsonProperty("settlement_summary")
+ @Schema(accessMode = READ_ONLY)
+ public RefundSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ public static RefundForSearchRefundsResult valueOf(RefundTransactionFromLedger refundResult, URI paymentURI, URI refundsURI) {
+ return new RefundForSearchRefundsResult(
+ refundResult.getTransactionId(),
+ refundResult.getCreatedDate(),
+ refundResult.getState().getStatus(),
+ refundResult.getParentTransactionId(),
+ refundResult.getAmount(),
+ paymentURI,
+ refundsURI,
+ refundResult.getSettlementSummary());
+ }
+
+ @Override
+ public String toString() {
+ return "RefundForSearchRefundsResult{" +
+ "refundId='" + refundId + '\'' +
+ ", createdDate='" + createdDate + '\'' +
+ ", status='" + status + '\'' +
+ ", amount=" + amount + '\'' +
+ ", links=" + links + '\'' +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchResult.java
new file mode 100644
index 000000000..2a6d2865c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundForSearchResult.java
@@ -0,0 +1,39 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.RefundLinksForSearch;
+
+import java.util.List;
+
+public class RefundForSearchResult {
+
+ @Schema(name = "payment_id", example = "hu20sqlact5260q2nanm0q8u93", description = "The unique ID GOV.UK Pay associated with this payment when you created it.")
+ private String paymentId;
+ @Schema(name = "_links")
+ private RefundLinksForSearch links;
+ @JsonProperty(value = "_embedded")
+ @Schema(name = "_embedded")
+ private Embedded embedded;
+
+ @Schema(name = "EmbeddedRefunds")
+ public class Embedded {
+ private List refunds;
+
+ public List getRefunds() {
+ return refunds;
+ }
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public RefundLinksForSearch getLinks() {
+ return links;
+ }
+
+ public Embedded getEmbedded() {
+ return embedded;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundResult.java
new file mode 100644
index 000000000..323b78f74
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/RefundResult.java
@@ -0,0 +1,22 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.RefundLinksForSearch;
+
+@Schema(name = "Refund")
+public class RefundResult {
+
+ @JsonUnwrapped
+ private RefundForSearchRefundsResult refunds;
+ @Schema(name = "_links")
+ private RefundLinksForSearch links;
+
+ public RefundForSearchRefundsResult getRefunds() {
+ return refunds;
+ }
+
+ public RefundLinksForSearch getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/SearchRefundsResults.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/SearchRefundsResults.java
new file mode 100644
index 000000000..88b8e71c7
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/card/SearchRefundsResults.java
@@ -0,0 +1,60 @@
+package uk.gov.pay.api.model.search.card;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+
+@Schema(name = "RefundSearchResults")
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class SearchRefundsResults implements SearchPagination {
+
+ @Schema(example = "100", description = "Number of refunds matching your search criteria.")
+ private int total;
+ @Schema(example = "20", description = "Number of refunds on the current page of search results.")
+ private int count;
+ @Schema(example = "1", description = "The [page of results](payments.service.gov.uk/api_reference/#pagination) you’re viewing. To view other pages, make this request again using the `page` parameter.")
+ private int page;
+
+ @Schema(description = "Contains the refunds matching your search criteria.")
+ private List results;
+ @JsonProperty("_links")
+ private SearchNavigationLinks links;
+
+ public SearchRefundsResults(int total, int count, int page, List results,
+ SearchNavigationLinks links) {
+ this.total = total;
+ this.count = count;
+ this.page = page;
+ this.results = results;
+ this.links = links;
+ }
+
+ @Override
+ public int getTotal() {
+ return total;
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+ public int getPage() {
+ return page;
+ }
+
+ public List getResults() {
+ return results;
+ }
+
+ @Override
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputeForSearchResult.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputeForSearchResult.java
new file mode 100644
index 000000000..2b1a9b385
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputeForSearchResult.java
@@ -0,0 +1,111 @@
+package uk.gov.pay.api.model.search.dispute;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.ledger.DisputeSettlementSummary;
+import uk.gov.pay.api.model.ledger.DisputeTransactionFromLedger;
+import uk.gov.pay.api.model.links.DisputeLinksForSearch;
+
+import java.net.URI;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+import static uk.gov.pay.api.model.CardPayment.LINKS_JSON_ATTRIBUTE;
+
+@Schema(name = "DisputeDetailForSearch")
+public class DisputeForSearchResult {
+ private Long amount;
+ @JsonProperty("created_date")
+ private String createdDate;
+ @JsonProperty("dispute_id")
+ private String disputeId;
+ @JsonProperty("evidence_due_date")
+ private String evidenceDueDate;
+ private Long fee;
+ @JsonProperty("net_amount")
+ private Long netAmount;
+ @JsonProperty("payment_id")
+ private String paymentId;
+ private String reason;
+ @JsonProperty("settlement_summary")
+ private DisputeSettlementSummary settlementSummary;
+ private String status;
+ @JsonProperty(LINKS_JSON_ATTRIBUTE)
+ private DisputeLinksForSearch links = new DisputeLinksForSearch();
+
+ public DisputeForSearchResult(Long amount, String createdDate, String disputeId, String evidenceDueDate,
+ Long fee, Long netAmount, String paymentId, String reason,
+ DisputeSettlementSummary settlementSummary, String status, URI paymentURI) {
+ this.amount = amount;
+ this.createdDate = createdDate;
+ this.disputeId = disputeId;
+ this.evidenceDueDate = evidenceDueDate;
+ this.fee = fee;
+ this.netAmount = netAmount;
+ this.paymentId = paymentId;
+ this.reason = reason;
+ this.settlementSummary = settlementSummary;
+ this.status = status;
+ this.links.addPayment(paymentURI.toString());
+ }
+
+ public static DisputeForSearchResult valueOf(DisputeTransactionFromLedger fromLedger, URI paymentUri) {
+ return new DisputeForSearchResult(fromLedger.getAmount(), fromLedger.getCreatedDate(), fromLedger.getTransactionId(),
+ fromLedger.getEvidenceDueDate(), fromLedger.getFee(), fromLedger.getNetAmount(), fromLedger.getParentTransactionId(),
+ fromLedger.getReason(), fromLedger.getSettlementSummary(), fromLedger.getState().getStatus(),
+ paymentUri);
+ }
+
+ @Schema(example = "1200", description = "The disputed amount in pence.", accessMode = READ_ONLY)
+ public Long getAmount() {
+ return amount;
+ }
+
+ @Schema(example = "2022-07-28T16:43:00.000Z", description = "The date and time the user's bank told GOV.UK Pay about this dispute.", accessMode = READ_ONLY)
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93", description = "The unique ID GOV.UK Pay automatically associated with this dispute when the paying user disputed the payment.", accessMode = READ_ONLY)
+ public String getDisputeId() {
+ return disputeId;
+ }
+
+ @Schema(example = "2022-07-28T16:43:00.000Z", description = "The deadline for submitting your supporting evidence. This value uses Coordinated Universal Time (UTC) and ISO 8601 format", accessMode = READ_ONLY)
+ public String getEvidenceDueDate() {
+ return evidenceDueDate;
+ }
+
+ @Schema(example = "1200", description = "The payment service provider’s dispute fee, in pence.", accessMode = READ_ONLY)
+ public Long getFee() {
+ return fee;
+ }
+
+ @Schema(example = "-2400", description = "The amount, in pence, your payment service provider will take for a lost dispute. 'net_amount' is deducted from your payout after you lose the dispute. For example, a 'net_amount' of '-1500' means your PSP will take £15.00 from your next payout into your bank account. 'net_amount' is always a negative value. 'net_amount' only appears if you lose the dispute.", accessMode = READ_ONLY)
+ public Long getNetAmount() {
+ return netAmount;
+ }
+
+ @Schema(example = "hu20sqlact5260q2nanm0q8u93", description = "The unique ID GOV.UK Pay automatically associated with this payment when you created it.", accessMode = READ_ONLY)
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ @Schema(example = "fraudulent", description = "The reason the paying user gave for disputing this payment. Possible values are: 'credit_not_processed', 'duplicate', 'fraudulent', 'general', 'product_not_received', 'product_unacceptable', 'unrecognised', 'subscription_cancelled', >'other'", accessMode = READ_ONLY)
+ public String getReason() {
+ return reason;
+ }
+
+ public DisputeSettlementSummary getSettlementSummary() {
+ return settlementSummary;
+ }
+
+ @Schema(example = "under_review", description = "The current status of the dispute. Possible values are: 'needs_response', 'won', 'lost', 'under_review'", accessMode = READ_ONLY)
+ public String getStatus() {
+ return status;
+ }
+
+ @Schema(description = "Contains an API method and endpoint to get information about this payment. A 'GET' request ('method') to this endpoint ('href') returns information about this payment.")
+ public DisputeLinksForSearch getLinks() {
+ return links;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputesSearchResults.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputesSearchResults.java
new file mode 100644
index 000000000..5b2157cea
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/dispute/DisputesSearchResults.java
@@ -0,0 +1,55 @@
+package uk.gov.pay.api.model.search.dispute;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import io.swagger.v3.oas.annotations.media.Schema;
+import uk.gov.pay.api.model.links.SearchNavigationLinks;
+import uk.gov.pay.api.model.search.SearchPagination;
+
+import java.util.List;
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class DisputesSearchResults implements SearchPagination {
+ @Schema(name = "total", example = "100", description = "Number of total disputes matching your search criteria.")
+ private int total;
+ @Schema(name = "count", example = "20", description = "Number of disputes on the current page of search results.")
+ private int count;
+ @Schema(name = "page", example = "1", description = "The page of results you’re viewing. To view other pages, make this request again using the 'page' parameter.")
+ private int page;
+ @Schema(description = "Contains disputes matching your search criteria.")
+ private List results;
+ @Schema(name = "links", description = "Contains links you can use to move between the pages of this search.")
+ SearchNavigationLinks links;
+
+ public DisputesSearchResults(int total, int count, int page, List results,
+ SearchNavigationLinks links) {
+ this.total = total;
+ this.count = count;
+ this.page = page;
+ this.results = results;
+ this.links = links;
+ }
+
+ @Override
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+ public int getTotal() {
+ return total;
+ }
+
+ @Override
+ public int getPage() {
+ return page;
+ }
+
+ @Override
+ public SearchNavigationLinks getLinks() {
+ return links;
+ }
+
+ public List getResults() {
+ return results;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/links/DDPaymentLinksForSearch.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/links/DDPaymentLinksForSearch.java
new file mode 100644
index 000000000..8a91e61e8
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/search/links/DDPaymentLinksForSearch.java
@@ -0,0 +1,13 @@
+package uk.gov.pay.api.model.search.links;
+
+import uk.gov.pay.api.model.links.Link;
+
+import static javax.ws.rs.HttpMethod.GET;
+
+public class DDPaymentLinksForSearch {
+ private Link self;
+
+ public void addSelf(String href) { this.self = new Link(href, GET); }
+
+ public Link getSelf() { return self; }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/CreateTelephonePaymentRequest.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/CreateTelephonePaymentRequest.java
new file mode 100644
index 000000000..3a6eb6b52
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/CreateTelephonePaymentRequest.java
@@ -0,0 +1,336 @@
+package uk.gov.pay.api.model.telephone;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.utils.JsonStringBuilder;
+import uk.gov.pay.api.validation.ValidCardExpiryDate;
+import uk.gov.pay.api.validation.ValidCardFirstSixDigits;
+import uk.gov.pay.api.validation.ValidCardLastFourDigits;
+import uk.gov.pay.api.validation.ValidCardType;
+import uk.gov.pay.api.validation.ValidZonedDateTime;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Optional;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class CreateTelephonePaymentRequest {
+
+ public static final int REFERENCE_MAX_LENGTH = 255;
+ public static final int AMOUNT_MAX_VALUE = 10000000;
+ public static final int AMOUNT_MIN_VALUE = 0;
+ public static final int DESCRIPTION_MAX_LENGTH = 255;
+
+ @Min(value = AMOUNT_MIN_VALUE, message = "Must be greater than or equal to 1")
+ @Max(value = AMOUNT_MAX_VALUE, message = "Must be less than or equal to {value}")
+ @NotNull(message = "Field [amount] cannot be null")
+ private int amount;
+
+ @Size(max = REFERENCE_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ @NotNull(message = "Field [reference] cannot be null")
+ private String reference;
+
+ @Size(max = DESCRIPTION_MAX_LENGTH, message = "Must be less than or equal to {max} characters length")
+ @NotNull(message = "Field [description] cannot be null")
+ private String description;
+
+ @JsonProperty("created_date")
+ @ValidZonedDateTime(message = "Field [created_date] must be a valid ISO-8601 time and date format")
+ private String createdDate;
+
+ @JsonProperty("authorised_date")
+ @ValidZonedDateTime(message = "Field [authorised_date] must be a valid ISO-8601 time and date format")
+ private String authorisedDate;
+
+ @NotNull(message = "Field [processor_id] cannot be null")
+ private String processorId;
+
+ @NotNull(message = "Field [provider_id] cannot be null")
+ private String providerId;
+
+ @JsonProperty("auth_code")
+ private String authCode;
+
+ @Valid
+ @NotNull(message = "Field [payment_outcome] cannot be null")
+ private PaymentOutcome paymentOutcome;
+
+ @ValidCardType(message = "Field [card_type] must be either master-card, visa, maestro, diners-club, american-express or jcb")
+ private String cardType;
+
+ @JsonProperty("name_on_card")
+ private String nameOnCard;
+
+ @JsonProperty("email_address")
+ private String emailAddress;
+
+ @ValidCardExpiryDate(message = "Field [card_expiry] must have valid MM/YY")
+ private String cardExpiry;
+
+ @ValidCardLastFourDigits(message = "Field [last_four_digits] must be exactly 4 digits")
+ private String lastFourDigits;
+
+ @ValidCardFirstSixDigits(message = "Field [first_six_digits] must be exactly 6 digits")
+ private String firstSixDigits;
+
+ @JsonProperty("telephone_number")
+ private String telephoneNumber;
+
+ public String toConnectorPayload() {
+ JsonStringBuilder request = new JsonStringBuilder()
+ .add("amount", this.getAmount())
+ .add("reference", this.getReference())
+ .add("description", this.getDescription())
+ .add("processor_id", this.getProcessorId())
+ .add("provider_id", this.getProviderId())
+ .addToMap("payment_outcome", "status", this.getPaymentOutcome().getStatus());
+ this.getPaymentOutcome().getCode().ifPresent(code -> request.addToMap("payment_outcome", "code", code));
+ this.getPaymentOutcome().getSupplemental().ifPresent(supplemental -> {
+ supplemental.getErrorCode().ifPresent(errorCode -> request.addToNestedMap("error_code", errorCode, "payment_outcome", "supplemental"));
+ supplemental.getErrorMessage().ifPresent(errorMessage -> request.addToNestedMap("error_message", errorMessage, "payment_outcome", "supplemental"));
+ }
+ );
+
+ this.getCardExpiry().ifPresent(cardExpiry -> request.add("card_expiry", cardExpiry));
+ this.getCreatedDate().ifPresent(createdDate -> request.add("created_date", createdDate));
+ this.getAuthorisedDate().ifPresent(authorisedDate -> request.add("authorised_date", authorisedDate));
+ this.getAuthCode().ifPresent(authCode -> request.add("auth_code", authCode));
+ this.getNameOnCard().ifPresent(nameOnCard -> request.add("name_on_card", nameOnCard));
+ this.getEmailAddress().ifPresent(emailAddress -> request.add("email_address", emailAddress));
+ this.getTelephoneNumber().ifPresent(telephoneNumber -> request.add("telephone_number", telephoneNumber));
+ this.getCardType().ifPresent(cardType -> request.add("card_type", cardType));
+ this.getLastFourDigits().ifPresent(lastFourDigits -> request.add("last_four_digits", lastFourDigits));
+ this.getFirstSixDigits().ifPresent(firstSixDigits -> request.add("first_six_digits", firstSixDigits));
+
+ return request.build();
+ }
+
+ public CreateTelephonePaymentRequest() {
+ // To enable Jackson serialisation we need a default constructor
+ }
+
+ public CreateTelephonePaymentRequest(int amount, String reference, String description, String createdDate, String authorisedDate, String processorId, String providerId, String authCode, PaymentOutcome paymentOutcome, String cardType, String nameOnCard, String emailAddress, String cardExpiry, String lastFourDigits, String firstSixDigits, String telephoneNumber) {
+ // For testing deserialization
+ this.amount = amount;
+ this.reference = reference;
+ this.description = description;
+ this.createdDate = createdDate;
+ this.authorisedDate = authorisedDate;
+ this.processorId = processorId;
+ this.providerId = providerId;
+ this.authCode = authCode;
+ this.paymentOutcome = paymentOutcome;
+ this.cardType = cardType;
+ this.nameOnCard = nameOnCard;
+ this.emailAddress = emailAddress;
+ this.cardExpiry = cardExpiry;
+ this.lastFourDigits = lastFourDigits;
+ this.firstSixDigits = firstSixDigits;
+ this.telephoneNumber = telephoneNumber;
+ }
+
+ public CreateTelephonePaymentRequest(Builder builder) {
+ this.amount = builder.amount;
+ this.reference = builder.reference;
+ this.description = builder.description;
+ this.createdDate = builder.createdDate;
+ this.authorisedDate = builder.authorisedDate;
+ this.processorId = builder.processorId;
+ this.providerId = builder.providerId;
+ this.authCode = builder.authCode;
+ this.paymentOutcome = builder.paymentOutcome;
+ this.cardType = builder.cardType;
+ this.nameOnCard = builder.nameOnCard;
+ this.emailAddress = builder.emailAddress;
+ this.cardExpiry = builder.cardExpiry;
+ this.lastFourDigits = builder.lastFourDigits;
+ this.firstSixDigits = builder.firstSixDigits;
+ this.telephoneNumber = builder.telephoneNumber;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Optional getCreatedDate() {
+ return Optional.ofNullable(createdDate);
+ }
+
+ public Optional getAuthorisedDate() {
+ return Optional.ofNullable(authorisedDate);
+ }
+
+ public String getProcessorId() {
+ return processorId;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public Optional getAuthCode() {
+ return Optional.ofNullable(authCode);
+ }
+
+ public PaymentOutcome getPaymentOutcome() {
+ return paymentOutcome;
+ }
+
+ public Optional getCardType() {
+ return Optional.ofNullable(cardType);
+ }
+
+ public Optional getNameOnCard() {
+ return Optional.ofNullable(nameOnCard);
+ }
+
+ public Optional getEmailAddress() {
+ return Optional.ofNullable(emailAddress);
+ }
+
+ public Optional getCardExpiry() {
+ return Optional.ofNullable(cardExpiry);
+ }
+
+ public Optional getLastFourDigits() {
+ return Optional.ofNullable(lastFourDigits);
+ }
+
+ public Optional getFirstSixDigits() {
+ return Optional.ofNullable(firstSixDigits);
+ }
+
+ public Optional getTelephoneNumber() {
+ return Optional.ofNullable(telephoneNumber);
+ }
+
+ public static class Builder {
+ private int amount;
+
+ private String reference;
+
+ private String description;
+
+ private String createdDate;
+
+ private String authorisedDate;
+
+ private String processorId;
+
+ private String providerId;
+
+ private String authCode;
+
+ private PaymentOutcome paymentOutcome;
+
+ private String cardType;
+
+ private String nameOnCard;
+
+ private String emailAddress;
+
+ private String cardExpiry;
+
+ private String lastFourDigits;
+
+ private String firstSixDigits;
+
+ private String telephoneNumber;
+
+ public Builder withAmount(int amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder withReference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public Builder withDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder withCreatedDate(String createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public Builder withAuthorisedDate(String authorisedDate) {
+ this.authorisedDate = authorisedDate;
+ return this;
+ }
+
+ public Builder withProcessorId(String processorId) {
+ this.processorId = processorId;
+ return this;
+ }
+
+ public Builder withProviderId(String providerId) {
+ this.providerId = providerId;
+ return this;
+ }
+
+ public Builder withAuthCode(String authCode) {
+ this.authCode = authCode;
+ return this;
+ }
+
+ public Builder withPaymentOutcome(PaymentOutcome paymentOutcome) {
+ this.paymentOutcome = paymentOutcome;
+ return this;
+ }
+
+ public Builder withCardType(String cardType) {
+ this.cardType = cardType;
+ return this;
+ }
+
+ public Builder withNameOnCard(String nameOnCard) {
+ this.nameOnCard = nameOnCard;
+ return this;
+ }
+
+ public Builder withEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ return this;
+ }
+
+ public Builder withCardExpiry(String cardExpiry) {
+ this.cardExpiry = cardExpiry;
+ return this;
+ }
+
+ public Builder withLastFourDigits(String lastFourDigits) {
+ this.lastFourDigits = lastFourDigits;
+ return this;
+ }
+
+ public Builder withFirstSixDigits(String firstSixDigits) {
+ this.firstSixDigits = firstSixDigits;
+ return this;
+ }
+
+ public Builder withTelephoneNumber(String telephoneNumber) {
+ this.telephoneNumber = telephoneNumber;
+ return this;
+ }
+
+ public CreateTelephonePaymentRequest build() {
+ return new CreateTelephonePaymentRequest(this);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/PaymentOutcome.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/PaymentOutcome.java
new file mode 100644
index 000000000..5df843c16
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/PaymentOutcome.java
@@ -0,0 +1,50 @@
+package uk.gov.pay.api.model.telephone;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import uk.gov.pay.api.validation.ValidPaymentOutcome;
+
+import java.util.Optional;
+
+@ValidPaymentOutcome(message = "Field [payment_outcome] must include a valid status and error code")
+public class PaymentOutcome {
+
+ @JsonProperty("status")
+ private String status;
+
+ @JsonProperty("code")
+ private String code;
+
+ @JsonProperty("supplemental")
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ private Supplemental supplemental;
+
+ public PaymentOutcome() {
+ }
+
+ public PaymentOutcome(String status) {
+ this.status = status;
+ }
+
+ public PaymentOutcome(String status, String code, Supplemental supplemental) {
+ // For testing deserialization
+ this.status = status;
+ this.code = code;
+ this.supplemental = supplemental;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ @JsonIgnore
+ public Optional getCode() {
+ return Optional.ofNullable(code);
+ }
+
+ @JsonIgnore
+ public Optional getSupplemental() {
+ return Optional.ofNullable(supplemental);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/Supplemental.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/Supplemental.java
new file mode 100644
index 000000000..1552bb62b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/Supplemental.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.model.telephone;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+import java.util.Optional;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class Supplemental {
+
+ @JsonProperty("error_code")
+ private String errorCode;
+
+ @JsonProperty("error_message")
+ private String errorMessage;
+
+ public Supplemental() {
+ }
+
+ public Supplemental(String errorCode, String errorMessage) {
+ // For testing deserialization
+ this.errorCode = errorCode;
+ this.errorMessage = errorMessage;
+ }
+
+ @JsonIgnore
+ public Optional getErrorCode() {
+ return Optional.ofNullable(errorCode);
+ }
+
+ @JsonIgnore
+ public Optional getErrorMessage() {
+ return Optional.ofNullable(errorMessage);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/TelephonePaymentResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/TelephonePaymentResponse.java
new file mode 100644
index 000000000..32d850d1d
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/model/telephone/TelephonePaymentResponse.java
@@ -0,0 +1,334 @@
+package uk.gov.pay.api.model.telephone;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import uk.gov.pay.api.model.ChargeFromResponse;
+import uk.gov.pay.api.model.PaymentState;
+
+import java.util.Optional;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class TelephonePaymentResponse {
+
+ private Long amount;
+
+ private String reference;
+
+ private String description;
+
+ @JsonProperty("created_date")
+ private String createdDate;
+
+ @JsonProperty("authorised_date")
+ private String authorisedDate;
+
+ private String processorId;
+
+ private String providerId;
+
+ @JsonProperty("auth_code")
+ private String authCode;
+
+ private PaymentOutcome paymentOutcome;
+
+ private String cardType;
+
+ @JsonProperty("name_on_card")
+ private String nameOnCard;
+
+ @JsonProperty("email_address")
+ private String emailAddress;
+
+ private String cardExpiry;
+
+ private String lastFourDigits;
+
+ private String firstSixDigits;
+
+ @JsonProperty("telephone_number")
+ private String telephoneNumber;
+
+ private String paymentId;
+
+ private PaymentState state;
+
+ public TelephonePaymentResponse() {
+ // For Jackson
+ }
+
+ public TelephonePaymentResponse(Builder builder) {
+ this.amount = builder.amount;
+ this.reference = builder.reference;
+ this.description = builder.description;
+ this.createdDate = builder.createdDate;
+ this.authorisedDate = builder.authorisedDate;
+ this.processorId = builder.processorId;
+ this.providerId = builder.providerId;
+ this.authCode = builder.authCode;
+ this.paymentOutcome = builder.paymentOutcome;
+ this.cardType = builder.cardType;
+ this.nameOnCard = builder.nameOnCard;
+ this.emailAddress = builder.emailAddress;
+ this.cardExpiry = builder.cardExpiry;
+ this.lastFourDigits = builder.lastFourDigits;
+ this.firstSixDigits = builder.firstSixDigits;
+ this.telephoneNumber = builder.telephoneNumber;
+ this.paymentId = builder.paymentId;
+ this.state = builder.state;
+ }
+
+ public TelephonePaymentResponse(Long amount,
+ String reference,
+ String description,
+ String createdDate,
+ String authorisedDate,
+ String processorId,
+ String providerId,
+ String authCode,
+ PaymentOutcome paymentOutcome,
+ String cardType,
+ String nameOnCard,
+ String emailAddress,
+ String cardExpiry,
+ String lastFourDigits,
+ String firstSixDigits,
+ String telephoneNumber,
+ String paymentId,
+ PaymentState state) {
+ // For testing serialization
+ this.amount = amount;
+ this.reference = reference;
+ this.description = description;
+ this.createdDate = createdDate;
+ this.authorisedDate = authorisedDate;
+ this.processorId = processorId;
+ this.providerId = providerId;
+ this.authCode = authCode;
+ this.paymentOutcome = paymentOutcome;
+ this.cardType = cardType;
+ this.nameOnCard = nameOnCard;
+ this.emailAddress = emailAddress;
+ this.cardExpiry = cardExpiry;
+ this.lastFourDigits = lastFourDigits;
+ this.firstSixDigits = firstSixDigits;
+ this.telephoneNumber = telephoneNumber;
+ this.paymentId = paymentId;
+ this.state = state;
+ }
+
+ public Long getAmount() {
+ return amount;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @JsonIgnore
+ public Optional getCreatedDate() {
+ return Optional.ofNullable(createdDate);
+ }
+
+ @JsonIgnore
+ public Optional getAuthorisedDate() {
+ return Optional.ofNullable(authorisedDate);
+ }
+
+ public String getProcessorId() {
+ return processorId;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ @JsonIgnore
+ public Optional getAuthCode() {
+ return Optional.ofNullable(authCode);
+ }
+
+ public PaymentOutcome getPaymentOutcome() {
+ return paymentOutcome;
+ }
+
+ public String getCardType() {
+ return cardType;
+ }
+
+ @JsonIgnore
+ public Optional getNameOnCard() {
+ return Optional.ofNullable(nameOnCard);
+ }
+
+ @JsonIgnore
+ public Optional getEmailAddress() {
+ return Optional.ofNullable(emailAddress);
+ }
+
+ public String getCardExpiry() {
+ return cardExpiry;
+ }
+
+ public String getLastFourDigits() {
+ return lastFourDigits;
+ }
+
+ public String getFirstSixDigits() {
+ return firstSixDigits;
+ }
+
+ @JsonIgnore
+ public Optional getTelephoneNumber() {
+ return Optional.ofNullable(telephoneNumber);
+ }
+
+ public String getPaymentId() { return paymentId; }
+
+ public PaymentState getState() {
+ return state;
+ }
+
+ public static TelephonePaymentResponse from(ChargeFromResponse chargeFromResponse) {
+ return new TelephonePaymentResponse.Builder()
+ .withAmount(chargeFromResponse.getAmount())
+ .withDescription(chargeFromResponse.getDescription())
+ .withReference(chargeFromResponse.getReference())
+ .withCreatedDate(chargeFromResponse.getCreatedDate())
+ .withAuthorisedDate(chargeFromResponse.getAuthorisedDate())
+ .withProcessorId(chargeFromResponse.getProcessorId())
+ .withProviderId(chargeFromResponse.getProviderId())
+ .withAuthCode(chargeFromResponse.getAuthCode())
+ .withPaymentOutcome(chargeFromResponse.getPaymentOutcome())
+ .withCardType(chargeFromResponse.getCardBrand())
+ .withEmailAddress(chargeFromResponse.getEmail())
+ .withNameOnCard(chargeFromResponse.getCardDetailsFromResponse() != null ? chargeFromResponse.getCardDetailsFromResponse().getCardHolderName() : null)
+ .withCardExpiry(chargeFromResponse.getCardDetailsFromResponse() != null ? chargeFromResponse.getCardDetailsFromResponse().getExpiryDate() : null)
+ .withLastFourDigits(chargeFromResponse.getCardDetailsFromResponse() != null ? chargeFromResponse.getCardDetailsFromResponse().getLastDigitsCardNumber() : null)
+ .withFirstSixDigits(chargeFromResponse.getCardDetailsFromResponse() != null ? chargeFromResponse.getCardDetailsFromResponse().getFirstDigitsCardNumber() : null)
+ .withTelephoneNumber(chargeFromResponse.getTelephoneNumber())
+ .withPaymentId(chargeFromResponse.getChargeId())
+ .withState(chargeFromResponse.getState())
+ .build();
+ }
+
+ public static class Builder {
+ private Long amount;
+ private String reference;
+ private String description;
+ private String createdDate;
+ private String authorisedDate;
+ private String processorId;
+ private String providerId;
+ private String authCode;
+ private PaymentOutcome paymentOutcome;
+ private String cardType;
+ private String nameOnCard;
+ private String emailAddress;
+ private String cardExpiry;
+ private String lastFourDigits;
+ private String firstSixDigits;
+ private String telephoneNumber;
+ private String paymentId;
+ private PaymentState state;
+
+ public Builder withAmount(Long amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder withReference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public Builder withDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder withCreatedDate(String createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public Builder withAuthorisedDate(String authorisedDate) {
+ this.authorisedDate = authorisedDate;
+ return this;
+ }
+
+ public Builder withProcessorId(String processorId) {
+ this.processorId = processorId;
+ return this;
+ }
+
+ public Builder withProviderId(String providerId) {
+ this.providerId = providerId;
+ return this;
+ }
+
+ public Builder withAuthCode(String authCode) {
+ this.authCode = authCode;
+ return this;
+ }
+
+ public Builder withPaymentOutcome(PaymentOutcome paymentOutcome) {
+ this.paymentOutcome = paymentOutcome;
+ return this;
+ }
+
+ public Builder withCardType(String cardType) {
+ this.cardType = cardType;
+ return this;
+ }
+
+ public Builder withNameOnCard(String nameOnCard) {
+ this.nameOnCard = nameOnCard;
+ return this;
+ }
+
+ public Builder withEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ return this;
+ }
+
+ public Builder withCardExpiry(String cardExpiry) {
+ this.cardExpiry = cardExpiry;
+ return this;
+ }
+
+ public Builder withLastFourDigits(String lastFourDigits) {
+ this.lastFourDigits = lastFourDigits;
+ return this;
+ }
+
+ public Builder withFirstSixDigits(String firstSixDigits) {
+ this.firstSixDigits = firstSixDigits;
+ return this;
+ }
+
+ public Builder withTelephoneNumber(String telephoneNumber) {
+ this.telephoneNumber = telephoneNumber;
+ return this;
+ }
+
+ public Builder withPaymentId(String paymentId) {
+ this.paymentId = paymentId;
+ return this;
+ }
+
+ public Builder withState(PaymentState state) {
+ this.state = state;
+ return this;
+ }
+
+ public TelephonePaymentResponse build () {
+ return new TelephonePaymentResponse(this);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/AuthorisationResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/AuthorisationResource.java
new file mode 100644
index 000000000..c9e442fb8
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/AuthorisationResource.java
@@ -0,0 +1,65 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.model.AuthorisationRequest;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.resources.error.ApiErrorResponse;
+import uk.gov.pay.api.service.AuthorisationService;
+
+import javax.inject.Inject;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path("/")
+@Produces({"application/json"})
+@Tag(name = "Authorise card payments")
+public class AuthorisationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthorisationResource.class);
+ private final AuthorisationService authorisationService;
+
+ @Inject
+ public AuthorisationResource(AuthorisationService authorisationService) {
+ this.authorisationService = authorisationService;
+ }
+
+ @POST
+ @Timed
+ @Path("/v1/auth")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Operation(operationId = "Authorise a MOTO payment",
+ summary = "Send card details to authorise a MOTO payment",
+ description = "You can use this endpoint to [authorise payments](https://docs.payments.service.gov.uk/moto_payments/moto_send_card_details_api/) you have created with `authorisation_mode` set to `moto_api`.",
+ responses = {
+ @ApiResponse(responseCode = "204", description = "Your authorisation request was successful."),
+ @ApiResponse(responseCode = "400", description = "Your request is invalid. Check the `code` and `description` in the response to find out why your request failed.",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "402",
+ description = "The `card_number` you sent is not a valid card number or you chose not to accept this card type. Check the `code` and `description` fields in the response to find out why your request failed.",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "422", description = "A value you sent is invalid or missing. Check the `code` and `description` in the response to find out why your request failed.",
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = "There is something wrong with GOV.UK Pay. If there are no issues on our status page (https://payments.statuspage.io), you can contact us with your error code and we'll investigate.",
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response authorisePayment(@Parameter(required = true)
+ @Valid AuthorisationRequest authorisationRequest) {
+ return authorisationService.authoriseRequest(authorisationRequest);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetOnePaymentStrategy.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetOnePaymentStrategy.java
new file mode 100644
index 000000000..84ef9c7fc
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetOnePaymentStrategy.java
@@ -0,0 +1,34 @@
+package uk.gov.pay.api.resources;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.links.PaymentWithAllLinks;
+import uk.gov.pay.api.service.GetPaymentService;
+
+public class GetOnePaymentStrategy extends LedgerOrConnectorStrategyTemplate {
+
+ private final Account account;
+ private final String paymentId;
+ private final GetPaymentService getPaymentService;
+
+ public GetOnePaymentStrategy(String strategy, Account account, String paymentId, GetPaymentService getPaymentService) {
+ super(strategy);
+ this.account = account;
+ this.paymentId = paymentId;
+ this.getPaymentService = getPaymentService;
+ }
+
+ @Override
+ protected PaymentWithAllLinks executeLedgerOnlyStrategy() {
+ return getPaymentService.getLedgerTransaction(account, paymentId);
+ }
+
+ @Override
+ protected PaymentWithAllLinks executeDefaultStrategy() {
+ return getPaymentService.getPayment(account, paymentId);
+ }
+
+ @Override
+ protected PaymentWithAllLinks executeConnectorOnlyStrategy() {
+ return getPaymentService.getConnectorCharge(account, paymentId);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentEventsStrategy.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentEventsStrategy.java
new file mode 100644
index 000000000..d6945e362
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentEventsStrategy.java
@@ -0,0 +1,34 @@
+package uk.gov.pay.api.resources;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.PaymentEventsResponse;
+import uk.gov.pay.api.service.GetPaymentEventsService;
+
+public class GetPaymentEventsStrategy extends LedgerOrConnectorStrategyTemplate {
+ private final Account account;
+ private final String paymentId;
+ private final GetPaymentEventsService getPaymentEventsService;
+
+ public GetPaymentEventsStrategy(String strategyName, Account account, String paymentId,
+ GetPaymentEventsService getPaymentEventsService) {
+ super(strategyName);
+ this.account = account;
+ this.paymentId = paymentId;
+ this.getPaymentEventsService = getPaymentEventsService;
+ }
+
+ @Override
+ protected PaymentEventsResponse executeLedgerOnlyStrategy() {
+ return getPaymentEventsService.getPaymentEventsFromLedger(account, paymentId);
+ }
+
+ @Override
+ protected PaymentEventsResponse executeDefaultStrategy() {
+ return getPaymentEventsService.getPaymentEvents(account, paymentId);
+ }
+
+ @Override
+ protected PaymentEventsResponse executeConnectorOnlyStrategy() {
+ return getPaymentEventsService.getPaymentEventsFromConnector(account, paymentId);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundStrategy.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundStrategy.java
new file mode 100644
index 000000000..50bc5028f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundStrategy.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.resources;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.RefundResponse;
+import uk.gov.pay.api.service.GetPaymentRefundService;
+
+public class GetPaymentRefundStrategy extends LedgerOrConnectorStrategyTemplate {
+
+ private final Account account;
+ private final String paymentId;
+ private final String refundId;
+ private final GetPaymentRefundService getPaymentRefundsService;
+
+ public GetPaymentRefundStrategy(String strategy, Account account, String paymentId, String refundId,
+ GetPaymentRefundService getPaymentRefundsService) {
+ super(strategy);
+ this.account = account;
+ this.paymentId = paymentId;
+ this.refundId = refundId;
+ this.getPaymentRefundsService = getPaymentRefundsService;
+ }
+
+ @Override
+ protected RefundResponse executeLedgerOnlyStrategy() {
+ return getPaymentRefundsService.getLedgerPaymentRefund(account, paymentId, refundId);
+ }
+
+ @Override
+ protected RefundResponse executeDefaultStrategy() {
+ return getPaymentRefundsService.getPaymentRefund(account, paymentId, refundId);
+ }
+
+ @Override
+ protected RefundResponse executeConnectorOnlyStrategy() {
+ return getPaymentRefundsService.getConnectorPaymentRefund(account, paymentId, refundId);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundsStrategy.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundsStrategy.java
new file mode 100644
index 000000000..9504c840e
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/GetPaymentRefundsStrategy.java
@@ -0,0 +1,35 @@
+package uk.gov.pay.api.resources;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.RefundsResponse;
+import uk.gov.pay.api.service.GetPaymentRefundsService;
+
+public class GetPaymentRefundsStrategy extends LedgerOrConnectorStrategyTemplate {
+
+ private final Account account;
+ private final String paymentId;
+ private final GetPaymentRefundsService getPaymentRefundsService;
+
+ public GetPaymentRefundsStrategy(String strategy, Account account, String paymentId,
+ GetPaymentRefundsService getPaymentRefundsService) {
+ super(strategy);
+ this.account = account;
+ this.paymentId = paymentId;
+ this.getPaymentRefundsService = getPaymentRefundsService;
+ }
+
+ @Override
+ protected RefundsResponse executeLedgerOnlyStrategy() {
+ return getPaymentRefundsService.getLedgerTransactionTransactions(account, paymentId);
+ }
+
+ @Override
+ protected RefundsResponse executeDefaultStrategy() {
+ return executeLedgerOnlyStrategy();
+ }
+
+ @Override
+ protected RefundsResponse executeConnectorOnlyStrategy() {
+ return getPaymentRefundsService.getConnectorPaymentRefunds(account, paymentId);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/HealthCheckResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/HealthCheckResource.java
new file mode 100644
index 000000000..53d213073
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/HealthCheckResource.java
@@ -0,0 +1,59 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import com.codahale.metrics.health.HealthCheck;
+import com.google.common.collect.ImmutableMap;
+import io.dropwizard.setup.Environment;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.Response.status;
+
+@Path("/")
+public class HealthCheckResource {
+ public static final String HEALTHCHECK = "healthcheck";
+ public static final String HEALTHY = "healthy";
+
+ Environment environment;
+
+ @Inject
+ public HealthCheckResource(Environment environment) {
+ this.environment = environment;
+ }
+
+ @GET
+ @Timed
+ @Path(HEALTHCHECK)
+ @Produces(APPLICATION_JSON)
+ public Response healthCheck() {
+ SortedMap results = environment.healthChecks().runHealthChecks();
+
+ Map> response = getResponse(results);
+
+ boolean healthy = results.size() == results.values()
+ .stream()
+ .filter(HealthCheck.Result::isHealthy)
+ .count();
+
+ if(healthy) {
+ return Response.ok().entity(response).build();
+ }
+ return status(503).entity(response).build();
+ }
+
+ private Map> getResponse(SortedMap results) {
+ Map> response = new HashMap<>();
+ for (SortedMap.Entry entry : results.entrySet() ) {
+ response.put(entry.getKey(), ImmutableMap.of(HEALTHY, entry.getValue().isHealthy()));
+ }
+ return response;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/LedgerOrConnectorStrategyTemplate.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/LedgerOrConnectorStrategyTemplate.java
new file mode 100644
index 000000000..29d178fa4
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/LedgerOrConnectorStrategyTemplate.java
@@ -0,0 +1,46 @@
+package uk.gov.pay.api.resources;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public abstract class LedgerOrConnectorStrategyTemplate {
+
+ private static final Logger logger = LoggerFactory.getLogger(LedgerOrConnectorStrategyTemplate.class);
+ private String strategy;
+ private List VALID_STRATEGIES = ImmutableList.of("ledger-only", "connector-only");
+
+ public LedgerOrConnectorStrategyTemplate(String strategy) {
+ this.strategy = strategy;
+ }
+
+ private void validate() {
+ if (!StringUtils.isBlank(strategy) && !VALID_STRATEGIES.contains(strategy)) {
+ logger.warn("Not valid strategy (valid values are \"ledger-only\", \"connector-only\" or empty); using the default strategy");
+ strategy = null;
+ }
+ }
+
+ public T validateAndExecute() {
+ validate();
+
+ if ("connector-only".equals(strategy)) {
+ return executeConnectorOnlyStrategy();
+ } else if ("ledger-only".equals(strategy)) {
+ return executeLedgerOnlyStrategy();
+ }
+
+ return executeDefaultStrategy();
+ }
+
+ protected abstract T executeLedgerOnlyStrategy();
+
+ protected abstract T executeDefaultStrategy();
+
+ protected abstract T executeConnectorOnlyStrategy();
+}
+
+
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentRefundsResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentRefundsResource.java
new file mode 100644
index 000000000..5fb8f489f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentRefundsResource.java
@@ -0,0 +1,175 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.CreatePaymentRefundRequest;
+import uk.gov.pay.api.model.RefundResponse;
+import uk.gov.pay.api.model.RefundsResponse;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.search.card.RefundForSearchResult;
+import uk.gov.pay.api.model.search.card.RefundResult;
+import uk.gov.pay.api.resources.error.ApiErrorResponse;
+import uk.gov.pay.api.service.ConnectorService;
+import uk.gov.pay.api.service.CreateRefundService;
+import uk.gov.pay.api.service.GetPaymentRefundService;
+import uk.gov.pay.api.service.GetPaymentRefundsService;
+import uk.gov.pay.api.service.GetPaymentService;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_200_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_401_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_404_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_429_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_500_DESCRIPTION;
+
+@Path("/v1/payments/{paymentId}/refunds")
+@Tag(name = "Refunding card payments")
+@Produces({"application/json"})
+public class PaymentRefundsResource {
+ private static final Logger logger = LoggerFactory.getLogger(PaymentRefundsResource.class);
+
+ private final GetPaymentRefundsService getPaymentRefundsService;
+ private final GetPaymentRefundService getPaymentRefundService;
+ private final CreateRefundService createRefundService;
+
+ @Inject
+ public PaymentRefundsResource(PublicApiConfig configuration,
+ GetPaymentRefundsService getPaymentRefundsService,
+ GetPaymentRefundService getPaymentRefundService,
+ ConnectorService connectorService,
+ GetPaymentService getPaymentService,
+ CreateRefundService createRefundService) {
+ this.getPaymentRefundsService = getPaymentRefundsService;
+ this.getPaymentRefundService = getPaymentRefundService;
+ this.createRefundService = createRefundService;
+ }
+
+ @GET
+ @Timed
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Get all refunds for a payment",
+ summary = "Get information about a payment’s refunds",
+ description = "You can use this endpoint to [get a list of refunds for a payment]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/#get-all-refunds-for-a-single-payment).",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RefundForSearchResult.class))),
+ @ApiResponse(responseCode = "401", description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public RefundsResponse getRefunds(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId") @Parameter(name = "paymentId",
+ description = "The unique `payment_id` of the payment you want a list of refunds for.") String paymentId,
+ @Parameter(hidden = true) @HeaderParam("X-Ledger") String strategyName) {
+
+ logger.info("Get refunds for payment request - paymentId={} using strategy={}", paymentId, strategyName);
+
+ GetPaymentRefundsStrategy strategy = new GetPaymentRefundsStrategy(strategyName, account, paymentId, getPaymentRefundsService);
+ RefundsResponse refundsResponse = strategy.validateAndExecute();
+
+ logger.debug("refund returned - [ {} ]", refundsResponse);
+ return refundsResponse;
+ }
+
+ @GET
+ @Timed
+ @Path("/{refundId}")
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Get a payment refund",
+ summary = "Check the status of a refund",
+ description = "You can use this endpoint to [get details about an individual refund]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/#checking-the-status-of-a-refund).",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RefundResult.class))),
+ @ApiResponse(responseCode = "401", description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ @Produces(APPLICATION_JSON)
+ public RefundResponse getRefundById(@Parameter(hidden = true)
+ @Auth Account account,
+ @PathParam("paymentId") @Parameter(name = "paymentId",
+ description = "The unique `payment_id` of the payment you want to view a refund of.") String paymentId,
+ @PathParam("refundId") @Parameter(name = "refundId",
+ description = "The unique `refund_id` of the refund you want to view. " +
+ "If one payment has multiple refunds, each refund has a different `refund_id`.") String refundId,
+ @Parameter(hidden = true) @HeaderParam("X-Ledger") String strategyName) {
+
+ logger.info("Payment refund request - paymentId={}, refundId={}", paymentId, refundId);
+
+ var strategy = new GetPaymentRefundStrategy(strategyName, account, paymentId, refundId, getPaymentRefundService);
+ RefundResponse refundResponse = strategy.validateAndExecute();
+
+ logger.info("refund returned - [ {} ]", refundResponse);
+
+ return refundResponse;
+ }
+
+ @POST
+ @Timed
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Submit a refund for a payment",
+ summary = "Refund a payment",
+ description = "You can use this endpoint to [fully or partially refund a payment]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments).",
+ responses = {
+ @ApiResponse(responseCode = "200", description = "successful operation",
+ content = @Content(schema = @Schema(implementation = RefundResult.class))),
+ @ApiResponse(responseCode = "202", description = "ACCEPTED"),
+ @ApiResponse(responseCode = "401", description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "412", description = "Refund amount available mismatch"),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response submitRefund(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId") @Parameter(name = "paymentId",
+ description = "The unique `payment_id` of the payment you want to refund.") String paymentId,
+ @Parameter(required = true, description = "requestPayload")
+ CreatePaymentRefundRequest requestPayload) {
+
+ logger.info("Create a refund for payment request - paymentId={}", paymentId);
+ RefundResponse refundResponse = createRefundService.createRefund(account, paymentId, requestPayload);
+ return Response.accepted(refundResponse).build();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentsResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentsResource.java
new file mode 100644
index 000000000..4d51854ef
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/PaymentsResource.java
@@ -0,0 +1,407 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.http.HttpStatus;
+import org.hibernate.validator.constraints.Length;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CaptureChargeException;
+import uk.gov.pay.api.model.CreateCardPaymentRequest;
+import uk.gov.pay.api.model.CreatePaymentResult;
+import uk.gov.pay.api.model.CreatedPaymentWithAllLinks;
+import uk.gov.pay.api.model.PaymentEventsResponse;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.links.PaymentWithAllLinks;
+import uk.gov.pay.api.model.search.card.PaymentSearchResults;
+import uk.gov.pay.api.resources.error.ApiErrorResponse;
+import uk.gov.pay.api.service.CancelPaymentService;
+import uk.gov.pay.api.service.CapturePaymentService;
+import uk.gov.pay.api.service.CreatePaymentService;
+import uk.gov.pay.api.service.GetPaymentEventsService;
+import uk.gov.pay.api.service.GetPaymentService;
+import uk.gov.pay.api.service.PaymentSearchParams;
+import uk.gov.pay.api.service.PaymentSearchService;
+import uk.gov.pay.api.service.PublicApiUriGenerator;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.validation.Valid;
+import javax.validation.constraints.Pattern;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import static java.lang.String.format;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.apache.http.HttpHeaders.CACHE_CONTROL;
+import static org.apache.http.HttpHeaders.PRAGMA;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_200_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_400_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_401_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_404_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_409_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_422_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_429_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_500_DESCRIPTION;
+
+@Path("/")
+@Tag(name = "Card payments")
+@Produces({"application/json"})
+public class PaymentsResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(PaymentsResource.class);
+
+ private final CreatePaymentService createPaymentService;
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private final PaymentSearchService paymentSearchService;
+ private final GetPaymentService getPaymentService;
+ private final CapturePaymentService capturePaymentService;
+ private final CancelPaymentService cancelPaymentService;
+ private final GetPaymentEventsService getPaymentEventsService;
+
+ @Inject
+ public PaymentsResource(CreatePaymentService createPaymentService,
+ PaymentSearchService paymentSearchService,
+ PublicApiUriGenerator publicApiUriGenerator,
+ GetPaymentService getPaymentService,
+ CapturePaymentService capturePaymentService,
+ CancelPaymentService cancelPaymentService,
+ GetPaymentEventsService getPaymentEventsService) {
+ this.createPaymentService = createPaymentService;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.paymentSearchService = paymentSearchService;
+ this.getPaymentService = getPaymentService;
+ this.capturePaymentService = capturePaymentService;
+ this.cancelPaymentService = cancelPaymentService;
+ this.getPaymentEventsService = getPaymentEventsService;
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/payments/{paymentId}")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Get a payment",
+ summary = "Get information about a single payment",
+ description = "You can use this endpoint to [get details about a single payment you’ve previously created]" +
+ "(https://docs.payments.service.gov.uk/reporting/#get-information-about-a-single-payment).",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = PaymentWithAllLinks.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response getPayment(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId")
+ @Parameter(name = "paymentId", description = "Returns the payment with the matching `payment_id`.", example = "hu20sqlact5260q2nanm0q8u93")
+ String paymentId,
+ @Parameter(hidden = true) @HeaderParam("X-Ledger") String strategyName) {
+
+ logger.info("Payment request - paymentId={}", paymentId);
+
+ var strategy = new GetOnePaymentStrategy(strategyName, account, paymentId, getPaymentService);
+ PaymentWithAllLinks payment = strategy.validateAndExecute();
+
+ logger.info("Payment returned - [ {} ]", payment);
+ return Response.ok(payment)
+ .header(PRAGMA, "no-cache")
+ .header(CACHE_CONTROL, "no-store")
+ .build();
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/payments/{paymentId}/events")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Get events for a payment",
+ summary = "Get a payment's events",
+ description = "You can use this endpoint to " +
+ "[get a list of a payment’s events](https://docs.payments.service.gov.uk/reporting/#get-a-payment-s-events). " +
+ "A payment event is when a payment’s `state` changes, such as when the payment is created, or when the paying user submits their details.",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = PaymentEventsResponse.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public PaymentEventsResponse getPaymentEvents(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId")
+ @Parameter(name = "paymentId", description = "Payment identifier", example = "hu20sqlact5260q2nanm0q8u93")
+ String paymentId,
+ @Parameter(hidden = true) @HeaderParam("X-Ledger") String strategyName) {
+
+ logger.info("Payment events request - payment_id={}", paymentId);
+
+ var strategy = new GetPaymentEventsStrategy(strategyName, account, paymentId, getPaymentEventsService);
+ PaymentEventsResponse paymentEventsResponse = strategy.validateAndExecute();
+
+ logger.info("Payment events returned - [ {} ]", paymentEventsResponse);
+
+ return paymentEventsResponse;
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/payments")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Search payments",
+ summary = "Search payments",
+ description = "You can use this endpoint to [search for payments you’ve previously created](https://docs.payments.service.gov.uk/reporting/#search-payments/). " +
+ "Payments are sorted by date, with the most recently-created payment appearing first.",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = PaymentSearchResults.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "422",
+ description = "Invalid parameters: from_date, to_date, status, display_size. See Public API documentation for the correct data formats",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response searchPayments(@Parameter(hidden = true)
+ @Auth Account account,
+ @Parameter(description = "Returns payments with `reference` values exactly matching your specified value.")
+ @QueryParam("reference") String reference,
+ @Parameter(description = "Returns payments with matching `email` values. You can send full or partial email addresses. " +
+ "`email` is the paying user’s email address.")
+ @QueryParam("email") String email,
+ @Parameter(description = "Returns payments in a matching `state`. `state` reflects where a payment is in the " +
+ "[payment status lifecycle](https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle).", example = "success",
+ schema = @Schema(allowableValues = {"created", "started", "submitted", "success", "failed", "cancelled", "error"}))
+ @QueryParam("state") String state,
+ @Parameter(description = "Returns payments paid with a particular card brand.")
+ @QueryParam("card_brand") String cardBrand,
+ @Parameter(description = "Returns payments created on or after the `from_date`. " +
+ "Date and time must be coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("from_date") String fromDate,
+ @Parameter(description = "Returns payments created before the `to_date`. " +
+ "Date and time must be coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("to_date") String toDate,
+ @Parameter(description = "Returns a [specific page of results](https://docs.payments.service.gov.uk/api_reference/#pagination). Defaults to `1`.")
+ @QueryParam("page") String pageNumber,
+ @Parameter(description = "The number of payments returned " +
+ "[per results page](https://docs.payments.service.gov.uk/api_reference/#pagination). Defaults to `500`. Maximum value is `500`.")
+ @QueryParam("display_size") String displaySize,
+ @Parameter(description = "Returns payments paid with cards under this cardholder name.")
+ @QueryParam("cardholder_name") String cardHolderName,
+ @Parameter(description = "Returns payments paid by cards beginning with the `first_digits_card_number` value. "
+ + "`first_digits_card_number` value must be 6 digits.")
+ @QueryParam("first_digits_card_number") String firstDigitsCardNumber,
+ @Parameter(description = "Returns payments paid by cards ending with the `last_digits_card_number` value. " +
+ "`last_digits_card_number` value must be 4 digits.", hidden = false)
+ @QueryParam("last_digits_card_number") String lastDigitsCardNumber,
+ @Parameter(description = "Returns payments settled on or after the `from_settled_date` value. " +
+ "You can only search by settled date if your payment service provider is Stripe. " +
+ "Date must be in ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Payments are settled when your payment service provider sends funds to your bank account.")
+ @QueryParam("from_settled_date") String fromSettledDate,
+ @Parameter(description = "Returns payments settled before the `to_settled_date` value. " +
+ "You can only search by settled date if your payment service provider is Stripe. " +
+ "Date must be in ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Payments are settled when your payment service provider sends funds to your bank account.")
+ @QueryParam("to_settled_date") String toSettledDate,
+ @Parameter(description = "Returns payments that were authorised using the agreement with this `agreement_id`. " +
+ "Must be an exact match.", example = "abcefghjklmnopqr1234567890")
+ @QueryParam("agreement_id") String agreementId,
+ @Context UriInfo uriInfo) {
+
+ logger.info("Payments search request - [ {} ]",
+ format("reference:%s, email: %s, status: %s, card_brand %s, fromDate: %s, toDate: %s, page: %s, " +
+ "display_size: %s, cardholder_name: %s, first_digits_card_number: %s, " +
+ "last_digits_card_number: %s, from_settled_date: %s, to_settled_date: %s, agreement_id: %s",
+ reference, email, state, cardBrand, fromDate, toDate, pageNumber, displaySize,
+ cardHolderName, firstDigitsCardNumber, lastDigitsCardNumber, fromSettledDate, toSettledDate, agreementId));
+
+ var paymentSearchParams = new PaymentSearchParams.Builder()
+ .withReference(reference)
+ .withEmail(email)
+ .withState(state)
+ .withCardBrand(cardBrand)
+ .withFromDate(fromDate)
+ .withToDate(toDate)
+ .withPageNumber(pageNumber)
+ .withDisplaySize(displaySize)
+ .withCardHolderName(cardHolderName)
+ .withFirstDigitsCardNumber(firstDigitsCardNumber)
+ .withLastDigitsCardNumber(lastDigitsCardNumber)
+ .withFromSettledDate(fromSettledDate)
+ .withToSettledDate(toSettledDate)
+ .withAgreementId(agreementId)
+ .build();
+
+ return paymentSearchService.searchLedgerPayments(account, paymentSearchParams);
+ }
+
+ @POST
+ @Timed
+ @Path("/v1/payments")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Create a payment",
+ summary = "Create a payment",
+ description = "You can use this endpoint to [create a new payment](https://docs.payments.service.gov.uk/making_payments/).",
+ responses = {
+ @ApiResponse(responseCode = "201", description = "Created",
+ content = @Content(schema = @Schema(implementation = CreatePaymentResult.class))),
+ @ApiResponse(responseCode = "400", description = RESPONSE_400_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "422",
+ description = RESPONSE_422_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response createNewPayment(@Parameter(hidden = true) @Auth Account account,
+ @Parameter(required = true, description = "requestPayload")
+ @Valid CreateCardPaymentRequest createCardPaymentRequest,
+ @Nullable
+ @Length(min = 1, max = 255, message = "Header [Idempotency-Key] can have a size between 1 and 255")
+ @Pattern(regexp = "^$|^[a-zA-Z0-9-]+$", message = "Header [Idempotency-Key] can only contain alphanumeric characters and hyphens")
+ @HeaderParam("Idempotency-Key")
+ String idempotencyKey) {
+ logger.info("Payment create request parsed to {}", createCardPaymentRequest);
+
+ CreatedPaymentWithAllLinks createdPayment = createPaymentService.create(account, createCardPaymentRequest, idempotencyKey);
+
+ PaymentWithAllLinks paymentWithAllLinks = createdPayment.getPayment();
+ Response.ResponseBuilder response;
+
+ switch (createdPayment.getWhenCreated()) {
+ case BRAND_NEW:
+ response = Response
+ .created(publicApiUriGenerator.getPaymentURI(paymentWithAllLinks.getPaymentId()));
+ break;
+ case EXISTING:
+ response = Response.ok();
+ break;
+ default:
+ throw new IllegalArgumentException(format("Unrecognised WhenCreated enum: %s", createdPayment.getWhenCreated()));
+ }
+
+ response.entity(paymentWithAllLinks)
+ .header(PRAGMA, "no-cache")
+ .header(CACHE_CONTROL, "no-store");
+
+ logger.info("Payment returned (created): [ {} ]", paymentWithAllLinks);
+ return response.build();
+ }
+
+ @POST
+ @Timed
+ @Path("/v1/payments/{paymentId}/cancel")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Cancel a payment",
+ summary = "Cancel payment",
+ description = "You can use this endpoint [to cancel an unfinished payment]" +
+ "(https://docs.payments.service.gov.uk/making_payments/#cancel-a-payment-that-s-in-progress).",
+ responses = {
+ @ApiResponse(responseCode = "204", description = "No Content"),
+ @ApiResponse(responseCode = "400", description = "Cancellation of payment failed",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "409", description = RESPONSE_409_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response cancelPayment(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId")
+ @Parameter(name = "paymentId", description = "The `payment_id` of the payment you’re cancelling.", example = "hu20sqlact5260q2nanm0q8u93")
+ String paymentId) {
+
+ logger.info("Payment cancel request - payment_id=[{}]", paymentId);
+
+ return cancelPaymentService.cancel(account, paymentId);
+ }
+
+ @POST
+ @Timed
+ @Path("/v1/payments/{paymentId}/capture")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Capture a payment",
+ summary = "Take a delayed payment",
+ description = "You can use this endpoint to [take (‘capture’) a delayed payment from the paying user’s bank account]" +
+ "(https://docs.payments.service.gov.uk/delayed_capture/).",
+ responses = {
+ @ApiResponse(responseCode = "204", description = "No Content"),
+ @ApiResponse(responseCode = "400", description = "Capture of payment failed",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "404", description = RESPONSE_404_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "409", description = RESPONSE_409_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public Response capturePayment(@Parameter(hidden = true) @Auth Account account,
+ @PathParam("paymentId")
+ @Parameter(name = "paymentId", description = "The `payment_id` of the payment you’re capturing.", example = "hu20sqlact5260q2nanm0q8u93")
+ String paymentId) {
+ logger.info("Payment capture request - payment_id=[{}]", paymentId);
+
+ Response connectorResponse = capturePaymentService.capture(account, paymentId);
+
+ if (connectorResponse.getStatus() == HttpStatus.SC_NO_CONTENT) {
+ connectorResponse.close();
+ return Response.noContent().build();
+ }
+
+ throw new CaptureChargeException(connectorResponse);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/RequestDeniedResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/RequestDeniedResource.java
new file mode 100644
index 000000000..b6926c771
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/RequestDeniedResource.java
@@ -0,0 +1,62 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static uk.gov.pay.api.model.RequestError.Code.REQUEST_DENIED_ERROR;
+import static uk.gov.pay.api.model.RequestError.aRequestError;
+
+@Path("/")
+public class RequestDeniedResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(RequestDeniedResource.class);
+
+ @GET
+ @Timed
+ @Path("request-denied")
+ @Produces(APPLICATION_JSON)
+ public Response requestDeniedGet(@HeaderParam("x-naxsi_sig") String naxsiViolatedRules) {
+ return requestDenied(naxsiViolatedRules);
+ }
+
+ @POST
+ @Timed
+ @Path("request-denied")
+ @Produces(APPLICATION_JSON)
+ public Response requestDeniedPost(@HeaderParam("x-naxsi_sig") String naxsiViolatedRules) {
+ return requestDenied(naxsiViolatedRules);
+ }
+
+ @PUT
+ @Timed
+ @Path("request-denied")
+ @Produces(APPLICATION_JSON)
+ public Response requestDeniedPut(@HeaderParam("x-naxsi_sig") String naxsiViolatedRules) {
+ return requestDenied(naxsiViolatedRules);
+ }
+
+ @DELETE
+ @Timed
+ @Path("request-denied")
+ @Produces(APPLICATION_JSON)
+ public Response requestDeniedDelete(@HeaderParam("x-naxsi_sig") String naxsiViolatedRules) {
+ return requestDenied(naxsiViolatedRules);
+ }
+
+ private Response requestDenied(@HeaderParam("x-naxsi_sig") String naxsiViolatedRules) {
+ logger.info("Naxsi rules violated - [ {} ]", naxsiViolatedRules);
+ return Response.status(BAD_REQUEST).entity(aRequestError(REQUEST_DENIED_ERROR)).build();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchDisputesResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchDisputesResource.java
new file mode 100644
index 000000000..c82b5336c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchDisputesResource.java
@@ -0,0 +1,113 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.search.dispute.DisputesSearchResults;
+import uk.gov.pay.api.resources.error.ApiErrorResponse;
+import uk.gov.pay.api.service.DisputesSearchParams;
+import uk.gov.pay.api.service.SearchDisputesService;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import static java.lang.String.format;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_200_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_401_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_429_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_500_DESCRIPTION;
+import static uk.gov.pay.api.validation.DisputeSearchValidator.validateDisputeParameters;
+
+@Path("/")
+@Tag(name = "Disputes")
+@Produces({"application/json"})
+public class SearchDisputesResource {
+ private static final Logger logger = LoggerFactory.getLogger(SearchDisputesResource.class);
+ private final SearchDisputesService searchDisputesService;
+
+ @Inject
+ public SearchDisputesResource(SearchDisputesService searchDisputesService) {
+ this.searchDisputesService = searchDisputesService;
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/disputes")
+ @Produces(APPLICATION_JSON)
+ @Operation(security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Search disputes",
+ summary = "Search disputes",
+ description = "You can use this endpoint to search disputes. " +
+ "A dispute is when [a paying user challenges a completed payment through their bank](https://docs.payments.service.gov.uk/disputes/).",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = DisputesSearchResults.class))),
+ @ApiResponse(responseCode = "401",
+ description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "422",
+ description = "Invalid parameters: from_date, to_date, from_settled_date, to_settled_date, status, display_size. See Public API documentation for the correct data formats",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "429", description = RESPONSE_429_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class)))
+ }
+ )
+ public DisputesSearchResults searchDisputes(@Parameter(hidden = true)
+ @Auth Account account,
+ @Parameter(description = "Returns disputes raised on or after the `from_date`. " +
+ "Date and time must be coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("from_date") String fromDate,
+ @Parameter(description = "Returns disputes raised before the `to_date`. " +
+ "Date and time must be coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("to_date") String toDate,
+ @Parameter(description = "Returns disputes settled on or after the `from_settled_date`. " +
+ "Date must be in ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Disputes are settled when your payment service provider takes the disputed amount from a payout to your bank account.")
+ @QueryParam("from_settled_date") String fromSettledDate,
+ @Parameter(description = "Returns disputes settled before the `to_settled_date`. " +
+ "Date must be in ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Disputes are settled when your payment service provider takes the disputed amount from a payout to your bank account.")
+ @QueryParam("to_settled_date") String toSettledDate,
+ @Parameter(description = "Returns disputes with a matching `status`. `status` reflects what stage of the dispute process a dispute is at. " +
+ "You can [read more about the meanings of the different status values](https://docs.payments.service.gov.uk/disputes/#dispute-status)", example = "won",
+ schema = @Schema(allowableValues = {"needs_response", "under_review", "lost", "won"}))
+ @QueryParam("status") String status,
+ @Parameter(description = "Returns a specific page of results. Defaults to `1`.")
+ @QueryParam("page") String pageNumber,
+ @Parameter(description = "The number of disputes returned per results page. Defaults to `500`. Maximum value is `500`.")
+ @QueryParam("display_size") String displaySize) {
+ logger.info("Disputes search request - [ {} ]",
+ format("from_date: %s, to_date: %s, from_settled_date: %s, to_settled_date: %s, " +
+ "status: s, page: %s, display_size: %s",
+ fromDate, toDate, fromSettledDate, toSettledDate, status, pageNumber, displaySize));
+
+ DisputesSearchParams params = new DisputesSearchParams.Builder()
+ .withFromDate(fromDate)
+ .withToDate(toDate)
+ .withFromSettledDate(fromSettledDate)
+ .withToSettledDate(toSettledDate)
+ .withStatus(status)
+ .withPage(pageNumber)
+ .withDisplaySize(displaySize)
+ .build();
+
+ validateDisputeParameters(params);
+
+ return searchDisputesService.searchDisputes(account, params);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchRefundsResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchRefundsResource.java
new file mode 100644
index 000000000..d2657980b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SearchRefundsResource.java
@@ -0,0 +1,102 @@
+package uk.gov.pay.api.resources;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.RequestError;
+import uk.gov.pay.api.model.search.card.SearchRefundsResults;
+import uk.gov.pay.api.service.RefundsParams;
+import uk.gov.pay.api.service.SearchRefundsService;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import static java.lang.String.format;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_200_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_401_DESCRIPTION;
+import static uk.gov.pay.api.common.ResponseConstants.RESPONSE_500_DESCRIPTION;
+
+@Path("/")
+@Tag(name = "Refunding card payments")
+@Produces({"application/json"})
+public class SearchRefundsResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(SearchRefundsResource.class);
+
+ private final SearchRefundsService searchRefundsService;
+
+ @Inject
+ public SearchRefundsResource(SearchRefundsService searchRefundsService, PublicApiConfig configuration) {
+ this.searchRefundsService = searchRefundsService;
+ }
+
+ @GET
+ @Timed
+ @Path("/v1/refunds")
+ @Produces(APPLICATION_JSON)
+ @Operation(
+ security = {@SecurityRequirement(name = "BearerAuth")},
+ operationId = "Search refunds",
+ summary = "Search refunds",
+ description = "You can use this endpoint to [search refunds you’ve previously created]" +
+ "(https://docs.payments.service.gov.uk/refunding_payments/#searching-refunds). " +
+ "The refunds are sorted by date, with the most recently created refunds appearing first.",
+ responses = {
+ @ApiResponse(responseCode = "200", description = RESPONSE_200_DESCRIPTION, content = @Content(schema = @Schema(implementation = SearchRefundsResults.class))),
+ @ApiResponse(responseCode = "401", description = RESPONSE_401_DESCRIPTION),
+ @ApiResponse(responseCode = "422", description = "Invalid parameters. See Public API documentation for the correct data formats",
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ @ApiResponse(responseCode = "500", description = RESPONSE_500_DESCRIPTION,
+ content = @Content(schema = @Schema(implementation = RequestError.class))),
+ }
+ )
+ public SearchRefundsResults searchRefunds(@Parameter(hidden = true)
+ @Auth Account account,
+ @Parameter(description = "Returns refunds created on or after the `from_date`. " +
+ "Date and time must use Coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("from_date") String fromDate,
+ @Parameter(description = "Returns refunds created before the `to_date`. " +
+ "Date and time must use Coordinated Universal Time (UTC) and ISO 8601 format to second-level accuracy - `YYYY-MM-DDThh:mm:ssZ`.", example = "2015-08-13T12:35:00Z")
+ @QueryParam("to_date") String toDate,
+ @Parameter(description = "Returns refunds settled on or after the `from_settled_date` value. " +
+ "You can only use `from_settled_date` if your payment service provider is Stripe. " +
+ "Date must use ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Refunds are settled when Stripe takes the refund from your account balance.", example="2022-08-13")
+ @QueryParam("from_settled_date") String fromSettledDate,
+ @Parameter(description = "Returns refunds settled before the `to_settled_date` value. " +
+ "You can only use `to_settled_date` if your payment service provider is Stripe. " +
+ "Date must use ISO 8601 format to date-level accuracy - `YYYY-MM-DD`. " +
+ "Refunds are settled when Stripe takes the refund from your account balance.", example="2022-08-13")
+ @QueryParam("to_settled_date") String toSettledDate,
+ @Parameter(description = "Returns a [specific page of results](https://docs.payments.service.gov.uk/api_reference/#pagination). " +
+ "Defaults to `1`.")
+ @QueryParam("page") String pageNumber,
+ @Parameter(description = "The number of refunds returned [per results page](https://docs.payments.service.gov.uk/api_reference/#pagination). " +
+ "Defaults to `500`. Maximum value is `500`.", hidden = false)
+ @QueryParam("display_size") String displaySize) {
+
+ logger.info("Refunds search request - [ {} ]",
+ format("from_date: %s, to_date: %s, page: %s, display_size: %s," +
+ "from_settled_date: %s, to_settled_date: %s",
+ fromDate, toDate, pageNumber, displaySize, fromSettledDate, toSettledDate));
+
+ RefundsParams refundsParams = new RefundsParams(fromDate, toDate, pageNumber, displaySize,
+ fromSettledDate, toSettledDate);
+
+ return searchRefundsService.searchLedgerRefunds(account, refundsParams);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SecuritytxtResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SecuritytxtResource.java
new file mode 100644
index 000000000..cee22613a
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/SecuritytxtResource.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.resources;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.Date;
+
+@Path("/")
+public class SecuritytxtResource {
+
+ // https://gds-way.cloudapps.digital/standards/vulnerability-disclosure.html
+ private static final URI CABINET_OFFICE_SECURITY_TXT = URI.create("https://vdp.cabinetoffice.gov.uk/.well-known/security.txt");
+
+ @GET
+ @Path("/.well-known/security.txt")
+ public Response redirectFromWellKnownSecuritytxt() {
+ return redirectToCabinetOfficeSecuritytxt();
+ }
+
+ @GET
+ @Path("/security.txt")
+ public Response redirectFromSecuritytxt() {
+ return redirectToCabinetOfficeSecuritytxt();
+ }
+
+ private static Response redirectToCabinetOfficeSecuritytxt() {
+ return Response
+ .status(Response.Status.FOUND)
+ .location(CABINET_OFFICE_SECURITY_TXT)
+ .cacheControl(CacheControl.valueOf("no-cache"))
+ .expires(new Date())
+ .build();
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/error/ApiErrorResponse.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/error/ApiErrorResponse.java
new file mode 100644
index 000000000..993a2d163
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/error/ApiErrorResponse.java
@@ -0,0 +1,66 @@
+package uk.gov.pay.api.resources.error;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+import static java.lang.String.format;
+
+@Schema(name = "ErrorResponse", description = "An error response")
+@JsonInclude(NON_NULL)
+public class ApiErrorResponse {
+
+ public enum Code {
+
+ TOO_MANY_REQUESTS_ERROR("P0900", "Too many requests");
+
+ private String value;
+ private String format;
+
+ Code(String value, String format) {
+ this.value = value;
+ this.format = format;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+ }
+
+ private final Code code;
+ private final String description;
+
+ public static ApiErrorResponse anApiErrorResponse(Code code, Object... parameters) {
+ return new ApiErrorResponse(code, parameters);
+ }
+
+ private ApiErrorResponse(Code code, Object... parameters) {
+ this.code = code;
+ this.description = format(code.getFormat(), parameters);
+ }
+
+ @Schema(example = "P0900", description = "A GOV.UK Pay API error code. " +
+ "You can [find out more about this code in our documentation]" +
+ "(https://docs.payments.service.gov.uk/api_reference/#gov-uk-pay-api-error-codes).")
+ public String getCode() {
+ return code.value();
+ }
+
+ @Schema(example = "Too many requests", description = "Additional details about the error")
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return "ApiErrorResponse{" +
+ "code=" + code.value() +
+ ", name=" + code +
+ ", description='" + description + '\'' +
+ '}';
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/telephone/TelephonePaymentNotificationResource.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/telephone/TelephonePaymentNotificationResource.java
new file mode 100644
index 000000000..61e29a54f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/resources/telephone/TelephonePaymentNotificationResource.java
@@ -0,0 +1,49 @@
+package uk.gov.pay.api.resources.telephone;
+
+import com.codahale.metrics.annotation.Timed;
+import io.dropwizard.auth.Auth;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.telephone.CreateTelephonePaymentRequest;
+import uk.gov.pay.api.model.telephone.TelephonePaymentResponse;
+import uk.gov.pay.api.service.telephone.CreateTelephonePaymentService;
+
+import javax.inject.Inject;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path("/")
+public class TelephonePaymentNotificationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(TelephonePaymentNotificationResource.class);
+
+ private final CreateTelephonePaymentService createTelephonePaymentService;
+
+ @Inject
+ public TelephonePaymentNotificationResource(CreateTelephonePaymentService createTelephonePaymentService) {
+ this.createTelephonePaymentService = createTelephonePaymentService;
+ }
+
+
+ @POST
+ @Timed
+ @Path("/v1/payment_notification")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response newPayment(@Auth Account account, @Valid CreateTelephonePaymentRequest createTelephonePaymentRequest) {
+ Pair responseAndStatusCode = createTelephonePaymentService.create(account, createTelephonePaymentRequest);
+ var response = responseAndStatusCode.getLeft();
+ var statusCode = responseAndStatusCode.getRight();
+
+ return Response.status(statusCode).entity(response).build();
+ }
+}
+
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/AuthorisationService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/AuthorisationService.java
new file mode 100644
index 000000000..7329093ae
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/AuthorisationService.java
@@ -0,0 +1,36 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.exception.AuthorisationRequestException;
+import uk.gov.pay.api.model.AuthorisationRequest;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Response;
+
+import static org.apache.http.HttpStatus.SC_NO_CONTENT;
+
+public class AuthorisationService {
+
+ private final Client client;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public AuthorisationService(Client client, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public Response authoriseRequest(AuthorisationRequest authorisationRequest) {
+ Response response = client
+ .target(connectorUriGenerator.authorisationURI())
+ .request()
+ .post(Entity.json(authorisationRequest));
+
+ if (response.getStatus() != SC_NO_CONTENT) {
+ throw new AuthorisationRequestException(response);
+ }
+
+ return Response.noContent().build();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CancelPaymentService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CancelPaymentService.java
new file mode 100644
index 000000000..39227ef19
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CancelPaymentService.java
@@ -0,0 +1,36 @@
+package uk.gov.pay.api.service;
+
+import org.apache.http.HttpStatus;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CancelChargeException;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.core.Response;
+
+public class CancelPaymentService {
+
+ private final Client client;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public CancelPaymentService(Client client, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public Response cancel(Account account, String chargeId) {
+ Response connectorResponse = client
+ .target(connectorUriGenerator.cancelURI(account, chargeId))
+ .request()
+ .post(null);
+
+ if (connectorResponse.getStatus() == HttpStatus.SC_NO_CONTENT) {
+ connectorResponse.close();
+ return Response.noContent().build();
+ }
+
+ throw new CancelChargeException(connectorResponse);
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CapturePaymentService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CapturePaymentService.java
new file mode 100644
index 000000000..0a9abd3d1
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CapturePaymentService.java
@@ -0,0 +1,28 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Response;
+
+public class CapturePaymentService {
+
+ private final Client client;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public CapturePaymentService(Client client, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public Response capture(Account account, String chargeId) {
+ return client
+ .target(connectorUriGenerator.captureURI(account, chargeId))
+ .request()
+ .post(Entity.json("{}"));
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorService.java
new file mode 100644
index 000000000..d4016df76
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorService.java
@@ -0,0 +1,120 @@
+package uk.gov.pay.api.service;
+
+import org.apache.http.HttpStatus;
+import uk.gov.pay.api.agreement.model.AgreementCreatedResponse;
+import uk.gov.pay.api.agreement.model.CreateAgreementRequest;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CancelAgreementException;
+import uk.gov.pay.api.exception.CreateAgreementException;
+import uk.gov.pay.api.exception.GetChargeException;
+import uk.gov.pay.api.exception.GetEventsException;
+import uk.gov.pay.api.exception.GetRefundException;
+import uk.gov.pay.api.exception.GetRefundsException;
+import uk.gov.pay.api.model.Charge;
+import uk.gov.pay.api.model.ChargeFromResponse;
+import uk.gov.pay.api.model.PaymentEvents;
+import uk.gov.pay.api.model.RefundFromConnector;
+import uk.gov.pay.api.model.RefundsFromConnector;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.client.Entity.json;
+import static org.apache.http.HttpStatus.SC_OK;
+
+public class ConnectorService {
+ private final Client client;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public ConnectorService(Client client, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public Charge getCharge(Account account, String paymentId) {
+ Response response = client
+ .target(connectorUriGenerator.chargeURI(account, paymentId))
+ .request()
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ ChargeFromResponse chargeFromResponse = response.readEntity(ChargeFromResponse.class);
+ return Charge.from(chargeFromResponse);
+ }
+
+ throw new GetChargeException(response);
+ }
+
+ public PaymentEvents getChargeEvents(Account account, String paymentId) {
+ Response connectorResponse = client
+ .target(connectorUriGenerator.chargeEventsURI(account, paymentId))
+ .request()
+ .accept(MediaType.APPLICATION_JSON)
+ .get();
+
+ if (connectorResponse.getStatus() == SC_OK) {
+ return connectorResponse.readEntity(PaymentEvents.class);
+ }
+
+ throw new GetEventsException(connectorResponse);
+ }
+
+ public RefundsFromConnector getPaymentRefunds(String accountId, String paymentId) {
+ Response connectorResponse = client
+ .target(connectorUriGenerator.refundsForPaymentURI(accountId, paymentId))
+ .request()
+ .get();
+
+ if (connectorResponse.getStatus() == SC_OK) {
+ return connectorResponse.readEntity(RefundsFromConnector.class);
+ }
+
+ throw new GetRefundsException(connectorResponse);
+ }
+
+ public RefundFromConnector getPaymentRefund(String accountId, String paymentId, String refundId) {
+ Response connectorResponse = client
+ .target(connectorUriGenerator.refundForPaymentURI(accountId, paymentId, refundId))
+ .request()
+ .accept(MediaType.APPLICATION_JSON)
+ .get();
+
+ if (connectorResponse.getStatus() == SC_OK) {
+ return connectorResponse.readEntity(RefundFromConnector.class);
+ }
+
+ throw new GetRefundException(connectorResponse);
+ }
+
+ public AgreementCreatedResponse createAgreement(Account account, CreateAgreementRequest createAgreementRequest) {
+ Response response = client
+ .target(connectorUriGenerator.getAgreementURI(account))
+ .request()
+ .accept(MediaType.APPLICATION_JSON)
+ .post(buildCreateAgreementRequestPayload(createAgreementRequest));
+ if (response.getStatus() != HttpStatus.SC_CREATED) {
+ throw new CreateAgreementException(response);
+ }
+ return response.readEntity(AgreementCreatedResponse.class);
+ }
+
+ public void cancelAgreement(Account account, String agreementId) {
+ Response response = client
+ .target(connectorUriGenerator.cancelAgreementURI(account, agreementId))
+ .request()
+ .post(null);
+ if (response.getStatus() != HttpStatus.SC_NO_CONTENT) {
+ throw new CancelAgreementException(response);
+ }
+
+ response.close();
+ }
+
+ private Entity buildCreateAgreementRequestPayload(CreateAgreementRequest requestPayload) {
+ return json(requestPayload.toConnectorPayload());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorUriGenerator.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorUriGenerator.java
new file mode 100644
index 000000000..c6d774016
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/ConnectorUriGenerator.java
@@ -0,0 +1,85 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.UriBuilder;
+import java.util.Collections;
+import java.util.Map;
+
+import static java.lang.String.format;
+
+public class ConnectorUriGenerator {
+ private final PublicApiConfig configuration;
+
+ @Inject
+ public ConnectorUriGenerator(PublicApiConfig configuration) {
+ this.configuration = configuration;
+ }
+
+ String chargesURI(Account account) {
+ return buildConnectorUri(format("/v1/api/accounts/%s/charges", account.getAccountId()));
+ }
+
+ String chargeURI(Account account, String chargeId) {
+ String path = format("/v1/api/accounts/%s/charges/%s", account.getAccountId(), chargeId); //TODO rename to /payments instead /charges
+ return buildConnectorUri(path);
+ }
+
+ public String chargeEventsURI(Account account, String paymentId) {
+ String path = format("/v1/api/accounts/%s/charges/%s/events", account.getAccountId(), paymentId);
+ return buildConnectorUri(path);
+ }
+
+ String cancelURI(Account account, String paymentId) {
+ String path = format("/v1/api/accounts/%s/charges/%s/cancel", account.getAccountId(), paymentId);
+ return buildConnectorUri(path, Collections.emptyMap());
+ }
+
+ public String telephoneChargesURI(Account account) {
+ return buildConnectorUri(format("/v1/api/accounts/%s/telephone-charges", account.getAccountId()));
+ }
+
+ String captureURI(Account account, String chargeId) {
+ String path = format("/v1/api/accounts/%s/charges/%s/capture", account.getAccountId(), chargeId);
+ return buildConnectorUri(path);
+ }
+
+ String refundsForPaymentURI(String accountId, String chargeId) {
+ String path = format("/v1/api/accounts/%s/charges/%s/refunds", accountId, chargeId);
+ return buildConnectorUri(path);
+ }
+
+ String refundForPaymentURI(String accountId, String chargeId, String refundId) {
+ String path = format("/v1/api/accounts/%s/charges/%s/refunds/%s", accountId, chargeId, refundId);
+ return buildConnectorUri(path);
+ }
+
+ String authorisationURI() {
+ String path = "/v1/api/charges/authorise";
+ return buildConnectorUri(path);
+ }
+
+ public String getAgreementURI(Account account) {
+ String path = format("/v1/api/accounts/%s/agreements", account.getAccountId());
+ return buildConnectorUri(path);
+ }
+
+ public String cancelAgreementURI(Account account, String agreementId) {
+ String path = format("/v1/api/accounts/%s/agreements/%s/cancel", account.getAccountId(), agreementId);
+ return buildConnectorUri(path, Collections.emptyMap());
+ }
+
+ private String buildConnectorUri(String path) {
+ return buildConnectorUri(path, Collections.emptyMap());
+ }
+
+ private String buildConnectorUri(String path, Map params) {
+ UriBuilder builder = UriBuilder.fromPath(configuration.getConnectorUrl()).path(path);
+ params.entrySet().stream()
+ .filter(k -> k.getValue() != null)
+ .forEach(k -> builder.queryParam(k.getKey(), k.getValue()));
+ return builder.toString();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreatePaymentService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreatePaymentService.java
new file mode 100644
index 000000000..2f4bda804
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreatePaymentService.java
@@ -0,0 +1,91 @@
+package uk.gov.pay.api.service;
+
+import org.apache.http.HttpStatus;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CreateChargeException;
+import uk.gov.pay.api.model.Charge;
+import uk.gov.pay.api.model.ChargeFromResponse;
+import uk.gov.pay.api.model.CreateCardPaymentRequest;
+import uk.gov.pay.api.model.CreatedPaymentWithAllLinks;
+import uk.gov.pay.api.model.links.PaymentWithAllLinks;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import java.util.Optional;
+
+import static javax.ws.rs.client.Entity.json;
+import static uk.gov.pay.api.model.CreatedPaymentWithAllLinks.WhenCreated.BRAND_NEW;
+import static uk.gov.pay.api.model.CreatedPaymentWithAllLinks.WhenCreated.EXISTING;
+
+public class CreatePaymentService {
+
+ private final Client client;
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public CreatePaymentService(Client client, PublicApiUriGenerator publicApiUriGenerator, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public CreatedPaymentWithAllLinks create(Account account, CreateCardPaymentRequest createCardPaymentRequest, String idempotencyKey) {
+ Response connectorResponse = createCharge(account, createCardPaymentRequest, idempotencyKey);
+
+ if (connectorCreatedNewPayment(connectorResponse)) {
+ ChargeFromResponse chargeFromResponse = connectorResponse.readEntity(ChargeFromResponse.class);
+ return CreatedPaymentWithAllLinks.of(buildResponseModel(Charge.from(chargeFromResponse)), BRAND_NEW);
+ }
+
+ if (connectorReturnedExistingPayment(connectorResponse)) {
+ ChargeFromResponse chargeFromResponse = connectorResponse.readEntity(ChargeFromResponse.class);
+ return CreatedPaymentWithAllLinks.of(buildResponseModel(Charge.from(chargeFromResponse)), EXISTING);
+ }
+
+ throw new CreateChargeException(connectorResponse);
+ }
+ private PaymentWithAllLinks buildResponseModel(Charge chargeFromResponse) {
+ return PaymentWithAllLinks.getPaymentWithLinks(
+ chargeFromResponse,
+ publicApiUriGenerator.getPaymentURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentEventsURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentCancelURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentRefundsURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentCaptureURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentAuthorisationURI());
+ }
+
+ private boolean connectorCreatedNewPayment(Response connectorResponse) {
+ return connectorResponse.getStatus() == HttpStatus.SC_CREATED;
+ }
+
+ private boolean connectorReturnedExistingPayment(Response connectorResponse) {
+ return connectorResponse.getStatus() == HttpStatus.SC_OK;
+ }
+
+
+ private Response createCharge(Account account, CreateCardPaymentRequest createCardPaymentRequest, String idempotencyKey) {
+ MultivaluedMap headers = new MultivaluedHashMap<>();
+
+ Optional.ofNullable(idempotencyKey)
+ .ifPresent(key -> headers.add("Idempotency-Key", idempotencyKey));
+
+ return client
+ .target(connectorUriGenerator.chargesURI(account))
+ .request()
+ .headers(headers)
+ .accept(MediaType.APPLICATION_JSON)
+ .post(buildChargeRequestPayload(createCardPaymentRequest));
+ }
+
+ private Entity buildChargeRequestPayload(CreateCardPaymentRequest requestPayload) {
+ return json(requestPayload.toConnectorPayload());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreateRefundService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreateRefundService.java
new file mode 100644
index 000000000..3983e91ac
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/CreateRefundService.java
@@ -0,0 +1,83 @@
+package uk.gov.pay.api.service;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.GsonBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.app.config.PublicApiConfig;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CreateRefundException;
+import uk.gov.pay.api.model.CardPayment;
+import uk.gov.pay.api.model.CreatePaymentRefundRequest;
+import uk.gov.pay.api.model.RefundFromConnector;
+import uk.gov.pay.api.model.RefundResponse;
+import uk.gov.pay.api.model.RefundSummary;
+import uk.gov.pay.api.resources.GetOnePaymentStrategy;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.core.Response;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static javax.ws.rs.client.Entity.json;
+import static javax.ws.rs.core.Response.Status.ACCEPTED;
+import static javax.ws.rs.core.UriBuilder.fromPath;
+
+public class CreateRefundService {
+
+ private static final Logger logger = LoggerFactory.getLogger(CreateRefundService.class);
+
+ private final GetPaymentService getPaymentService;
+ private final Client client;
+ private final String connectorUrl;
+ private final String baseUrl;
+
+ @Inject
+ public CreateRefundService(GetPaymentService getPaymentService,
+ Client client,
+ PublicApiConfig configuration) {
+ this.getPaymentService = getPaymentService;
+ this.client = client;
+ this.baseUrl = configuration.getBaseUrl();
+ this.connectorUrl = configuration.getConnectorUrl();
+ }
+
+ public RefundResponse createRefund(Account account, String paymentId, CreatePaymentRefundRequest createPaymentRefundRequest) {
+ var strategy = new GetOnePaymentStrategy("", account, paymentId, getPaymentService);
+
+ Integer refundAmountAvailable = createPaymentRefundRequest.getRefundAmountAvailable()
+ .orElseGet(() -> getRefundAmountAvailableFromPayment(strategy));
+
+ ImmutableMap payloadMap = ImmutableMap.of(
+ "amount", createPaymentRefundRequest.getAmount(),
+ "refund_amount_available", refundAmountAvailable);
+ String connectorPayload = new GsonBuilder().create().toJson(payloadMap);
+
+ Response connectorResponse = client
+ .target(getConnectorUrl(format("/v1/api/accounts/%s/charges/%s/refunds", account.getAccountId(), paymentId)))
+ .request()
+ .post(json(connectorPayload));
+
+ if (connectorResponse.getStatus() != ACCEPTED.getStatusCode()) {
+ throw new CreateRefundException(connectorResponse);
+ }
+
+ RefundFromConnector refundFromConnector = connectorResponse.readEntity(RefundFromConnector.class);
+ logger.debug("created refund returned - [ {} ]", refundFromConnector);
+ return RefundResponse.valueOf(refundFromConnector, paymentId, baseUrl);
+ }
+
+ private int getRefundAmountAvailableFromPayment(GetOnePaymentStrategy strategy) {
+ return Optional.of((CardPayment) strategy.validateAndExecute())
+ .map(p -> p.getRefundSummary()
+ .map(RefundSummary::getAmountAvailable)
+ .orElse(0L))
+ .map(Long::intValue)
+ .orElse(0);
+ }
+
+ private String getConnectorUrl(String urlPath) {
+ return fromPath(connectorUrl).path(urlPath).toString();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/DisputesSearchParams.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/DisputesSearchParams.java
new file mode 100644
index 000000000..03e7c2362
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/DisputesSearchParams.java
@@ -0,0 +1,129 @@
+package uk.gov.pay.api.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class DisputesSearchParams {
+
+ private static final String PAGE = "page";
+ private static final String DISPLAY_SIZE = "display_size";
+ private static final String DEFAULT_PAGE = "1";
+ private static final String DEFAULT_DISPLAY_SIZE = "500";
+ private static final String FROM_DATE = "from_date";
+ private static final String TO_DATE = "to_date";
+ private static final String FROM_SETTLED_DATE = "from_settled_date";
+ private static final String TO_SETTLED_DATE = "to_settled_date";
+ private static final String STATUS = "status";
+
+ private String fromDate;
+ private String toDate;
+ private String page;
+ private String displaySize;
+ private String fromSettledDate;
+ private String toSettledDate;
+ private String state;
+
+ private DisputesSearchParams(Builder builder) {
+ this.fromDate = builder.fromDate;
+ this.toDate = builder.toDate;
+ this.page = builder.page;
+ this.displaySize = builder.displaySize;
+ this.fromSettledDate = builder.fromSettledDate;
+ this.toSettledDate = builder.toSettledDate;
+ this.state = builder.status;
+ }
+
+ public Map getParamsAsMap() {
+ Map params = new LinkedHashMap<>();
+ params.put(FROM_DATE, fromDate);
+ params.put(TO_DATE, toDate);
+ params.put(PAGE, Optional.ofNullable(page).orElse(DEFAULT_PAGE));
+ params.put(DISPLAY_SIZE, Optional.ofNullable(displaySize).orElse(DEFAULT_DISPLAY_SIZE));
+ params.put(FROM_SETTLED_DATE, fromSettledDate);
+ params.put(TO_SETTLED_DATE, toSettledDate);
+ params.put(STATUS, state);
+
+ return params;
+ }
+
+ public String getPage() {
+ return page;
+ }
+
+ public String getDisplaySize() {
+ return displaySize;
+ }
+
+ public String getFromDate() {
+ return fromDate;
+ }
+
+ public String getToDate() {
+ return toDate;
+ }
+
+ public String getFromSettledDate() {
+ return fromSettledDate;
+ }
+
+ public String getToSettledDate() {
+ return toSettledDate;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public static class Builder {
+ private String fromDate;
+ private String toDate;
+ private String page;
+ private String displaySize;
+ private String fromSettledDate;
+ private String toSettledDate;
+ private String status;
+
+ public Builder() {
+ }
+
+ public DisputesSearchParams build() {
+ return new DisputesSearchParams(this);
+ }
+
+ public Builder withFromDate(String fromDate) {
+ this.fromDate = fromDate;
+ return this;
+ }
+
+ public Builder withToDate(String toDate) {
+ this.toDate = toDate;
+ return this;
+ }
+
+ public Builder withPage(String page) {
+ this.page = page;
+ return this;
+ }
+
+ public Builder withDisplaySize(String displaySize) {
+ this.displaySize = displaySize;
+ return this;
+ }
+
+ public Builder withFromSettledDate(String fromSettledDate) {
+ this.fromSettledDate = fromSettledDate;
+ return this;
+ }
+
+ public Builder withToSettledDate(String toSettledDate) {
+ this.toSettledDate = toSettledDate;
+ return this;
+ }
+
+ public Builder withStatus(String status) {
+ this.status = status;
+ return this;
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentEventsService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentEventsService.java
new file mode 100644
index 000000000..65e4ae62f
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentEventsService.java
@@ -0,0 +1,52 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.GetEventsException;
+import uk.gov.pay.api.model.PaymentEvents;
+import uk.gov.pay.api.model.PaymentEventsResponse;
+import uk.gov.pay.api.model.TransactionEvents;
+
+import javax.inject.Inject;
+import java.net.URI;
+
+public class GetPaymentEventsService {
+
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private ConnectorService connectorService;
+ private LedgerService ledgerService;
+
+ @Inject
+ public GetPaymentEventsService(PublicApiUriGenerator publicApiUriGenerator,
+ ConnectorService connectorService,
+ LedgerService ledgerService) {
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.connectorService = connectorService;
+ this.ledgerService = ledgerService;
+ }
+
+ public PaymentEventsResponse getPaymentEventsFromConnector(Account account, String paymentId) {
+ PaymentEvents chargeEvents = connectorService.getChargeEvents(account, paymentId);
+
+ URI paymentEventsLink = publicApiUriGenerator.getPaymentEventsURI(paymentId);
+ URI paymentLink = publicApiUriGenerator.getPaymentURI(paymentId);
+
+ return PaymentEventsResponse.from(chargeEvents, paymentLink, paymentEventsLink);
+ }
+
+ public PaymentEventsResponse getPaymentEventsFromLedger(Account account, String paymentId) {
+ TransactionEvents transactionEvents = ledgerService.getTransactionEvents(account, paymentId);
+
+ URI paymentEventsLink = publicApiUriGenerator.getPaymentEventsURI(paymentId);
+ URI paymentLink = publicApiUriGenerator.getPaymentURI(paymentId);
+
+ return PaymentEventsResponse.from(transactionEvents, paymentLink, paymentEventsLink);
+ }
+
+ public PaymentEventsResponse getPaymentEvents(Account account, String paymentId) {
+ try {
+ return getPaymentEventsFromConnector(account, paymentId);
+ } catch (GetEventsException ex) {
+ return getPaymentEventsFromLedger(account, paymentId);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundService.java
new file mode 100644
index 000000000..f7deda062
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundService.java
@@ -0,0 +1,58 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.GetRefundException;
+import uk.gov.pay.api.exception.GetTransactionException;
+import uk.gov.pay.api.model.RefundFromConnector;
+import uk.gov.pay.api.model.RefundResponse;
+import uk.gov.pay.api.model.ledger.RefundTransactionFromLedger;
+
+import javax.inject.Inject;
+
+public class GetPaymentRefundService {
+
+ private final ConnectorService connectorService;
+ private final LedgerService ledgerService;
+ private PublicApiUriGenerator publicApiUriGenerator;
+
+ @Inject
+ public GetPaymentRefundService(ConnectorService connectorService,
+ LedgerService ledgerService,
+ PublicApiUriGenerator publicApiUriGenerator) {
+ this.connectorService = connectorService;
+ this.ledgerService = ledgerService;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ }
+
+ public RefundResponse getConnectorPaymentRefund(Account account, String paymentId, String refundId) {
+ RefundFromConnector refundFromConnector = connectorService.getPaymentRefund(account.getAccountId(), paymentId, refundId);
+
+ return RefundResponse.from(
+ refundFromConnector,
+ publicApiUriGenerator.getRefundsURI(paymentId, refundId),
+ publicApiUriGenerator.getPaymentURI(paymentId)
+ );
+ }
+
+ public RefundResponse getLedgerPaymentRefund(Account account, String paymentId, String refundId) {
+ try {
+ RefundTransactionFromLedger refund = ledgerService.getRefundTransaction(account, refundId, paymentId);
+
+ return RefundResponse.from(
+ refund,
+ publicApiUriGenerator.getRefundsURI(paymentId, refundId),
+ publicApiUriGenerator.getPaymentURI(paymentId)
+ );
+ } catch (GetTransactionException exception) {
+ throw new GetRefundException(exception);
+ }
+ }
+
+ public RefundResponse getPaymentRefund(Account account, String paymentId, String refundId) {
+ try {
+ return getConnectorPaymentRefund(account, paymentId, refundId);
+ } catch (GetRefundException ex) {
+ return getLedgerPaymentRefund(account, paymentId, refundId);
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundsService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundsService.java
new file mode 100644
index 000000000..74918d862
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentRefundsService.java
@@ -0,0 +1,76 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.RefundResponse;
+import uk.gov.pay.api.model.RefundsFromConnector;
+import uk.gov.pay.api.model.RefundsResponse;
+import uk.gov.pay.api.model.ledger.RefundsFromLedger;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GetPaymentRefundsService {
+
+ private final ConnectorService connectorService;
+ private final LedgerService ledgerService;
+ private PublicApiUriGenerator publicApiUriGenerator;
+
+ @Inject
+ public GetPaymentRefundsService(ConnectorService connectorService,
+ LedgerService ledgerService,
+ PublicApiUriGenerator publicApiUriGenerator) {
+ this.connectorService = connectorService;
+ this.ledgerService = ledgerService;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ }
+
+ public RefundsResponse getConnectorPaymentRefunds(Account account, String paymentId) {
+ RefundsFromConnector refundsFromConnector = connectorService.getPaymentRefunds(account.getAccountId(), paymentId);
+ List refundResponses = processRefunds(paymentId, refundsFromConnector);
+
+ return getRefundsResponse(paymentId, refundResponses);
+ }
+
+ public RefundsResponse getLedgerTransactionTransactions(Account account, String paymentId) {
+ RefundsFromLedger refundsFromLedger = ledgerService.getPaymentRefunds(account.getAccountId(), paymentId);
+ List refundResponses = processRefunds(paymentId, refundsFromLedger);
+
+ return getRefundsResponse(paymentId, refundResponses);
+ }
+
+ private List processRefunds(String paymentId, RefundsFromLedger refundsFromLedger) {
+ return refundsFromLedger.getTransactions()
+ .stream()
+ .map(refundTransactionFromLedger ->
+ RefundResponse.from(refundTransactionFromLedger,
+ publicApiUriGenerator.getRefundsURI(paymentId,
+ refundTransactionFromLedger.getTransactionId()),
+ publicApiUriGenerator.getPaymentURI(paymentId)
+ )
+ )
+ .collect(Collectors.toList());
+ }
+
+ private List processRefunds(String paymentId, RefundsFromConnector refundsFromConnector) {
+ return refundsFromConnector
+ .getEmbedded()
+ .getRefunds()
+ .stream()
+ .map(refundFromConnector ->
+ RefundResponse.from(refundFromConnector,
+ publicApiUriGenerator.getRefundsURI(paymentId,
+ refundFromConnector.getRefundId()),
+ publicApiUriGenerator.getPaymentURI(paymentId)
+ )
+ )
+ .collect(Collectors.toList());
+ }
+
+ private RefundsResponse getRefundsResponse(String paymentId, List refunds) {
+ return RefundsResponse.from(paymentId, refunds,
+ publicApiUriGenerator.getPaymentRefundsURI(paymentId).toString(),
+ publicApiUriGenerator.getPaymentURI(paymentId).toString()
+ );
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentService.java
new file mode 100644
index 000000000..d92df3d55
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/GetPaymentService.java
@@ -0,0 +1,57 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.GetChargeException;
+import uk.gov.pay.api.model.Charge;
+import uk.gov.pay.api.model.links.PaymentWithAllLinks;
+
+import javax.inject.Inject;
+import java.net.URI;
+
+public class GetPaymentService {
+
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private final ConnectorService connectorService;
+ private final LedgerService ledgerService;
+
+ @Inject
+ public GetPaymentService(PublicApiUriGenerator publicApiUriGenerator,
+ ConnectorService connectorService, LedgerService ledgerService) {
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.connectorService = connectorService;
+ this.ledgerService = ledgerService;
+ }
+
+ public PaymentWithAllLinks getConnectorCharge(Account account, String paymentId) {
+ Charge charge = connectorService.getCharge(account, paymentId);
+
+ return getPaymentWithAllLinks(charge);
+ }
+
+ public PaymentWithAllLinks getLedgerTransaction(Account account, String paymentId) {
+ Charge charge = ledgerService.getPaymentTransaction(account, paymentId);
+
+ return getPaymentWithAllLinks(charge);
+ }
+
+ public PaymentWithAllLinks getPayment(Account account, String paymentId) {
+ try {
+ return getConnectorCharge(account, paymentId);
+ } catch (GetChargeException ex) {
+ return getLedgerTransaction(account, paymentId);
+ }
+ }
+
+ private PaymentWithAllLinks getPaymentWithAllLinks(Charge chargeFromResponse) {
+ URI paymentURI = publicApiUriGenerator.getPaymentURI(chargeFromResponse.getChargeId());
+
+ return PaymentWithAllLinks.getPaymentWithLinks(
+ chargeFromResponse,
+ paymentURI,
+ publicApiUriGenerator.getPaymentEventsURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentCancelURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentRefundsURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentCaptureURI(chargeFromResponse.getChargeId()),
+ publicApiUriGenerator.getPaymentAuthorisationURI());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/LedgerService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/LedgerService.java
new file mode 100644
index 000000000..1ae1b0bc5
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/LedgerService.java
@@ -0,0 +1,225 @@
+package uk.gov.pay.api.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.gov.pay.api.agreement.model.AgreementLedgerResponse;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.GetAgreementException;
+import uk.gov.pay.api.exception.GetChargeException;
+import uk.gov.pay.api.exception.GetEventsException;
+import uk.gov.pay.api.exception.GetRefundsException;
+import uk.gov.pay.api.exception.GetTransactionException;
+import uk.gov.pay.api.exception.SearchAgreementsException;
+import uk.gov.pay.api.exception.SearchDisputesException;
+import uk.gov.pay.api.exception.SearchPaymentsException;
+import uk.gov.pay.api.exception.SearchRefundsException;
+import uk.gov.pay.api.ledger.model.AgreementSearchParams;
+import uk.gov.pay.api.ledger.model.SearchResults;
+import uk.gov.pay.api.ledger.service.LedgerUriGenerator;
+import uk.gov.pay.api.model.Charge;
+import uk.gov.pay.api.model.TransactionEvents;
+import uk.gov.pay.api.model.TransactionResponse;
+import uk.gov.pay.api.model.ledger.RefundTransactionFromLedger;
+import uk.gov.pay.api.model.ledger.RefundsFromLedger;
+import uk.gov.pay.api.model.ledger.SearchDisputesResponseFromLedger;
+import uk.gov.pay.api.model.ledger.SearchRefundsResponseFromLedger;
+import uk.gov.pay.api.model.search.card.PaymentSearchResponse;
+import uk.gov.pay.api.validation.AgreementSearchValidator;
+
+import javax.inject.Inject;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.Map;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.apache.http.HttpStatus.SC_OK;
+import static uk.gov.pay.api.common.SearchConstants.GATEWAY_ACCOUNT_ID;
+
+public class LedgerService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LedgerService.class);
+
+ private static final String PARAM_ACCOUNT_ID = "account_id";
+ private static final String PARAM_TRANSACTION_TYPE = "transaction_type";
+ public static final String PARAM_EXACT_REFERENCE_MATCH = "exact_reference_match";
+ private static final String PAYMENT_TRANSACTION_TYPE = "PAYMENT";
+ private static final String REFUND_TRANSACTION_TYPE = "REFUND";
+ private static final String DISPUTE_TRANSACTION_TYPE = "DISPUTE";
+
+ private final Client client;
+ private final LedgerUriGenerator ledgerUriGenerator;
+
+ @Inject
+ public LedgerService(Client client, LedgerUriGenerator ledgerUriGenerator) {
+ this.client = client;
+ this.ledgerUriGenerator = ledgerUriGenerator;
+ }
+
+ public Charge getPaymentTransaction(Account account, String paymentId) {
+ Response response = client
+ .target(ledgerUriGenerator.transactionURI(account, paymentId, PAYMENT_TRANSACTION_TYPE))
+ .request()
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ TransactionResponse transactionResponse = response.readEntity(TransactionResponse.class);
+ return Charge.from(transactionResponse);
+ }
+
+ throw new GetChargeException(response);
+ }
+
+ public RefundTransactionFromLedger getRefundTransaction(Account account, String transactionId, String parentExternalId) {
+ Response response = client
+ .target(ledgerUriGenerator.transactionURI(account, transactionId, REFUND_TRANSACTION_TYPE, parentExternalId))
+ .request()
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ return response.readEntity(RefundTransactionFromLedger.class);
+ }
+
+ throw new GetTransactionException(response);
+ }
+
+ public TransactionEvents getTransactionEvents(Account account, String paymentId) {
+ Response response = client
+ .target(ledgerUriGenerator.transactionEventsURI(account, paymentId))
+ .request()
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ return response.readEntity(TransactionEvents.class);
+ }
+ throw new GetEventsException(response);
+ }
+
+ public RefundsFromLedger getPaymentRefunds(String accountId, String paymentId) {
+ Response response = client
+ .target(ledgerUriGenerator.transactionsForTransactionURI(accountId, paymentId, REFUND_TRANSACTION_TYPE))
+ .request()
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ return response.readEntity(RefundsFromLedger.class);
+ }
+
+ throw new GetRefundsException(response);
+ }
+
+ public SearchRefundsResponseFromLedger searchRefunds(Account account, Map paramsAsMap) {
+
+ paramsAsMap.put(PARAM_ACCOUNT_ID, account.getAccountId());
+ paramsAsMap.put(PARAM_TRANSACTION_TYPE, REFUND_TRANSACTION_TYPE);
+
+ Response response = client
+ .target(ledgerUriGenerator.transactionsURIWithParams(paramsAsMap))
+ .request()
+ .accept(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ try {
+ return response.readEntity(SearchRefundsResponseFromLedger.class);
+ } catch (ProcessingException exception) {
+ throw new SearchRefundsException(exception);
+ }
+ }
+
+ throw new SearchRefundsException(response);
+ }
+
+ public SearchDisputesResponseFromLedger searchDisputes(Account account, Map paramsAsMap) {
+ paramsAsMap.put(PARAM_ACCOUNT_ID, account.getAccountId());
+ paramsAsMap.put(PARAM_TRANSACTION_TYPE, DISPUTE_TRANSACTION_TYPE);
+
+ if (paramsAsMap.containsKey("status")) {
+ paramsAsMap.put("state", paramsAsMap.remove("status"));
+ }
+
+ Response response = client
+ .target(ledgerUriGenerator.transactionsURIWithParams(paramsAsMap))
+ .request()
+ .accept(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ try {
+ return response.readEntity(SearchDisputesResponseFromLedger.class);
+ } catch (ProcessingException exception) {
+ throw new SearchDisputesException(exception);
+ }
+ }
+
+ throw new SearchDisputesException(response);
+ }
+
+ public PaymentSearchResponse searchPayments(Account account, Map paramsAsMap) {
+
+ paramsAsMap.put(PARAM_ACCOUNT_ID, account.getAccountId());
+ paramsAsMap.put(PARAM_TRANSACTION_TYPE, PAYMENT_TRANSACTION_TYPE);
+ paramsAsMap.put(PARAM_EXACT_REFERENCE_MATCH, "true");
+
+ Response response = client
+ .target(ledgerUriGenerator.transactionsURIWithParams(paramsAsMap))
+ .request()
+ .accept(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ try {
+ return response.readEntity(new GenericType<>() {
+ });
+ } catch (ProcessingException ex) {
+ throw new SearchPaymentsException(ex);
+ }
+ }
+
+ throw new SearchPaymentsException(response);
+ }
+
+ public AgreementLedgerResponse getAgreement(Account account, String agreementId) {
+ Response response = client
+ .target(ledgerUriGenerator.agreementURI(account, agreementId))
+ .request()
+ .header("X-Consistent", true)
+ .get();
+
+ if (response.getStatus() == SC_OK) {
+ return response.readEntity(AgreementLedgerResponse.class);
+ }
+
+ throw new GetAgreementException(response);
+ }
+
+ public SearchResults searchAgreements(Account account, AgreementSearchParams searchParams) {
+ AgreementSearchValidator.validateSearchParameters(searchParams);
+
+ var params = new HashMap<>(searchParams.getQueryMap());
+ params.put(GATEWAY_ACCOUNT_ID, account.getAccountId());
+ params.put(PARAM_EXACT_REFERENCE_MATCH, "true");
+
+ String url = ledgerUriGenerator.agreementsURIWithParams(params);
+ Response ledgerResponse = client
+ .target(url)
+ .request()
+ .header(HttpHeaders.ACCEPT, APPLICATION_JSON)
+ .get();
+ LOGGER.info("response from ledger for agreement search: {}", ledgerResponse);
+
+ if (ledgerResponse.getStatus() == SC_OK) {
+ try {
+ return ledgerResponse.readEntity(new GenericType<>() {
+ });
+ } catch (ProcessingException ex) {
+ throw new SearchAgreementsException(ex);
+ }
+ }
+ throw new SearchAgreementsException(ledgerResponse);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchParams.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchParams.java
new file mode 100644
index 000000000..89bd588ff
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchParams.java
@@ -0,0 +1,227 @@
+package uk.gov.pay.api.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+public class PaymentSearchParams {
+
+ public static final String REFERENCE_KEY = "reference";
+ public static final String EMAIL_KEY = "email";
+ public static final String STATE_KEY = "state";
+ public static final String CARD_BRAND_KEY = "card_brand";
+ public static final String FIRST_DIGITS_CARD_NUMBER_KEY = "first_digits_card_number";
+ public static final String LAST_DIGITS_CARD_NUMBER_KEY = "last_digits_card_number";
+ public static final String CARDHOLDER_NAME_KEY = "cardholder_name";
+ public static final String FROM_DATE_KEY = "from_date";
+ public static final String TO_DATE_KEY = "to_date";
+ public static final String PAGE = "page";
+ public static final String DISPLAY_SIZE = "display_size";
+ public static final String FROM_SETTLED_DATE = "from_settled_date";
+ public static final String TO_SETTLED_DATE = "to_settled_date";
+ public static final String AGREEMENT_ID = "agreement_id";
+
+ private String reference;
+ private String email;
+ private String state;
+ private String cardBrand;
+ private String fromDate;
+ private String toDate;
+ private String pageNumber;
+ private String displaySize;
+ private String cardHolderName;
+ private String firstDigitsCardNumber;
+ private String lastDigitsCardNumber;
+ private String fromSettledDate;
+ private String toSettledDate;
+ private String agreementId;
+
+ public PaymentSearchParams(Builder builder) {
+ this.reference = builder.reference;
+ this.email = builder.email;
+ this.state = builder.state;
+ this.cardBrand = builder.cardBrand;
+ this.fromDate = builder.fromDate;
+ this.toDate = builder.toDate;
+ this.pageNumber = builder.pageNumber;
+ this.displaySize = builder.displaySize;
+ this.cardHolderName = builder.cardHolderName;
+ this.firstDigitsCardNumber = builder.firstDigitsCardNumber;
+ this.lastDigitsCardNumber = builder.lastDigitsCardNumber;
+ this.fromSettledDate = builder.fromSettledDate;
+ this.toSettledDate = builder.toSettledDate;
+ this.agreementId = builder.agreementId;
+ }
+
+ public Map getParamsAsMap() {
+ Map params = new LinkedHashMap<>();
+ params.put(REFERENCE_KEY, reference);
+ params.put(EMAIL_KEY, email);
+ params.put(STATE_KEY, state);
+ params.put(CARD_BRAND_KEY, cardBrand);
+ params.put(CARDHOLDER_NAME_KEY, cardHolderName);
+ params.put(FIRST_DIGITS_CARD_NUMBER_KEY, firstDigitsCardNumber);
+ params.put(LAST_DIGITS_CARD_NUMBER_KEY, lastDigitsCardNumber);
+ params.put(FROM_DATE_KEY, fromDate);
+ params.put(TO_DATE_KEY, toDate);
+ params.put(PAGE, pageNumber);
+ params.put(DISPLAY_SIZE, displaySize);
+ params.put(FROM_SETTLED_DATE, fromSettledDate);
+ params.put(TO_SETTLED_DATE, toSettledDate);
+ params.put(AGREEMENT_ID, agreementId);
+
+ return params;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public String getReference() {
+ return reference;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getCardBrand() {
+ return cardBrand;
+ }
+
+ public String getFromDate() {
+ return fromDate;
+ }
+
+ public String getToDate() {
+ return toDate;
+ }
+
+ public String getPageNumber() {
+ return pageNumber;
+ }
+
+ public String getDisplaySize() {
+ return displaySize;
+ }
+
+ public String getFirstDigitsCardNumber() {
+ return firstDigitsCardNumber;
+ }
+
+ public String getLastDigitsCardNumber() {
+ return lastDigitsCardNumber;
+ }
+
+ public String getFromSettledDate() {
+ return fromSettledDate;
+ }
+
+ public String getToSettledDate() {
+ return toSettledDate;
+ }
+
+ public String getAgreementId() {
+ return agreementId;
+ }
+
+ public static class Builder {
+ private String reference;
+ private String email;
+ private String state;
+ private String cardBrand;
+ private String fromDate;
+ private String toDate;
+ private String pageNumber;
+ private String displaySize;
+ private String cardHolderName;
+ private String firstDigitsCardNumber;
+ private String lastDigitsCardNumber;
+ private String fromSettledDate;
+ private String toSettledDate;
+ private String agreementId;
+
+ public Builder() {
+ }
+
+ public PaymentSearchParams build() {
+ return new PaymentSearchParams(this);
+ }
+
+ public Builder withReference(String reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ public Builder withEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public Builder withState(String state) {
+ this.state = state;
+ return this;
+ }
+
+ public Builder withCardBrand(String cardBrand) {
+ this.cardBrand = cardBrand;
+
+ if (isNotBlank(cardBrand)) {
+ this.cardBrand = cardBrand.toLowerCase();
+ }
+
+ return this;
+ }
+
+ public Builder withFromDate(String fromDate) {
+ this.fromDate = fromDate;
+ return this;
+ }
+
+ public Builder withToDate(String toDate) {
+ this.toDate = toDate;
+ return this;
+ }
+
+ public Builder withPageNumber(String pageNumber) {
+ this.pageNumber = pageNumber;
+ return this;
+ }
+
+ public Builder withDisplaySize(String displaySize) {
+ this.displaySize = displaySize;
+ return this;
+ }
+
+ public Builder withCardHolderName(String cardHolderName) {
+ this.cardHolderName = cardHolderName;
+ return this;
+ }
+
+ public Builder withFirstDigitsCardNumber(String firstDigitsCardNumber) {
+ this.firstDigitsCardNumber = firstDigitsCardNumber;
+ return this;
+ }
+
+ public Builder withLastDigitsCardNumber(String lastDigitsCardNumber) {
+ this.lastDigitsCardNumber = lastDigitsCardNumber;
+ return this;
+ }
+
+ public Builder withFromSettledDate(String fromSettledDate) {
+ this.fromSettledDate = fromSettledDate;
+ return this;
+ }
+
+ public Builder withToSettledDate(String toSettledDate) {
+ this.toSettledDate = toSettledDate;
+ return this;
+ }
+
+ public Builder withAgreementId(String agreementId) {
+ this.agreementId = agreementId;
+ return this;
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchService.java
new file mode 100644
index 000000000..d12ba562b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentSearchService.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.service;
+
+import black.door.hate.HalRepresentation;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.TransactionResponse;
+import uk.gov.pay.api.model.search.PaginationDecorator;
+import uk.gov.pay.api.model.search.card.PaymentForSearchResult;
+import uk.gov.pay.api.model.search.card.PaymentSearchResponse;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.http.HttpHeaders.CACHE_CONTROL;
+import static org.apache.http.HttpHeaders.PRAGMA;
+import static uk.gov.pay.api.validation.PaymentSearchValidator.validateSearchParameters;
+
+public class PaymentSearchService {
+
+ private static final String PAYMENTS_PATH = "/v1/payments";
+
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private final PaginationDecorator paginationDecorator;
+ private LedgerService ledgerService;
+
+ @Inject
+ public PaymentSearchService(PublicApiUriGenerator publicApiUriGenerator,
+ PaginationDecorator paginationDecorator,
+ LedgerService ledgerService) {
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.paginationDecorator = paginationDecorator;
+ this.ledgerService = ledgerService;
+ }
+
+ public Response searchLedgerPayments(Account account, PaymentSearchParams searchParams) {
+ validateSearchParameters(searchParams);
+
+ PaymentSearchResponse paymentSearchResponse =
+ ledgerService.searchPayments(account, searchParams.getParamsAsMap());
+ return processLedgerResponse(paymentSearchResponse);
+ }
+
+ private Response processLedgerResponse(PaymentSearchResponse paymentSearchResponse) {
+ List chargeFromResponses = paymentSearchResponse.getPayments()
+ .stream()
+ .map(t -> PaymentForSearchResult.valueOf(
+ t,
+ publicApiUriGenerator.getPaymentURI(t.getTransactionId()),
+ publicApiUriGenerator.getPaymentEventsURI(t.getTransactionId()),
+ publicApiUriGenerator.getPaymentCancelURI(t.getTransactionId()),
+ publicApiUriGenerator.getPaymentRefundsURI(t.getTransactionId()),
+ publicApiUriGenerator.getPaymentCaptureURI(t.getTransactionId())))
+ .collect(toList());
+
+ HalRepresentation.HalRepresentationBuilder halRepresentation = HalRepresentation
+ .builder()
+ .addProperty("results", chargeFromResponses);
+
+ return Response.ok()
+ .header(PRAGMA, "no-cache")
+ .header(CACHE_CONTROL, "no-store")
+ .entity(paginationDecorator
+ .decoratePagination(halRepresentation, paymentSearchResponse, PAYMENTS_PATH)
+ .build()
+ .toString())
+ .build();
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentUriGenerator.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentUriGenerator.java
new file mode 100644
index 000000000..01cf75dca
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PaymentUriGenerator.java
@@ -0,0 +1,37 @@
+package uk.gov.pay.api.service;
+
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+public class PaymentUriGenerator {
+ public URI getPaymentURI(String baseUrl, String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}")
+ .build(chargeId);
+ }
+
+ public URI getPaymentEventsURI(String baseUrl, String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/events")
+ .build(chargeId);
+ }
+
+ public URI getPaymentCancelURI(String baseUrl, String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/cancel")
+ .build(chargeId);
+ }
+
+ public URI getPaymentRefundsURI(String baseUrl, String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/refunds")
+ .build(chargeId);
+ }
+
+ public URI getPaymentCaptureURI(String baseUrl, String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/capture")
+ .build(chargeId);
+ }
+
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PublicApiUriGenerator.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PublicApiUriGenerator.java
new file mode 100644
index 000000000..766aa950b
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/PublicApiUriGenerator.java
@@ -0,0 +1,69 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.app.config.PublicApiConfig;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+public class PublicApiUriGenerator {
+
+ private final String baseUrl;
+
+ @Inject
+ public PublicApiUriGenerator(PublicApiConfig configuration) {
+ this.baseUrl = configuration.getBaseUrl();
+ }
+
+ public URI getPaymentURI(String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}")
+ .build(chargeId);
+ }
+ public URI getRefundsURI(String chargeId, String refundId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/refunds/{refunds}")
+ .build(chargeId, refundId);
+ }
+
+ public URI getPaymentEventsURI(String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/events")
+ .build(chargeId);
+ }
+
+ public URI getPaymentCancelURI(String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/cancel")
+ .build(chargeId);
+ }
+
+ public URI getPaymentRefundsURI(String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/refunds")
+ .build(chargeId);
+ }
+
+ public URI getPaymentCaptureURI(String chargeId) {
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/payments/{paymentId}/capture")
+ .build(chargeId);
+ }
+
+ public URI getPaymentAuthorisationURI(){
+ return UriBuilder.fromUri(baseUrl)
+ .path("/v1/auth")
+ .build();
+ }
+
+ public String convertHostToPublicAPI(String link) {
+ URI originalUri = UriBuilder.fromUri(link).build();
+ URI newUri = UriBuilder.fromUri(baseUrl)
+ .path(originalUri.getPath())
+ .replaceQuery(originalUri.getQuery())
+ .build();
+ return URLDecoder.decode(newUri.toString(), StandardCharsets.UTF_8);
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/RefundsParams.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/RefundsParams.java
new file mode 100644
index 000000000..f39e093a2
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/RefundsParams.java
@@ -0,0 +1,70 @@
+package uk.gov.pay.api.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class RefundsParams {
+
+ private static final String PAGE = "page";
+ private static final String DISPLAY_SIZE = "display_size";
+ private static final String DEFAULT_PAGE = "1";
+ private static final String DEFAULT_DISPLAY_SIZE = "500";
+ private static final String FROM_DATE = "from_date";
+ private static final String TO_DATE = "to_date";
+ private static final String FROM_SETTLED_DATE = "from_settled_date";
+ private static final String TO_SETTLED_DATE = "to_settled_date";
+
+ private String fromDate;
+ private String toDate;
+ private String page;
+ private String displaySize;
+ private String fromSettledDate;
+ private String toSettledDate;
+
+ public RefundsParams(String fromDate, String toDate, String page,
+ String displaySize, String fromSettledDate, String toSettledDate) {
+ this.fromDate = fromDate;
+ this.toDate = toDate;
+ this.page = page;
+ this.displaySize = displaySize;
+ this.fromSettledDate = fromSettledDate;
+ this.toSettledDate = toSettledDate;
+ }
+
+ public Map getParamsAsMap() {
+ Map params = new LinkedHashMap<>();
+ params.put(FROM_DATE, fromDate);
+ params.put(TO_DATE, toDate);
+ params.put(PAGE, Optional.ofNullable(page).orElse(DEFAULT_PAGE));
+ params.put(DISPLAY_SIZE, Optional.ofNullable(displaySize).orElse(DEFAULT_DISPLAY_SIZE));
+ params.put(FROM_SETTLED_DATE, fromSettledDate);
+ params.put(TO_SETTLED_DATE, toSettledDate);
+
+ return params;
+ }
+
+ public String getPage() {
+ return page;
+ }
+
+ public String getDisplaySize() {
+ return displaySize;
+ }
+
+ public String getFromDate() {
+ return fromDate;
+ }
+
+ public String getToDate() {
+ return toDate;
+ }
+
+ public String getFromSettledDate() {
+ return fromSettledDate;
+ }
+
+ public String getToSettledDate() {
+ return toSettledDate;
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchDisputesService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchDisputesService.java
new file mode 100644
index 000000000..7cd896153
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchDisputesService.java
@@ -0,0 +1,65 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.ledger.SearchDisputesResponseFromLedger;
+import uk.gov.pay.api.model.search.PaginationDecorator;
+import uk.gov.pay.api.model.search.dispute.DisputeForSearchResult;
+import uk.gov.pay.api.model.search.dispute.DisputesSearchResults;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SearchDisputesService {
+ private static final String DISPUTES_PATH = "/v1/disputes";
+ private final LedgerService ledgerService;
+ private final PublicApiUriGenerator publicApiUriGenerator;
+ private final PaginationDecorator paginationDecorator;
+
+ @Inject
+ public SearchDisputesService(LedgerService ledgerService,
+ PublicApiUriGenerator publicApiUriGenerator,
+ PaginationDecorator paginationDecorator) {
+
+ this.ledgerService = ledgerService;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.paginationDecorator = paginationDecorator;
+ }
+
+ public DisputesSearchResults searchDisputes(Account account, DisputesSearchParams params) {
+ SearchDisputesResponseFromLedger disputesFromLedger = ledgerService.searchDisputes(account, params.getParamsAsMap());
+ return processLedgerResponse(disputesFromLedger);
+ }
+
+ private DisputesSearchResults processLedgerResponse(SearchDisputesResponseFromLedger searchResponse) {
+ List results = searchResponse.getDisputes()
+ .stream()
+ .map(dispute -> DisputeForSearchResult.valueOf(dispute,
+ publicApiUriGenerator.getPaymentURI(dispute.getParentTransactionId())))
+ .collect(Collectors.toList());
+
+ reWriteSearchLinks(searchResponse);
+
+ return new DisputesSearchResults(searchResponse.getTotal(), searchResponse.getCount(), searchResponse.getPage(),
+ results, paginationDecorator.transformLinksToPublicApiUri(searchResponse.getLinks(), DISPUTES_PATH));
+ }
+
+ private void reWriteSearchLinks(SearchDisputesResponseFromLedger searchResponse) {
+ var links = searchResponse.getLinks();
+ if (links.getSelf() != null) {
+ links.withSelfLink(links.getSelf().getHref().replace("state", "status"));
+ }
+ if (links.getFirstPage() != null) {
+ links.withFirstLink(links.getFirstPage().getHref().replaceAll("state", "status"));
+ }
+ if (links.getLastPage() != null) {
+ links.withLastLink(links.getLastPage().getHref().replaceAll("state", "status"));
+ }
+ if (links.getPrevPage() != null) {
+ links.withPrevLink(links.getPrevPage().getHref().replaceAll("state", "status"));
+ }
+ if (links.getLastPage() != null) {
+ links.withLastLink(links.getLastPage().getHref().replaceAll("state", "status"));
+ }
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchRefundsService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchRefundsService.java
new file mode 100644
index 000000000..d2bd1548c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/SearchRefundsService.java
@@ -0,0 +1,58 @@
+package uk.gov.pay.api.service;
+
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.model.ledger.SearchRefundsResponseFromLedger;
+import uk.gov.pay.api.model.search.PaginationDecorator;
+import uk.gov.pay.api.model.search.card.RefundForSearchRefundsResult;
+import uk.gov.pay.api.model.search.card.SearchRefundsResults;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static uk.gov.pay.api.validation.RefundSearchValidator.validateSearchParameters;
+
+public class SearchRefundsService {
+
+ private static final String REFUNDS_PATH = "/v1/refunds";
+ private final PaginationDecorator paginationDecorator;
+ private LedgerService ledgerService;
+ private PublicApiUriGenerator publicApiUriGenerator;
+
+ @Inject
+ public SearchRefundsService(LedgerService ledgerService,
+ PublicApiUriGenerator publicApiUriGenerator,
+ PaginationDecorator paginationDecorator) {
+
+ this.ledgerService = ledgerService;
+ this.publicApiUriGenerator = publicApiUriGenerator;
+ this.paginationDecorator = paginationDecorator;
+ }
+
+ public SearchRefundsResults searchLedgerRefunds(Account account, RefundsParams params) {
+ validateSearchParameters(params);
+ SearchRefundsResponseFromLedger refunds
+ = ledgerService.searchRefunds(account, params.getParamsAsMap());
+ return processLedgerResponse(refunds);
+ }
+
+ private SearchRefundsResults processLedgerResponse(SearchRefundsResponseFromLedger searchResponse) {
+ List results = searchResponse.getRefunds()
+ .stream()
+ .map(refund -> RefundForSearchRefundsResult.valueOf(
+ refund,
+ publicApiUriGenerator.getPaymentURI(refund.getParentTransactionId()),
+ publicApiUriGenerator.getRefundsURI(refund.getParentTransactionId(),
+ refund.getTransactionId()))
+ )
+ .collect(Collectors.toList());
+
+ return new SearchRefundsResults(
+ searchResponse.getTotal(),
+ searchResponse.getCount(),
+ searchResponse.getPage(),
+ results,
+ paginationDecorator.transformLinksToPublicApiUri(searchResponse.getLinks(), REFUNDS_PATH)
+ );
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/telephone/CreateTelephonePaymentService.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/telephone/CreateTelephonePaymentService.java
new file mode 100644
index 000000000..d621e3bee
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/service/telephone/CreateTelephonePaymentService.java
@@ -0,0 +1,59 @@
+package uk.gov.pay.api.service.telephone;
+
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.http.HttpStatus;
+import uk.gov.pay.api.auth.Account;
+import uk.gov.pay.api.exception.CreateChargeException;
+import uk.gov.pay.api.model.ChargeFromResponse;
+import uk.gov.pay.api.model.telephone.CreateTelephonePaymentRequest;
+import uk.gov.pay.api.model.telephone.TelephonePaymentResponse;
+import uk.gov.pay.api.service.ConnectorUriGenerator;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import static javax.ws.rs.client.Entity.json;
+
+public class CreateTelephonePaymentService {
+
+ private final Client client;
+ private final ConnectorUriGenerator connectorUriGenerator;
+
+ @Inject
+ public CreateTelephonePaymentService(Client client, ConnectorUriGenerator connectorUriGenerator) {
+ this.client = client;
+ this.connectorUriGenerator = connectorUriGenerator;
+ }
+
+ public Pair create(Account account, CreateTelephonePaymentRequest createTelephonePaymentRequest) {
+ Response connectorResponse = createTelephoneCharge(account, createTelephonePaymentRequest);
+
+ if (!createdSuccessfully(connectorResponse)) {
+ throw new CreateChargeException(connectorResponse);
+ }
+
+ ChargeFromResponse chargeFromResponse = connectorResponse.readEntity(ChargeFromResponse.class);
+ TelephonePaymentResponse telephonePaymentResponse = TelephonePaymentResponse.from(chargeFromResponse);
+ return Pair.of(telephonePaymentResponse, connectorResponse.getStatus());
+ }
+
+ private boolean createdSuccessfully(Response connectorResponse) {
+ return connectorResponse.getStatus() == HttpStatus.SC_CREATED || connectorResponse.getStatus() == HttpStatus.SC_OK;
+ }
+
+ private Response createTelephoneCharge(Account account, CreateTelephonePaymentRequest createTelephonePaymentRequest) {
+ return client
+ .target(connectorUriGenerator.telephoneChargesURI(account))
+ .request()
+ .accept(MediaType.APPLICATION_JSON)
+ .post(buildTelephoneChargeRequestPayload(createTelephonePaymentRequest));
+ }
+
+ private Entity buildTelephoneChargeRequestPayload(CreateTelephonePaymentRequest requestPayload) {
+ return json(requestPayload.toConnectorPayload());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/CustomSupportedLanguageDeserializer.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/CustomSupportedLanguageDeserializer.java
new file mode 100644
index 000000000..4f565f0a7
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/CustomSupportedLanguageDeserializer.java
@@ -0,0 +1,24 @@
+package uk.gov.pay.api.utils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import uk.gov.service.payments.commons.model.SupportedLanguage;
+
+import java.io.IOException;
+
+public class CustomSupportedLanguageDeserializer extends StdDeserializer {
+
+ public CustomSupportedLanguageDeserializer() {
+ this(null);
+ }
+
+ public CustomSupportedLanguageDeserializer(Class> vc) {
+ super(vc);
+ }
+
+ @Override
+ public SupportedLanguage deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException {
+ return SupportedLanguage.fromIso639AlphaTwoCode(jsonparser.getText());
+ }
+}
diff --git a/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/JsonStringBuilder.java b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/JsonStringBuilder.java
new file mode 100644
index 000000000..c37c7ad5c
--- /dev/null
+++ b/jdk_11_maven/cs/rest/pay-publicapi/src/main/java/uk/gov/pay/api/utils/JsonStringBuilder.java
@@ -0,0 +1,104 @@
+package uk.gov.pay.api.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class JsonStringBuilder {
+ private static final ObjectWriter WRITER = new ObjectMapper()
+ .writer();
+
+ private String root;
+ private Map map;
+
+ private boolean prettyPrint;
+
+ public JsonStringBuilder() {
+ map = new LinkedHashMap<>();
+ prettyPrint = true;
+ }
+
+ public JsonStringBuilder noPrettyPrint() {
+ prettyPrint = false;
+ return this;
+ }
+
+ public JsonStringBuilder addRoot(String root) {
+ this.root = root;
+ return this;
+ }
+
+ public JsonStringBuilder add(String key, Object value) {
+ if (value != null) {
+ map.put(key, value);
+ }
+ return this;
+ }
+
+ public JsonStringBuilder addToMap(String mapKey, String key, Object value) {
+ Map nestedMap = ensureNestedMap(mapKey);
+ nestedMap.put(key, value);
+ return this;
+ }
+
+ public JsonStringBuilder addToMap(String mapKey) {
+ ensureNestedMap(mapKey);
+ return this;
+ }
+
+ public JsonStringBuilder addToNestedMap(String key, Object value, String... mapKeys) {
+ Map localMap = map;
+ for (String mapKey : mapKeys) {
+ localMap = ensureNestedMap(localMap, mapKey);
+ }
+ localMap.put(key, value);
+ return this;
+ }
+
+ public String build() {
+ ObjectWriter writer = WRITER;
+ if (prettyPrint) {
+ writer = writer.withDefaultPrettyPrinter();
+ }
+
+ if (root != null) {
+ writer = writer
+ .with(SerializationFeature.WRAP_ROOT_VALUE)
+ .withRootName(root);
+ }
+
+ return asJsonString(writer, map);
+ }
+
+ private String asJsonString(ObjectWriter writer, Object object) {
+ try {
+ return writer.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException("Error processing json object to string", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map ensureNestedMap(String mapKey) {
+ Map nestedMap = (Map) map.get(mapKey);
+ if (nestedMap == null) {
+ nestedMap = new LinkedHashMap<>();
+ map.put(mapKey, nestedMap);
+ }
+ return nestedMap;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map ensureNestedMap(Map