diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2afe4ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+target
+build
+.project
+.classpath
+.settings
+.idea
+*.iml
\ No newline at end of file
diff --git a/README.md b/README.md
index bf16fc7..0478e68 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,37 @@
-# paymennt-java
-official paymennt.com api client for Java
+# Paymennt Client SDK
+
+The PaymentClient library facilitates the processing and management of payments for various applications. To leverage the capabilities of this library, clients are required to possess an API key and API secret. These credentials can be generated within the Payment Admin UI, granting access to the comprehensive functionality of the library.
+
+Please ensure that you securely store and manage your API key and secret, as they are essential for establishing a secure and efficient connection to the payment system.
+
+## How to use
+
+- Open your project's configuration file (e.g., pom.xml for Maven). Navigate to the dependencies section. Add the following dependency to your project:
+ ```sh
+
+ com.paymennt
+ paymennt-client
+ 1.0
+
+ ```
+- Connect to the PaymenntClient. Use the apiKey, apiSecret generated from the Paymennt admin. Use TEST, LIVE environment based on your requirements for testing or production.
+ ```sh
+ CheckoutOperations operations = new PaymenntClient("{API_KEY}", "{API_SECRET}", PaymenntClient.PaymenntEnvironment.TEST).checkoutOperations();
+ ```
+- To perform checkout operations, such as creating a checkout or retrieving checkout information, ensure that you provide valid parameters (refer official Paymennt API documentation).
+ ```sh
+ /**
+ * create checkout
+ * webCheckoutRequest is the request body required by Paymennt api to create the checkout.
+ */
+ Checkout checkout = operations.createWebCheckout({webCheckoutRequest});
+
+ /**
+ * get checkout details
+ * use checkoutId retrieved from paymennt API.
+ */
+ Checkout checkout = operations.getCheckout({checkoutId});
+ ```
+
+## Official Paymennt API documentation
+[Link](https://docs.paymennt.com/api#section/Introduction)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6e77838
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+ com.paymennt
+ paymennt-client
+ 1.0
+ paymennt-client
+ Paymennt Client
+
+ 17
+ 3.1.2
+ 2.12.7.1
+ 4.5.13
+ 3.9
+ 2.17.1
+ 7.0.1.Final
+ 2.12.5
+ 1.18.22
+ 20
+ 20
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
+
+ commons-codec
+ commons-codec
+
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ ${log4j.version}
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-joda
+ ${joda-time.version}
+
+
+ org.hibernate.validator
+ hibernate-validator
+ ${hibernate-validator.version}
+
+
+ joda-time
+ joda-time
+ ${joda-time.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ compile
+
+
+
+
diff --git a/src/main/java/com/paymennt/client/PaymenntClient.java b/src/main/java/com/paymennt/client/PaymenntClient.java
new file mode 100644
index 0000000..8708165
--- /dev/null
+++ b/src/main/java/com/paymennt/client/PaymenntClient.java
@@ -0,0 +1,131 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client;
+
+import com.paymennt.client.exception.PaymenntClientException;
+import com.paymennt.client.operations.CheckoutOperations;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHeader;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+/**
+ * A client for interacting with the PointCheckout API.
+ * Manages the connection to the API and provides access to various operations.
+ *
+ * Usage example:
+ * PaymenntClient client = new PaymenntClient("your-api-key", "your-api-secret");
+ * CheckoutOperations checkoutOps = client.checkoutOperations();
+ * Checkout checkout = checkoutOps.getCheckout("checkout-id");
+ *
+ * @author Ankur
+ */
+public class PaymenntClient {
+
+ // Constants for API headers and default prefix
+ private static final String API_KEY_HEADER = "X-Paymennt-Api-Key";
+ private static final String API_SECRET_HEADER = "X-Paymennt-Api-Secret";
+ private static final String DEFAULT_PREFIX = "api/mer/v2.0";
+
+ // HTTP client instance
+ private final HttpClient httpClient;
+
+ // Checkout operations instance
+ private final CheckoutOperations checkoutOperations;
+
+ /*******************************************************************************************************************
+ * CONSTRUCTOR AND CONNECTION MANAGEMENT
+ */
+
+ /**
+ * Constructs a PaymenntClient instance using the provided API key and API secret.
+ * Initializes the HTTP client and sets the base URI for the operations.
+ *
+ * @param apiKey The API key for authentication.
+ * @param apiSecret The API secret for authentication.
+ * @throws PaymenntClientException If API key or API secret is empty.
+ */
+ public PaymenntClient(String apiKey, String apiSecret)
+ throws PaymenntClientException {
+ this(apiKey, apiSecret, PaymenntEnvironment.LIVE);
+ }
+
+
+ /**
+ * Constructs a PaymenntClient instance using the provided API key, API secret, and environment.
+ * Initializes the HTTP client and sets the base URI for the operations based on the environment.
+ *
+ * @param apiKey The API key for authentication.
+ * @param apiSecret The API secret for authentication.
+ * @param environment The environment to connect to (LIVE, TEST, LOCAL).
+ * @throws PaymenntClientException If API key or API secret is empty, or if URI syntax is invalid.
+ */
+ public PaymenntClient(String apiKey, String apiSecret, PaymenntEnvironment environment) throws PaymenntClientException {
+ assertTrue(StringUtils.isNotBlank(apiKey), "apiKey cannot be empty");
+ assertTrue(StringUtils.isNotBlank(apiSecret), "apiSecret cannot be empty");
+
+ // CREATE HTTP CLIENT
+ this.httpClient = HttpClientBuilder.create().setDefaultHeaders(
+ List.of(
+ new BasicHeader(API_KEY_HEADER, apiKey),
+ new BasicHeader(API_SECRET_HEADER, apiSecret)
+ )
+ ).build();
+
+ String scheme = environment.scheme;
+ String host = environment.host;
+ int port = environment.port;
+
+ URIBuilder builder = new URIBuilder().setScheme(scheme).setHost(host).setPort(port)
+ .setPath(DEFAULT_PREFIX);
+
+ URI baseUri = null;
+ try {
+ baseUri = builder.build();
+ } catch (URISyntaxException e) {
+ throw new PaymenntClientException("Invalid URI: %s://%s:%d", scheme, host, port);
+ }
+
+ this.checkoutOperations = new CheckoutOperations(httpClient, baseUri);
+ }
+
+ /**
+ * Get the instance of CheckoutOperations for performing checkout-related operations.
+ *
+ * @return The CheckoutOperations instance.
+ */
+ public CheckoutOperations checkoutOperations() {
+ return this.checkoutOperations;
+ }
+
+ private void assertTrue(boolean condition, String message) throws PaymenntClientException {
+ if (!condition)
+ throw new PaymenntClientException(message);
+ }
+
+ /**
+ * Environment properties
+ */
+ public enum PaymenntEnvironment {
+ LIVE("https","api.paymennt.com", 443),
+ TEST("https","api.test.paymennt.com", 443),
+ LOCAL("http","localhost", 8080);
+
+ private final String scheme;
+ private final String host;
+ private final int port;
+
+ PaymenntEnvironment(String scheme, String host, int port) {
+ this.scheme = scheme;
+ this.host = host;
+ this.port = port;
+ }
+ }
+
+}
diff --git a/src/main/java/com/paymennt/client/exception/PaymenntClientException.java b/src/main/java/com/paymennt/client/exception/PaymenntClientException.java
new file mode 100644
index 0000000..f2781f8
--- /dev/null
+++ b/src/main/java/com/paymennt/client/exception/PaymenntClientException.java
@@ -0,0 +1,79 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.exception;
+
+import lombok.Getter;
+
+import java.io.Serial;
+
+/**
+ * Custom exception class for handling errors in the PaymenntClient.
+ * This exception provides a response code and supports formatted messages.
+ *
+ * @author Ankur
+ */
+public class PaymenntClientException extends Exception {
+ @Serial
+ private static final long serialVersionUID = 326864452189922315L;
+
+ /**
+ * The HTTP response code associated with the exception.
+ */
+ @Getter
+ private final int responseCode;
+
+ /**
+ * Constructs a PaymenntClientException with a given message.
+ *
+ * @param message The exception message.
+ */
+ public PaymenntClientException(String message) {
+ this(-1, message);
+ }
+
+ /**
+ * Constructs a PaymenntClientException with a given response code and message.
+ *
+ * @param responseCode The HTTP response code.
+ * @param message The exception message.
+ */
+ public PaymenntClientException(int responseCode, String message) {
+ super(message);
+ this.responseCode = responseCode;
+ }
+
+ /**
+ * Constructs a PaymenntClientException with a formatted message.
+ *
+ * @param message The exception message format.
+ * @param args Arguments to be formatted into the message.
+ */
+ public PaymenntClientException(String message, Object... args) {
+ this(-1, String.format(message, args));
+ }
+
+ /**
+ * Constructs a PaymenntClientException with a formatted message and cause.
+ *
+ * @param message The exception message format.
+ * @param cause The cause of the exception.
+ * @param args Arguments to be formatted into the message.
+ */
+ public PaymenntClientException(String message, Throwable cause, Object... args) {
+ this(-1, String.format(message, args), cause);
+ }
+
+ /**
+ * Constructs a PaymenntClientException with a given response code, formatted message, and cause.
+ *
+ * @param responseCode The HTTP response code.
+ * @param message The exception message format.
+ * @param cause The cause of the exception.
+ * @param args Arguments to be formatted into the message.
+ */
+ public PaymenntClientException(int responseCode, String message, Throwable cause, Object... args) {
+ super(String.format(message, args), cause);
+ this.responseCode = responseCode;
+ }
+}
diff --git a/src/main/java/com/paymennt/client/model/Checkout.java b/src/main/java/com/paymennt/client/model/Checkout.java
new file mode 100644
index 0000000..c12a978
--- /dev/null
+++ b/src/main/java/com/paymennt/client/model/Checkout.java
@@ -0,0 +1,104 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.model;
+
+import com.paymennt.client.request.AbstractCheckoutRequest;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+
+/**
+ * Represents a checkout in the Paymennt system.
+ * This class encapsulates various details related to a checkout.
+ *
+ * @author Ankur
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class Checkout {
+
+ private String id;
+ private String displayId;
+ private String checkoutKey;
+ private String requestId;
+ private String orderId;
+ private String currency;
+ private BigDecimal amount;
+ private BigDecimal cashAmount;
+ private String branchId;
+ private String branchName;
+ private CheckoutStatus status;
+ private String redirectUrl;
+ private PaymentView usedPaymentMethod;
+ private BigDecimal totalRefunded;
+ private AbstractCheckoutRequest.CheckoutCustomer customer;
+ private AbstractCheckoutRequest.CheckoutAddress billingAddress;
+ private AbstractCheckoutRequest.CheckoutAddress deliveryAddress;
+ private String firebaseDatabase;
+ private String firebaseCollection;
+ private String firebaseDocument;
+ private DateTime timestamp;
+
+ /**
+ * Represents the status of a checkout.
+ */
+ public enum CheckoutStatus {
+ PENDING,
+ AUTHORIZED,
+ PENDING_CONFIRMATION,
+ PAID,
+ FAILED,
+ CANCELLED,
+ EXPIRED,
+ REFUNDED,
+ PARTIALLY_REFUNDED,
+ CHARGEBACK;
+ }
+
+ /**
+ * Represents a payment view or method.
+ */
+ public enum PaymentView {
+ POINTCHECKOUT("PointCheckout", false),
+ CARD("Debit/Credit Cards"),
+ TABBY("Tabby"),
+ CAREEM_PAY("Careem pay"),
+
+ CRYPTO("Crypto"),
+ SOLANA_PAY("Solana pay");
+
+ @Getter
+ @Setter
+ private String label;
+
+ @Getter
+ @Setter
+ private boolean hasDetails;
+
+ PaymentView(String label) {
+ this(label, true);
+ }
+
+ PaymentView(String label, boolean hasDetails) {
+ this.label = label;
+ this.hasDetails = hasDetails;
+ }
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case CARD:
+ return "Card";
+ default:
+ return this.label;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/paymennt/client/operations/AbstractOperations.java b/src/main/java/com/paymennt/client/operations/AbstractOperations.java
new file mode 100644
index 0000000..f126bab
--- /dev/null
+++ b/src/main/java/com/paymennt/client/operations/AbstractOperations.java
@@ -0,0 +1,140 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.operations;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.paymennt.client.exception.PaymenntClientException;
+import com.paymennt.client.response.ApiResponse;
+import com.paymennt.client.utils.JsonUtils;
+import lombok.extern.log4j.Log4j2;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Abstract class providing common operations for making HTTP requests.
+ * This class is designed to be extended for specific operations.
+ *
+ * @author Ankur
+ */
+
+@Log4j2
+public abstract class AbstractOperations {
+
+ private final HttpClient httpClient;
+ private final URI baseUri;
+
+ /**
+ * Constructor to initialize the AbstractOperations class.
+ *
+ * @param httpClient - The HttpClient to be used for making requests.
+ * @param baseUri - The base URI for the operation.
+ */
+ protected AbstractOperations(HttpClient httpClient, URI baseUri) {
+ this.httpClient = httpClient;
+ this.baseUri = baseUri;
+ }
+
+ /**
+ * Perform an HTTP GET request.
+ *
+ * @param path - The path of the URL.
+ * @param queryParameters - Query parameters to be added to the request.
+ * @param toValueType - Class to which the response should be cast.
+ * @param - The generic type of the response.
+ * @return The response object of type T.
+ * @throws PaymenntClientException - Custom PaymenntClient exception.
+ * @throws URISyntaxException - Exception while creating URI.
+ * @throws IOException - In case of a problem or connection abortion.
+ */
+ protected T doGet(
+ String path,
+ Map queryParameters,
+ Class toValueType) throws PaymenntClientException, URISyntaxException, IOException {
+
+ URI uri = this.getURI(path, queryParameters);
+ HttpGet httpGet = new HttpGet(uri);
+ log.info("Performing GET request to: {}", uri);
+ return this.execute(httpGet, toValueType);
+ }
+
+ /**
+ * Perform an HTTP POST request.
+ *
+ * @param path - The path of the URL.
+ * @param queryParameters - Query parameters to be added to the request.
+ * @param postBody - Request body for the post request.
+ * @param toValueType - Class to which the response should be cast.
+ * @param - The generic type of the response.
+ * @return The response object of type T.
+ * @throws PaymenntClientException - Custom PaymenntClient exception.
+ * @throws URISyntaxException - Exception while creating URI.
+ * @throws IOException - In case of a problem or connection abortion.
+ */
+ protected T doPost(
+ String path,
+ Map queryParameters,
+ Object postBody,
+ Class toValueType) throws PaymenntClientException, URISyntaxException, IOException {
+
+ if (postBody != null && !JsonUtils.canEncodeDecode(postBody))
+ throw new PaymenntClientException("Unable to serialize post body of type '%s'", postBody.getClass().getName());
+
+ URI uri = this.getURI(path, queryParameters);
+ HttpPost httpPost = new HttpPost(uri);
+ log.info("Performing POST request to: {}", uri);
+ if (postBody != null) {
+ HttpEntity entity = new StringEntity(JsonUtils.encode(postBody), ContentType.APPLICATION_JSON);
+ httpPost.setEntity(entity);
+ }
+ return this.execute(httpPost, toValueType);
+ }
+
+ /*******************************************************************************************************************
+ * PRIVATE METHOD CALLS
+ */
+ private URI getURI(String path, Map parameters) throws URISyntaxException {
+ URIBuilder uriBuilder = new URIBuilder(this.baseUri).setPath(this.baseUri.getPath() + "/" + path);
+ if (parameters != null) {
+ for (Map.Entry entry : parameters.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue() != null ? entry.getValue().toString() : null;
+ uriBuilder.setParameter(key, value);
+ }
+ }
+ return uriBuilder.build();
+ }
+
+ private T execute(HttpUriRequest request, Class toValueType)
+ throws IOException,
+ PaymenntClientException {
+ HttpResponse response = this.httpClient.execute(request);
+ HttpEntity entity = response.getEntity();
+ String result = EntityUtils.toString(entity);
+ TypeFactory typeFactory = JsonUtils.getObjectMapper().getTypeFactory();
+ JavaType javaType = typeFactory.constructParametricType(ApiResponse.class, toValueType);
+ ApiResponse apiResponse = JsonUtils.decode(result, javaType);
+ if (!Objects.isNull(apiResponse.getError())) {
+ log.error("Request failed with error: {}", apiResponse.getError());
+ throw new PaymenntClientException(apiResponse.getError());
+ }
+
+ log.info("Request successful.");
+ return apiResponse.getResult();
+ }
+}
diff --git a/src/main/java/com/paymennt/client/operations/CheckoutOperations.java b/src/main/java/com/paymennt/client/operations/CheckoutOperations.java
new file mode 100644
index 0000000..5cbc4f3
--- /dev/null
+++ b/src/main/java/com/paymennt/client/operations/CheckoutOperations.java
@@ -0,0 +1,67 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+
+package com.paymennt.client.operations;
+
+import com.paymennt.client.exception.PaymenntClientException;
+import com.paymennt.client.model.Checkout;
+import com.paymennt.client.request.WebCheckoutRequest;
+import lombok.extern.log4j.Log4j2;
+import org.apache.http.client.HttpClient;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Class providing operations related to creating and retrieving checkouts.
+ * This class extends AbstractOperations and utilizes HTTP requests.
+ *
+ * @author Ankur
+ */
+
+@Log4j2
+public class CheckoutOperations extends AbstractOperations {
+
+ /**
+ * Constructor to initialize the CheckoutOperations class.
+ *
+ * @param httpClient The HttpClient for checkout operations.
+ * @param baseUri The base URI for checkout operations.
+ */
+ public CheckoutOperations(HttpClient httpClient, URI baseUri) {
+ super(httpClient, baseUri);
+ }
+
+ /**
+ * Create a web checkout using the provided WebCheckoutRequest.
+ *
+ * @param webCheckoutRequest Contains checkout details required for creation.
+ * @return The created Checkout object.
+ * @throws PaymenntClientException Custom PaymenntClient exception.
+ * @throws URISyntaxException Exception while creating URI.
+ * @throws IOException In case of a problem or connection abortion.
+ */
+ public Checkout createWebCheckout(WebCheckoutRequest webCheckoutRequest) throws PaymenntClientException, URISyntaxException, IOException {
+ webCheckoutRequest.validate();
+ String path = "checkout/web";
+ log.info("Creating web checkout with request: {}", webCheckoutRequest);
+ return this.doPost(path, null, webCheckoutRequest, Checkout.class);
+ }
+
+ /**
+ * Retrieve a checkout using the provided checkout ID.
+ *
+ * @param checkoutId The Paymennt checkout reference ID.
+ * @return The retrieved Checkout object.
+ * @throws PaymenntClientException Custom PaymenntClient exception.
+ * @throws URISyntaxException Exception while creating URI.
+ * @throws IOException In case of a problem or connection abortion.
+ */
+ public Checkout getCheckout(String checkoutId) throws PaymenntClientException, URISyntaxException, IOException {
+ String path = "checkout/" + checkoutId;
+ log.info("Retrieving checkout with ID: {}", checkoutId);
+ return this.doGet(path, null, Checkout.class);
+ }
+}
diff --git a/src/main/java/com/paymennt/client/request/AbstractCheckoutRequest.java b/src/main/java/com/paymennt/client/request/AbstractCheckoutRequest.java
new file mode 100644
index 0000000..2846a08
--- /dev/null
+++ b/src/main/java/com/paymennt/client/request/AbstractCheckoutRequest.java
@@ -0,0 +1,221 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.request;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.validator.constraints.Length;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents an abstract checkout request.
+ * This class holds various properties and nested classes related to checkout requests.
+ * It is meant to be extended for specific types of checkout requests.
+ *
+ * @author Ankur
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonPropertyOrder({
+ "requestId", "orderId", "currency", "amount", "totals", "branchId", "allowedPaymentMethods",
+ "defaultPaymentMethod", "language"
+})
+public class AbstractCheckoutRequest {
+
+ @NotBlank
+ @Length(min = 1, max = 50)
+ private String requestId;
+
+ @NotBlank
+ @Length(min = 1, max = 50)
+ private String orderId;
+
+ @NotBlank
+ @Length(min = 3, max = 3)
+ private String currency;
+
+ @NotNull
+ @DecimalMin(value = "0", inclusive = false)
+ private BigDecimal amount;
+
+ @Size(min = 2, max = 3)
+ private String language;
+
+ private Long branchId;
+
+ private List allowedPaymentMethods;
+
+ private PaymentMethod defaultPaymentMethod;
+
+ @Valid
+ private CheckoutRequestTotals totals;
+
+ @JsonIgnore
+ private Map otherFields = new HashMap();
+
+ @NotNull
+ @Valid
+ private CheckoutCustomer customer;
+
+ @NotNull
+ @Valid
+ private CheckoutAddress billingAddress;
+
+ @Valid
+ private CheckoutAddress deliveryAddress;
+
+ @Valid
+ @NotEmpty
+ private List items;
+
+ /**
+ * Represents a checkout address.
+ */
+ @Getter
+ @Setter
+ public static class CheckoutAddress implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @NotBlank
+ @Length(max = 100)
+ private String name;
+
+ @NotBlank
+ @Length(max = 255)
+ private String address1;
+
+ @Length(max = 255)
+ private String address2;
+
+ @NotBlank
+ @Length(max = 50)
+ private String city;
+
+ @Length(max = 50)
+ private String state;
+
+ @Length(max = 20)
+ private String zip;
+
+ @NotBlank
+ @Length(min = 2, max = 3)
+ private String country;
+ }
+
+ /**
+ * Represents a checkout customer.
+ */
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ public static class CheckoutCustomer implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Length(max = 50)
+ private String id;
+
+ @NotBlank
+ @Length(max = 60)
+ private String firstName;
+
+ @NotBlank
+ @Length(max = 40)
+ private String lastName;
+
+ @NotBlank
+ @Length(max = 50)
+ private String email;
+
+ @Length(max = 20)
+ private String phone;
+ }
+
+ /**
+ * Represents a checkout item.
+ */
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CheckoutItem {
+
+ Long id;
+
+ @NotBlank
+ @Length(max = 500)
+ private String name;
+
+ @Length(max = 200)
+ private String sku;
+
+ private BigDecimal unitprice;
+
+ @DecimalMin(value = "0", inclusive = false)
+ @NotNull
+ private BigDecimal quantity;
+
+ @NotNull
+ private BigDecimal linetotal;
+ }
+
+ /**
+ * Represents a payment method.
+ */
+ public enum PaymentMethod {
+ CARD,
+ CRYPTO,
+ POINTCHECKOUT,
+ VISA,
+ MASTERCARD,
+ AMEX,
+ UNIONPAY,
+ TABBY,
+ CAREEM_PAY,
+ MADA;
+ }
+
+ /**
+ * Represents checkout request totals.
+ */
+ @Getter
+ @Setter
+ public static class CheckoutRequestTotals {
+
+ @DecimalMin(value = "0", inclusive = true)
+ @NotNull
+ private BigDecimal subtotal;
+
+ @DecimalMin(value = "0")
+ @NotNull
+ private BigDecimal tax;
+
+ @DecimalMin(value = "0")
+ private BigDecimal shipping;
+
+ @DecimalMin(value = "0")
+ private BigDecimal handling;
+
+ @DecimalMin(value = "0")
+ private BigDecimal discount;
+
+ private boolean skipTotalsValidation = true;
+ }
+}
+
diff --git a/src/main/java/com/paymennt/client/request/WebCheckoutRequest.java b/src/main/java/com/paymennt/client/request/WebCheckoutRequest.java
new file mode 100644
index 0000000..091bac4
--- /dev/null
+++ b/src/main/java/com/paymennt/client/request/WebCheckoutRequest.java
@@ -0,0 +1,64 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.request;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.paymennt.client.exception.PaymenntClientException;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+import java.util.Set;
+
+/**
+ * Represents a web checkout request, extending AbstractCheckoutRequest.
+ * This class holds specific properties for creating a web checkout.
+ * It provides validation for mandatory parameters required to create the web checkout.
+ *
+ * @author Ankur
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonPropertyOrder({
+ "requestId", "orderId", "currency", "amount", "totals", "items", "customer",
+ "billingAddress", "deliveryAddress", "returnUrl", "branchId", "allowedPaymentMethods",
+ "defaultPaymentMethod", "language"
+})
+@Log4j2
+public class WebCheckoutRequest extends AbstractCheckoutRequest {
+
+ @NotBlank
+ private String returnUrl;
+
+ /**
+ * Validates the mandatory parameters required to create the web checkout.
+ *
+ * @throws PaymenntClientException If parameter validation fails.
+ */
+ public void validate() throws PaymenntClientException {
+ Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+ Set> violations = validator.validate(this);
+
+ StringBuilder errorMessage = new StringBuilder();
+ if (!violations.isEmpty()) {
+ errorMessage.append("Parameter validation failed:\n");
+ for (ConstraintViolation violation : violations) {
+ String fieldName = violation.getPropertyPath().toString();
+ String message = violation.getMessage();
+ errorMessage.append("- ").append(fieldName).append(": ").append(message).append("\n");
+ }
+ log.error("Validation error: {} for requestId: {}, orderId: {}", errorMessage, getRequestId(), getOrderId());
+ throw new PaymenntClientException("Validation error: " + errorMessage);
+ }
+ log.info("Web checkout request is valid. RequestId: {}, orderId: {}", getRequestId(), getOrderId());
+ }
+}
diff --git a/src/main/java/com/paymennt/client/response/ApiResponse.java b/src/main/java/com/paymennt/client/response/ApiResponse.java
new file mode 100644
index 0000000..85eeb33
--- /dev/null
+++ b/src/main/java/com/paymennt/client/response/ApiResponse.java
@@ -0,0 +1,40 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+package com.paymennt.client.response;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Represents a generic API response.
+ * This class encapsulates the response structure returned from API calls.
+ * It includes properties for success status, elapsed time, error message, and result.
+ * The class is generic, allowing you to specify the type of the result.
+ *
+ * @param The type of the result in the API response.
+ * @author Ankur
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ApiResponse {
+
+ @JsonProperty("success")
+ private boolean success;
+
+ @JsonProperty("elapsed")
+ private long elapsed;
+
+ @JsonProperty("error")
+ private String error;
+
+ @JsonProperty("result")
+ private T result;
+}
diff --git a/src/main/java/com/paymennt/client/utils/JsonUtils.java b/src/main/java/com/paymennt/client/utils/JsonUtils.java
new file mode 100644
index 0000000..72a65af
--- /dev/null
+++ b/src/main/java/com/paymennt/client/utils/JsonUtils.java
@@ -0,0 +1,243 @@
+/************************************************************************
+ * Copyright PointCheckout Ltd.
+ */
+
+package com.paymennt.client.utils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
+import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.paymennt.client.exception.PaymenntClientException;
+import lombok.extern.log4j.Log4j2;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utility class for JSON-related operations using Jackson ObjectMapper.
+ * This class provides methods for encoding, decoding, and validating JSON objects.
+ * It also configures the default ObjectMapper with specific features.
+ *
+ * @author Ankur
+ */
+
+@Log4j2
+public class JsonUtils {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final Map, Boolean> ENCODE_DECODE_MAP = new ConcurrentHashMap, Boolean>();
+
+ static {
+ ObjectMapper mapper = JsonUtils.OBJECT_MAPPER;
+ mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+ mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
+ mapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS, true);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
+ mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.registerModule(new JodaModule());
+ SimpleFilterProvider filters = new SimpleFilterProvider();
+ filters.setDefaultFilter(SimpleBeanPropertyFilter.serializeAllExcept(new String[] {}));
+ filters.setFailOnUnknownId(false);
+ mapper.setFilterProvider(filters);
+ log.debug("JsonUtils initialized");
+ }
+
+ private JsonUtils() {
+ }
+
+ /**
+ * Returns the configured ObjectMapper instance.
+ *
+ * @return The ObjectMapper instance.
+ */
+ public static ObjectMapper getObjectMapper() {
+ return JsonUtils.OBJECT_MAPPER;
+ }
+
+ /**
+ * Checks if an object can be encoded and decoded as JSON.
+ *
+ * @param object The object to check.
+ * @return True if encoding and decoding is possible, otherwise false.
+ */
+ public static boolean canEncodeDecode(Object object) {
+ Class> objectClass = object.getClass();
+ log.debug("Checking if object can be encoded and decoded: {}", objectClass.getSimpleName());
+
+ if (JsonUtils.ENCODE_DECODE_MAP.containsKey(objectClass))
+ return JsonUtils.ENCODE_DECODE_MAP.get(objectClass);
+ JavaType objectType = TypeFactory.defaultInstance().constructType(objectClass);
+ boolean canEncodeDecode =
+ JsonUtils.OBJECT_MAPPER.canSerialize(objectClass) && JsonUtils.OBJECT_MAPPER.canDeserialize(objectType);
+ JsonUtils.ENCODE_DECODE_MAP.put(objectClass, canEncodeDecode);
+ return canEncodeDecode;
+ }
+
+ /**
+ * Encodes an object to JSON string.
+ *
+ * @param object The object to encode.
+ * @return The JSON string representation of the object.
+ * @throws PaymenntClientException If encoding fails.
+ */
+ public static String encode(Object object) throws PaymenntClientException {
+ try {
+ log.debug("Encoding object to JSON: {}", object.getClass().getSimpleName());
+ return JsonUtils.OBJECT_MAPPER.writeValueAsString(object);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to encode object to JSON: " + object.getClass().getSimpleName(), e);
+ }
+ }
+
+ /**
+ * Encodes an object to a pretty-printed JSON string.
+ *
+ * @param object The object to encode.
+ * @return The pretty-printed JSON string representation of the object.
+ * @throws PaymenntClientException If encoding fails.
+ */
+ public static String encodePretty(Object object) throws PaymenntClientException {
+ try {
+ log.debug("Encoding object to pretty-printed JSON: {}", object.getClass().getSimpleName());
+ JsonUtils.OBJECT_MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
+ return JsonUtils.OBJECT_MAPPER.writeValueAsString(object);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to encode object to pretty-printed JSON: " + object.getClass().getSimpleName(), e);
+ } finally {
+ JsonUtils.OBJECT_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
+ }
+ }
+
+ /**
+ * Decodes an InputStream to a map of key-value pairs.
+ *
+ * @param inputStream The InputStream to decode.
+ * @return The decoded map.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static Map decode(InputStream inputStream) throws PaymenntClientException {
+ try {
+ log.debug("Decoding input stream to map");
+ TypeReference> typeReference = new TypeReference>() {};
+ return JsonUtils.OBJECT_MAPPER.readValue(inputStream, typeReference);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to decode input stream to map", e);
+ }
+ }
+
+ /**
+ * Decodes an InputStream to an object of a specified class.
+ *
+ * @param inputStream The InputStream to decode.
+ * @param toValueType The class to decode to.
+ * @param The type of the result.
+ * @return The decoded object.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static T decode(InputStream inputStream, Class toValueType) throws PaymenntClientException {
+ try {
+ log.debug("Converting object to type: {}", toValueType.getSimpleName());
+ return JsonUtils.OBJECT_MAPPER.readValue(inputStream, toValueType);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to convert object to type " + toValueType.getSimpleName(), e);
+ }
+ }
+
+ /**
+ * Decodes a JSON string to a map of key-value pairs.
+ *
+ * @param jsonString The JSON string to decode.
+ * @return The decoded map.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static Map decode(String jsonString) throws PaymenntClientException {
+ try {
+ log.debug("Decoding JSON string to a map of key-value pairs");
+ TypeReference> typeReference = new TypeReference>() {};
+ return JsonUtils.OBJECT_MAPPER.readValue(jsonString, typeReference);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to decode JSON string to a map of key-value pairs", e);
+ }
+ }
+
+ /**
+ * Decodes a JSON string to an object of a specified class.
+ *
+ * @param jsonString The JSON string to decode.
+ * @param toValueType The class to decode to.
+ * @param The type of the result.
+ * @return The decoded object.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static T decode(String jsonString, Class toValueType) throws PaymenntClientException {
+ try {
+ log.debug("Decoding JSON string to an object of class {}", toValueType.getSimpleName());
+ return JsonUtils.OBJECT_MAPPER.readValue(jsonString, toValueType);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to decode JSON string to an object of class " + toValueType.getSimpleName(), e);
+ }
+ }
+
+ /**
+ * Decodes a JSON string to an object of a specified JavaType.
+ *
+ * @param jsonString The JSON string to decode.
+ * @param javaType The JavaType to decode to.
+ * @param The type of the result.
+ * @return The decoded object.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static T decode(String jsonString, JavaType javaType) throws PaymenntClientException {
+ try {
+ log.debug("Decoding JSON string to an object of type {}", javaType);
+ return JsonUtils.OBJECT_MAPPER.readValue(jsonString, javaType);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to decode JSON string to an object of type "+ javaType, e);
+ }
+ }
+
+ /**
+ * Decodes a JSON string to an object using a TypeReference.
+ *
+ * @param jsonString The JSON string to decode.
+ * @param typeReference The TypeReference describing the target type.
+ * @param The type of the result.
+ * @return The decoded object.
+ * @throws PaymenntClientException If decoding fails.
+ */
+ public static T decode(String jsonString, TypeReference typeReference) throws PaymenntClientException {
+ try {
+ log.debug("Decoding JSON string to an object of type {}", typeReference.getType());
+ return JsonUtils.OBJECT_MAPPER.readValue(jsonString, typeReference);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to decode JSON string to an object of type "+ typeReference.getType(), e);
+ }
+ }
+
+ /**
+ * Decodes an object to another class using ObjectMapper's conversion.
+ *
+ * @param object The object to convert.
+ * @param toValueType The class to convert to.
+ * @param The type of the result.
+ * @return The converted object.
+ * @throws PaymenntClientException If conversion fails.
+ */
+ public static T decode(Object object, Class toValueType) throws PaymenntClientException {
+ try {
+ log.debug("Converting object to an instance of class {}", toValueType.getSimpleName());
+ return JsonUtils.OBJECT_MAPPER.convertValue(object, toValueType);
+ } catch (Exception e) {
+ throw new PaymenntClientException("Failed to converting object to an instance of class " + toValueType.getSimpleName(), e);
+ }
+ }
+
+}
\ No newline at end of file