diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d714d..7e895b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ All notable changes to this project will be documented in this file. :warning: **BREAKING:** Changed SIPS platform URLs ### Changed -- Bumped interface version to 21R1 (IR_WS_2.35) +- Bumped interface version to 21R1 (IR_WS_2.46) ### Upgraded - Update fasterxml/jackson to 2.12.1 diff --git a/README.md b/README.md index 59a6d9f..3782573 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,16 @@ This package provides a JAVA implementation for SIPS, a Worldline e-payments gat > :warning: This library was written for SIPS 2.0 and is not compatible with SIPS 1.0 ## Before you begin -This package contains a basic connector for SIPS, based on the **SIPS Paypage JSON API**. +This package contains a basic connector for SIPS, based on the **SIPS Paypage JSON API** and a very basic but extendable connector to the **SIPS Office JSON API**. Please refer to the [documentation](https://documentation.sips.worldline.com) for a better understanding. If you find field or functionality is missing, feel free to submit a PR or create an issue. -## Installing +## Payage API -### using Gradle +### Installing + +#### using Gradle ```groovy dependencies { @@ -20,7 +22,7 @@ dependencies { ``` -### using Maven +#### using Maven ```xml com.worldline.sips @@ -29,14 +31,14 @@ dependencies { ``` -## Usage +### Usage > :bulb: Currently this library only supports SIPS in pay page mode. -### Initialization +#### Initialization First, create a client for the desired environment using your merchant ID, key version & secret key: ```java -PaypageClient paypageClient = new PaypageClient( - Environment.TEST, +SipsClient paypageClient = new SipsClient( + PaymentEnvironment.TEST, "002001000000002", 1, // This shouldn't be hardcoded here... "002001000000002_KEY1"); // ...and neither should this. @@ -59,14 +61,14 @@ paymentRequest.setTransactionReference("My awesome transaction reference"); And initialize your session on the server: ```java -InitalizationResponse initializationResponse = paypageClient.initialize(paymentRequest); +InitalizationResponse initializationResponse = paypageClient.send(paymentRequest); ``` The `initializationResponse` you'll receive from the server contains all information needed to continue handling your transaction. If your initialization was successful, your response will contain a `RedirectionStatusCode.TRANSACTION_INITIALIZED`. -### Making the payment +#### Making the payment In case your initialization was successful, you have to use the `redirectionUrl` received to perform a POST request with both the `redirectionData` and `seal` as parameters. Since this should redirect the customer the SIPS payment page, the cleanest example is a simple HTML form: @@ -79,16 +81,53 @@ payment page, the cleanest example is a simple HTML form: ``` -### Verifying the payment +#### Verifying the payment When your customer is done, he will be able to return to your application. This is done via a form, making a POST request to the `normalReturnUrl` provided during the initialization of your payment. This POST request contains details on the payment. You can simply decode these responses, providing a `Map` of the parameters included in the received request to your `PaypageClient`: ```java -PaypageResponse paypageResponse = paypageClient.decodeResponse(mappedRequestParameters); +PaypageResponse paypageResponse = paypageClient.decodeResponse(PaypageResponse.class, mappedRequestParameters); +``` + +Alternatively if you don't have a client object when you need to verify the response, you can use the static method +of the SipsClient class (in this case you will have to provide your secret key): + +```java +PaypageResponse paypageResponse = SipsClient.decodeResponse(PaypageResponse.class, mappedRequestParameters, sipsSecretKey) ``` > :warning: Since the customer is not always redirecting back (e.g. he closes the confirmation page), it's a good practice to include an `automaticResponseUrl`. SIPS will always POST details on the transaction on this URL, even if a customer doesn't redirect back to your application. + +## Office API + +### Installing + +#### using Gradle +```groovy + +dependencies { + implementation 'com.worldline.sips:office-sdk:1.4.3' +} + +``` + +#### using Maven +```xml + + com.worldline.sips + office-sdk + 1.4.3 + +``` +### Usage + +The usage for sips office is the same as the payment API : create a SipsClient and send requests. + +Example with the getWalletData call : +````java +GetWalletDataResponse response = sipsClient.send(new GetWalletDataRequest(merchantWalletId)); +```` diff --git a/build.gradle b/build.gradle index d047c1d..fac5306 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ allprojects { sourceCompatibility = 1.8 group 'com.worldline.sips' - version '1.4.4-SNAPSHOT' + version '1.4.5-SNAPSHOT' task sourcesJar(type: Jar, dependsOn: classes) { archiveClassifier.set("sources") @@ -59,18 +59,21 @@ allprojects { } repositories { maven { - url "https://oss.sonatype.org/service/local/staging/deploy/maven2" + def releasesRepoUrl = "https://nexus.kazan.myworldline.com/repository/rcs-store-releases" + def snapshotsRepoUrl = "https://nexus.kazan.myworldline.com/repository/rcs-store-snapshots" + + url = project.hasProperty('release') ? releasesRepoUrl : snapshotsRepoUrl credentials { - username sonatypeUsername - password sonatypePassword + username= findProperty('nexusRepoUser') + password= findProperty('nexusRepoPass') } } } } - - signing { - sign publishing.publications.maven - } +// +// signing { +// sign publishing.publications.maven +// } javadoc { if (JavaVersion.current().isJava9Compatible()) { diff --git a/office-sdk/build.gradle b/office-sdk/build.gradle new file mode 100644 index 0000000..7491a6b --- /dev/null +++ b/office-sdk/build.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation project(':sdk-common') + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' + implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.2' +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/WalletRequest.java b/office-sdk/src/main/java/com/worldline/sips/api/WalletRequest.java new file mode 100644 index 0000000..24d82bb --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/WalletRequest.java @@ -0,0 +1,18 @@ +package com.worldline.sips.api; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.worldline.sips.SIPSRequest; +import com.worldline.sips.api.configuration.OfficeConfiguration; + +public abstract class WalletRequest extends SIPSRequest { + private static final String INTERFACE_VERSION = OfficeConfiguration.INTERFACE_VERSION; + + public WalletRequest(String endpoint) { + super("wallet/" + endpoint); + } + + @JsonInclude + public String getInterfaceVersion() { + return INTERFACE_VERSION; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/WalletResponse.java b/office-sdk/src/main/java/com/worldline/sips/api/WalletResponse.java new file mode 100644 index 0000000..b3254eb --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/WalletResponse.java @@ -0,0 +1,25 @@ +package com.worldline.sips.api; + +import com.worldline.sips.SIPSResponse; +import com.worldline.sips.api.model.data.NamedWalletResponseCode; +import com.worldline.sips.api.model.data.WalletResponseCode; + + +public abstract class WalletResponse extends SIPSResponse { + + private String errorFieldName; + private WalletResponseCode walletResponseCode; + + /** + * Available if walletResponseCode is {@link NamedWalletResponseCode#FORMAT_ERROR} or + * {@link NamedWalletResponseCode#INVALID_DATA} + * @return the error + */ + public String getErrorFieldName() { + return errorFieldName; + } + + public WalletResponseCode getWalletResponseCode() { + return walletResponseCode; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeConfiguration.java b/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeConfiguration.java new file mode 100644 index 0000000..7073c96 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeConfiguration.java @@ -0,0 +1,15 @@ +package com.worldline.sips.api.configuration; + +/** + * Container for global configuration values + */ +public class OfficeConfiguration { + /** + * The targeted version of the API + */ + public static final String INTERFACE_VERSION = "WR_WS_2.42"; + + private OfficeConfiguration() { + // Nothing to see here + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeEnvironment.java b/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeEnvironment.java new file mode 100644 index 0000000..935de0c --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/configuration/OfficeEnvironment.java @@ -0,0 +1,23 @@ +package com.worldline.sips.api.configuration; + +import com.worldline.sips.model.SipsEnvironment; + +import java.net.URI; + +public enum OfficeEnvironment implements SipsEnvironment { + TEST("https://office-server.test.sips-services.com"), + PROD("https://office-server.sips-services.com"); + + private final String url; + private final URI uri; + + OfficeEnvironment(String url) { + this.url = url + "/rs-services/v2"; + this.uri = URI.create(url); + } + + public String getUrl() { + return url; + } + +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/data/NamedWalletResponseCode.java b/office-sdk/src/main/java/com/worldline/sips/api/model/data/NamedWalletResponseCode.java new file mode 100644 index 0000000..68942f8 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/data/NamedWalletResponseCode.java @@ -0,0 +1,55 @@ +package com.worldline.sips.api.model.data; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NamedWalletResponseCode implements WalletResponseCode { + /** + * Successful operation + */ + SUCCESS("00"), + /** + * Invalid Merchant contract + */ + INVALID_MERCHANT("03"), + /** + * Invalid data, verify the request + */ + INVALID_DATA("12"), + /** + * Wallet / payment mean unknown by WL Sips + */ + UNKNOWN_WALLET("25"), + FORMAT_ERROR("30"), + + FRAUD_SUSPECTED("34"), + /** + * MerchantId not allowed to access this wallet service + */ + NOT_ALLOWED("40"), + /** + * Duplicated wallet / payment mean + */ + ALREADY_PRESENT("94"), + /** + * Temporary problem at the WL Sips server level + */ + INTERNAL_TEMPORARY_PROBLEM("99"); + + private final String code; + + NamedWalletResponseCode(String code) { + this.code = code; + } + + @Override + @JsonValue + public String getCode() { + return code; + } + + @Override + public String toString() { + return code; + } + +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/data/UnknownResponseCode.java b/office-sdk/src/main/java/com/worldline/sips/api/model/data/UnknownResponseCode.java new file mode 100644 index 0000000..8a5065b --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/data/UnknownResponseCode.java @@ -0,0 +1,23 @@ +package com.worldline.sips.api.model.data; + +import com.fasterxml.jackson.annotation.JsonValue; + +public class UnknownResponseCode implements WalletResponseCode { + + private final String responseCode; + + public UnknownResponseCode(String responseCode) { + this.responseCode = responseCode; + } + + @Override + @JsonValue + public String getCode() { + return responseCode; + } + + @Override + public String toString() { + return responseCode; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletPaymentMeanData.java b/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletPaymentMeanData.java new file mode 100644 index 0000000..b9778e7 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletPaymentMeanData.java @@ -0,0 +1,50 @@ +package com.worldline.sips.api.model.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.worldline.sips.model.PaymentMeanBrand; +import java.time.YearMonth; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class WalletPaymentMeanData { + private String paymentMeanData; + private String paymentMeanId; + private String maskedPan; + private YearMonth panExpiryDate; + private String paymentMeanAlias; + private PaymentMeanBrand paymentMeanBrand; + private String[] paymentMeanCoBadgingBrandList; + private String[] transactionActors; + + public String getPaymentMeanId() { + return paymentMeanId; + } + + public String getMaskedPan() { + return maskedPan; + } + + public YearMonth getPanExpiryDate() { + return panExpiryDate; + } + + public String getPaymentMeanAlias() { + return paymentMeanAlias; + } + + public PaymentMeanBrand getPaymentMeanBrand() { + return paymentMeanBrand; + } + + public String[] getPaymentMeanCoBadgingBrandList() { + return paymentMeanCoBadgingBrandList; + } + + public String getPaymentMeanData() { + return paymentMeanData; + } + + public String[] getTransactionActors() { + return transactionActors; + } + +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletResponseCode.java b/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletResponseCode.java new file mode 100644 index 0000000..932caa3 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/data/WalletResponseCode.java @@ -0,0 +1,19 @@ +package com.worldline.sips.api.model.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public interface WalletResponseCode { + @JsonValue + String getCode(); + + @JsonCreator + static WalletResponseCode fromCode(String code) { + for (NamedWalletResponseCode responseCode : NamedWalletResponseCode.values()) { + if (responseCode.getCode().equals(code)) { + return responseCode; + } + } + return new UnknownResponseCode(code); + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/request/DeletePaymentMeanRequest.java b/office-sdk/src/main/java/com/worldline/sips/api/model/request/DeletePaymentMeanRequest.java new file mode 100644 index 0000000..102f2ea --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/request/DeletePaymentMeanRequest.java @@ -0,0 +1,38 @@ +package com.worldline.sips.api.model.request; + +import com.worldline.sips.api.WalletRequest; +import com.worldline.sips.api.model.response.DeletePaymentMeanResponse; + +public class DeletePaymentMeanRequest extends WalletRequest { + + private final String merchantWalletId; + private final String paymentMeanId; + private String intermediateServiceProviderId; + + public DeletePaymentMeanRequest(String merchantWalletId, String paymentMeanId) { + super("deletePaymentMean"); + this.merchantWalletId = merchantWalletId; + this.paymentMeanId = paymentMeanId; + } + + public String getMerchantWalletId() { + return merchantWalletId; + } + + public String getPaymentMeanId() { + return paymentMeanId; + } + + public String getIntermediateServiceProviderId() { + return intermediateServiceProviderId; + } + + public void setIntermediateServiceProviderId(String intermediateServiceProviderId) { + this.intermediateServiceProviderId = intermediateServiceProviderId; + } + + @Override + public Class getResponseType() { + return DeletePaymentMeanResponse.class; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/request/GetWalletDataRequest.java b/office-sdk/src/main/java/com/worldline/sips/api/model/request/GetWalletDataRequest.java new file mode 100644 index 0000000..5c98aa6 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/request/GetWalletDataRequest.java @@ -0,0 +1,32 @@ +package com.worldline.sips.api.model.request; + +import com.worldline.sips.api.WalletRequest; +import com.worldline.sips.api.model.response.GetWalletDataResponse; + +public class GetWalletDataRequest extends WalletRequest { + + private final String merchantWalletId; + private String intermediateServiceProviderId; + + public GetWalletDataRequest(String merchantWalletId) { + super("getWalletData"); + this.merchantWalletId = merchantWalletId; + } + + public String getIntermediateServiceProviderId() { + return intermediateServiceProviderId; + } + + public void setIntermediateServiceProviderId(String intermediateServiceProviderId) { + this.intermediateServiceProviderId = intermediateServiceProviderId; + } + + public String getMerchantWalletId() { + return merchantWalletId; + } + + @Override + public Class getResponseType() { + return GetWalletDataResponse.class; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/response/DeletePaymentMeanResponse.java b/office-sdk/src/main/java/com/worldline/sips/api/model/response/DeletePaymentMeanResponse.java new file mode 100644 index 0000000..a7d6960 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/response/DeletePaymentMeanResponse.java @@ -0,0 +1,14 @@ +package com.worldline.sips.api.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.worldline.sips.api.WalletResponse; +import java.time.OffsetDateTime; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DeletePaymentMeanResponse extends WalletResponse { + private OffsetDateTime walletActionDateTime; + + public OffsetDateTime getWalletActionDateTime() { + return walletActionDateTime; + } +} diff --git a/office-sdk/src/main/java/com/worldline/sips/api/model/response/GetWalletDataResponse.java b/office-sdk/src/main/java/com/worldline/sips/api/model/response/GetWalletDataResponse.java new file mode 100644 index 0000000..3673cf7 --- /dev/null +++ b/office-sdk/src/main/java/com/worldline/sips/api/model/response/GetWalletDataResponse.java @@ -0,0 +1,27 @@ +package com.worldline.sips.api.model.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.worldline.sips.api.WalletResponse; +import com.worldline.sips.api.model.data.WalletPaymentMeanData; +import java.time.OffsetDateTime; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetWalletDataResponse extends WalletResponse { + private OffsetDateTime walletCreationDateTime; + private OffsetDateTime walletLastActionDateTime; + + private List walletPaymentMeanDataList; + + public OffsetDateTime getWalletCreationDateTime() { + return walletCreationDateTime; + } + + public OffsetDateTime getWalletLastActionDateTime() { + return walletLastActionDateTime; + } + + public List getWalletPaymentMeanDataList() { + return walletPaymentMeanDataList; + } +} diff --git a/office-sdk/src/test/java/com/worldline/sips/api/model/data/WalletResponseDeserializeCodeTest.java b/office-sdk/src/test/java/com/worldline/sips/api/model/data/WalletResponseDeserializeCodeTest.java new file mode 100644 index 0000000..307fc65 --- /dev/null +++ b/office-sdk/src/test/java/com/worldline/sips/api/model/data/WalletResponseDeserializeCodeTest.java @@ -0,0 +1,22 @@ +package com.worldline.sips.api.model.data; + +import com.worldline.sips.util.ObjectMapperHolder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class WalletResponseDeserializeCodeTest { + + @Test + public void deserializeKnownCode() throws Exception { + WalletResponseCode red = ObjectMapperHolder.INSTANCE.get().readerFor(WalletResponseCode.class).readValue("\"00\""); + Assertions.assertEquals("00", red.getCode()); + Assertions.assertEquals(NamedWalletResponseCode.SUCCESS, red); + } + + @Test + public void deserializeUnknownCode() throws Exception { + WalletResponseCode red = ObjectMapperHolder.INSTANCE.get().readerFor(WalletResponseCode.class).readValue("\"01\""); + Assertions.assertEquals("01", red.getCode()); + Assertions.assertEquals(UnknownResponseCode.class, red.getClass()); + } +} diff --git a/office-sdk/src/test/java/com/worldline/sips/utils/WalletSipsTest.java b/office-sdk/src/test/java/com/worldline/sips/utils/WalletSipsTest.java new file mode 100644 index 0000000..b06e9e2 --- /dev/null +++ b/office-sdk/src/test/java/com/worldline/sips/utils/WalletSipsTest.java @@ -0,0 +1,102 @@ +package com.worldline.sips.utils; + +import com.worldline.sips.SipsClient; +import com.worldline.sips.api.configuration.OfficeEnvironment; +import com.worldline.sips.api.model.data.NamedWalletResponseCode; +import com.worldline.sips.api.model.data.WalletPaymentMeanData; +import com.worldline.sips.api.model.request.DeletePaymentMeanRequest; +import com.worldline.sips.api.model.request.GetWalletDataRequest; +import com.worldline.sips.api.model.response.DeletePaymentMeanResponse; +import com.worldline.sips.api.model.response.GetWalletDataResponse; +import com.worldline.sips.model.NamedPaymentMeanBrand; +import com.worldline.sips.util.ObjectMapperHolder; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WalletSipsTest { + + private SipsClient sipsClient; + + public static final String SECRET_KEY = "yJb7SGlw6-_uy7E0aq8HG_V_rGPzZDuF7tzcvS-gzD4"; + + + @BeforeEach + public void setUp() throws Exception { + sipsClient = new SipsClient(OfficeEnvironment.TEST, "011122211100002", 1, SECRET_KEY); + } + + @Test + void testDeleteWalletMean() throws Exception { + DeletePaymentMeanRequest req = new DeletePaymentMeanRequest("ATCTR59_3884977603", "2"); + System.out.println(ObjectMapperHolder.INSTANCE.get().writerFor(DeletePaymentMeanRequest.class).writeValueAsString(req)); + DeletePaymentMeanResponse response = sipsClient.send(req); + System.out.println("Response"); + System.out.println(ObjectMapperHolder.INSTANCE.get().writerFor(DeletePaymentMeanResponse.class).writeValueAsString(response)); + } + + @Test + void testSendWalletRequest() throws Exception { + GetWalletDataRequest req = new GetWalletDataRequest("ATCTR59_3884977603"); + System.out.println(ObjectMapperHolder.INSTANCE.get().writerFor(GetWalletDataRequest.class).writeValueAsString(req)); + GetWalletDataResponse response = sipsClient.send(req); +// System.out.println("Response"); + System.out.println(ObjectMapperHolder.INSTANCE.get().writerFor(GetWalletDataResponse.class).writeValueAsString(response)); +// for (WalletPaymentMeanData walletPaymentMeanData : response.getWalletPaymentMeanDataList()) { +// YearMonth panExpiryDate = walletPaymentMeanData.getPanExpiryDate(); +// // we check the expiry date (we don't care about expired cards after 3 months) +// if (panExpiryDate == null || panExpiryDate.isAfter(YearMonth.now().plus(3, ChronoUnit.MONTHS))) { +// System.out.println("yeet " + panExpiryDate); +// continue; +// } +// System.out.println("sus " + panExpiryDate); +// } + } + + @Test + void testResponseDeserialization() throws Exception { + GetWalletDataResponse response = ObjectMapperHolder.INSTANCE.get().readerFor(GetWalletDataResponse.class) + .readValue("{\"seal\":\"1\"}"); + Assertions.assertEquals("1", response.getSeal()); + + + response = ObjectMapperHolder.INSTANCE.get().readerFor(GetWalletDataResponse.class) + .readValue("{\"walletResponseCode\":\"00\"}"); + Assertions.assertEquals(NamedWalletResponseCode.SUCCESS, response.getWalletResponseCode()); + + + response = ObjectMapperHolder.INSTANCE.get().readerFor(GetWalletDataResponse.class) + .readValue("{\n" + + " \"walletCreationDateTime\": \"2013-12-23T05:17:26-12:00\",\n" + + " \"walletLastActionDateTime\": \"2014-01-19T23:16:00-12:00\",\n" + + " \"walletResponseCode\": \"00\",\n" + + " \"walletPaymentMeanDataList\": [\n" + + " {\n" + + " \"paymentMeanId\": \"14\",\n" + + " \"maskedPan\": \"4977##########02\",\n" + + " \"paymentMeanBrand\": \"SEPA_DIRECT_DEBIT\"\n" + + " },\n" + + " {\n" + + " \"paymentMeanId\": \"13\",\n" + + " \"maskedPan\": \"4977##########55\",\n" + + " \"paymentMeanAlias\": \"MySDD\",\n" + + " \"panExpiryDate\": \"201501\",\n" + + " \"paymentMeanBrand\": \"CB\"\n" + + " }\n" + + " ],\n" + + " \"seal\": \"4579cfc4044c29550327f9cba0be400129e95cb5b2639c6e301484930b4f9f94\"\n" + + "}"); + Assertions.assertEquals(NamedWalletResponseCode.SUCCESS, response.getWalletResponseCode()); + List list = response.getWalletPaymentMeanDataList(); + Assertions.assertNotNull(list); + Assertions.assertEquals(2, list.size()); + WalletPaymentMeanData walletPaymentMeanData = list.get(0); + Assertions.assertNull(walletPaymentMeanData.getPaymentMeanAlias()); + Assertions.assertNull(walletPaymentMeanData.getPanExpiryDate()); + Assertions.assertEquals("14", walletPaymentMeanData.getPaymentMeanId()); + Assertions.assertEquals("4977##########02", walletPaymentMeanData.getMaskedPan()); + Assertions.assertEquals(NamedPaymentMeanBrand.SEPA_DIRECT_DEBIT, walletPaymentMeanData.getPaymentMeanBrand()); + Assertions.assertNull(response.getErrorFieldName()); + } +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/configuration/Configuration.java b/payment-sdk-common/src/main/java/com/worldline/sips/configuration/Configuration.java deleted file mode 100644 index 70d1bda..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/configuration/Configuration.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.worldline.sips.configuration; - -public class Configuration { - /** - * The targeted version of the API - */ - public static final String INTERFACE_VERSION = "IR_WS_2.35"; - - private Configuration() { - // Nothing to see here - } -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java b/payment-sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java deleted file mode 100644 index 3d7b4d6..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.worldline.sips.helper; - -import org.apache.commons.lang3.builder.ToStringStyle; - -public class AlphabeticalReflectionToStringBuilder extends SortedReflectionToStringBuilder { - - public AlphabeticalReflectionToStringBuilder(Object object, ToStringStyle style) { - super(object, style); - setComparator(new AlphabeticalFieldComparator()); - } - - -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java b/payment-sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java deleted file mode 100644 index a389c03..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.worldline.sips.helper; - -import org.apache.commons.lang3.ClassUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.RecursiveToStringStyle; - -import java.util.Arrays; - - -public final class SealStringStyle extends RecursiveToStringStyle { - public SealStringStyle() { - super(); - setUseClassName(false); - setUseIdentityHashCode(false); - setUseFieldNames(false); - setNullText(StringUtils.EMPTY); - setContentStart(StringUtils.EMPTY); - setContentEnd(StringUtils.EMPTY); - setFieldSeparator(StringUtils.EMPTY); - setArrayStart(StringUtils.EMPTY); - setArrayEnd(StringUtils.EMPTY); - setArraySeparator(StringUtils.EMPTY); - } - - @Override - public void appendDetail(StringBuffer buffer, String fieldName, Object value) { - if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && - !String.class.equals(value.getClass()) && - accept(value.getClass())) { - buffer.append(AlphabeticalReflectionToStringBuilder.toString(value, this)); - } else { - super.appendDetail(buffer, fieldName, value); - } - } - - @Override - protected void appendDetail(StringBuffer buffer, String fieldName, Object[] array) { - Arrays.sort(array); - super.appendDetail(buffer, fieldName, array); - } - - @Override - protected boolean accept(Class clazz) { - return !clazz.isEnum() && clazz.getPackage().getName().startsWith("com.worldline"); - } -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java b/payment-sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java deleted file mode 100644 index b5a1f5c..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.worldline.sips.helper; - -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; - -public class SortedReflectionToStringBuilder extends ReflectionToStringBuilder { - - private Comparator comparator; - - public SortedReflectionToStringBuilder(Object object, ToStringStyle style) { - super(object, style); - } - - - public void setComparator(Comparator comparator) { - this.comparator = comparator; - } - - @Override - protected void appendFieldsIn(Class clazz) { - if (clazz.isArray()) { - this.reflectionAppendArray(this.getObject()); - return; - } - - Field[] fields = clazz.getDeclaredFields(); - - if (comparator != null) { - Arrays.sort(fields, comparator); - } - AccessibleObject.setAccessible(fields, true); - for (final Field field : fields) { - final String fieldName = field.getName(); - if (this.accept(field)) { - try { - // Warning: Field.get(Object) creates wrappers objects - // for primitive types. - final Object fieldValue = this.getValue(field); - if (!isExcludeNullValues() || fieldValue != null) { - this.append(fieldName, fieldValue); - } - } catch (final IllegalAccessException ex) { - //this can't happen. Would get a Security exception - // instead - //throw a runtime exception in case the impossible - // happens. - throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage()); - } - } - } - } - - -} - - diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java b/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java deleted file mode 100644 index 1cb4fa4..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.worldline.sips.model; - -public enum PaymentMeanBrand { - AMEX, BCMC, CB, CBCONLINE, ELV, IDEAL, IGNHOMEPAY, KBCONLINE, MAESTRO, MASTERCARD, MASTERPASS, PAYPAL, PAYTRAIL, SOFORTUBERWEISUNG, VISA, VPAY -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java b/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java deleted file mode 100644 index 48a41ec..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.worldline.sips.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import org.apache.commons.lang3.StringUtils; - -public enum PaymentMeanType { - CARD, CREDIT_TRANSFER, DIRECT_DEBIT, EMPTY; - - @JsonCreator - public static PaymentMeanType fromValue(String value) { - if (StringUtils.isBlank(value)) { - return PaymentMeanType.EMPTY; - } - - return PaymentMeanType.valueOf(value); - } - -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleType.java b/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleType.java deleted file mode 100644 index 6d238f7..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.worldline.sips.model; - -public enum RuleType { - G, N -} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/util/SealCalculator.java b/payment-sdk-common/src/main/java/com/worldline/sips/util/SealCalculator.java deleted file mode 100644 index 47e21b7..0000000 --- a/payment-sdk-common/src/main/java/com/worldline/sips/util/SealCalculator.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.worldline.sips.util; - -import com.worldline.sips.exception.SealCalculationException; -import com.worldline.sips.helper.AlphabeticalReflectionToStringBuilder; -import com.worldline.sips.helper.SealStringStyle; -import com.worldline.sips.model.InitializationResponse; -import com.worldline.sips.model.PaymentRequest; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -public class SealCalculator { - private SealCalculator() { - // Nothing to see here - } - - /** - * Calculate the encrypted seal for a {@link PaymentRequest} based on a given seal string. - * - * @param sealString the seal string for the {@link PaymentRequest} that needs to be signed - * @param key the merchant's secret key - * @return the encrypted seal for the request - * @throws SealCalculationException when the encryption fails (e.g. algorithm missing, invalid key specified) - * @see #getSealString(PaymentRequest) - */ - public static String calculate(String sealString, String key) throws SealCalculationException { - try { - Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); - SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); - hmacSHA256.init(secretKeySpec); - return Hex.encodeHexString(hmacSHA256.doFinal(sealString.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new SealCalculationException("Seal could not be calculated!", e); - } - - } - - /** - * Sort & concatenate the fields of a given {@link PaymentRequest}, needed to correctly sign the request. - * - * @param paymentRequest the request that's needs to be signed - * @return a String, formatted as described in the API docs. - */ - public static String getSealString(PaymentRequest paymentRequest) { - ReflectionToStringBuilder reflectionToStringBuilder = new AlphabeticalReflectionToStringBuilder(paymentRequest, new SealStringStyle()); - reflectionToStringBuilder.setExcludeFieldNames("keyVersion"); - reflectionToStringBuilder.setExcludeNullValues(true); - reflectionToStringBuilder.setAppendStatics(true); - return reflectionToStringBuilder.toString(); - } - - /** - * Sort & concatenate the fields of a given {@link PaymentRequest}, needed to correctly verify a response. - * - * @param initializationResponse the response that's needs to be verified - * @return a String, formatted as described in the API docs. - */ - public static String getSealString(InitializationResponse initializationResponse) { - ReflectionToStringBuilder reflectionToStringBuilder = new AlphabeticalReflectionToStringBuilder(initializationResponse, new SealStringStyle()); - reflectionToStringBuilder.setExcludeFieldNames("seal"); - reflectionToStringBuilder.setExcludeNullValues(true); - - return reflectionToStringBuilder.toString(); - } - -} - diff --git a/payment-sdk/build.gradle b/payment-sdk/build.gradle index 2779cea..dd3c8b0 100644 --- a/payment-sdk/build.gradle +++ b/payment-sdk/build.gradle @@ -1,5 +1,5 @@ dependencies { - implementation project(':payment-sdk-common') + implementation project(':sdk-common') implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.2' diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/PaypageClient.java b/payment-sdk/src/main/java/com/worldline/sips/api/PaypageClient.java deleted file mode 100644 index 6239ce6..0000000 --- a/payment-sdk/src/main/java/com/worldline/sips/api/PaypageClient.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.worldline.sips.api; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.worldline.sips.api.configuration.Environment; -import com.worldline.sips.api.exception.*; -import com.worldline.sips.exception.SealCalculationException; -import com.worldline.sips.model.InitializationResponse; -import com.worldline.sips.model.PaymentRequest; -import com.worldline.sips.model.PaypageResponse; -import com.worldline.sips.util.ObjectMapperHolder; -import com.worldline.sips.util.SealCalculator; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.io.entity.StringEntity; - -import java.io.IOException; -import java.net.URI; -import java.util.Map; - -/** - * Interact with the SIPS API in payment page mode. - */ -public class PaypageClient { - - private final Environment environment; - private final Integer keyVersion; - private final String merchantId; - private final String secretKey; - private final String proxyHost; - private final Integer proxyPort; - private final boolean proxyEnabled; - - /** - * Construct a new instance of the client for a given {@link Environment} - * - * @param environment the API environment to connect to. - * @param merchantId the merchant's ID. - * @param keyVersion the version of the secret key to use. - * @param secretKey the merchant's secret key. - * @throws IncorrectProxyConfException when the proxy configuration is incorrect - * @throws InvalidEnvironmentException when an unknown environment is specified - * @throws InvalidKeyException when the key version is null, or a key is blank, empty or null. - * @throws InvalidMerchantException when the key version is null, or a key is blank, empty or null. - */ - public PaypageClient(Environment environment, String merchantId, Integer keyVersion, String secretKey) - throws InvalidEnvironmentException, IncorrectProxyConfException, InvalidKeyException, InvalidMerchantException { - this(environment, merchantId, keyVersion, secretKey, false, null, null); - } - - public PaypageClient(Environment environment, String merchantId, Integer keyVersion, String secretKey, boolean proxyEnabled, String proxyHost, Integer proxyPort) - throws InvalidEnvironmentException, InvalidMerchantException, InvalidKeyException, IncorrectProxyConfException { - if (environment == null) { - throw new InvalidEnvironmentException("Invalid environment specified!"); - } - - if (StringUtils.isBlank(merchantId)) { - throw new InvalidMerchantException("Invalid merchant ID specified!"); - } - - if (keyVersion == null) { - throw new InvalidKeyException("Invalid key version specified!"); - } - - if (StringUtils.isBlank(secretKey)) { - throw new InvalidKeyException("Invalid key specified!"); - } - - if (proxyEnabled) { - if(StringUtils.isBlank(proxyHost) || proxyPort == null){ - throw new IncorrectProxyConfException("ProxyEnabled is true but proxyHost or proxyPort not filled"); - } - } - - this.environment = environment; - this.keyVersion = keyVersion; - this.merchantId = merchantId; - this.secretKey = secretKey; - this.proxyEnabled=proxyEnabled; - this.proxyHost=proxyHost; - this.proxyPort=proxyPort; - } - - /** - * Initialize a session with the SIPS API for given parameters. - * This is always the first step in a payment process. - * - * @param paymentRequest the parameters to use during the requested session. - * @return The API 's response for the preformed request. - * @throws IncorrectSealException when the response has been tampered with. - * @throws PaymentInitializationException when initialization fails due to processing exceptions, see inner exception for details. - * @throws SealCalculationException when seal calculation fails, see inner excpetion for details. - * @see PaymentRequest - * @see InitializationResponse - * @see #verifySeal(InitializationResponse) - */ - public InitializationResponse initialize(PaymentRequest paymentRequest) throws IncorrectSealException, PaymentInitializationException, SealCalculationException { - try { - HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - if(this.proxyEnabled){ - HttpHost httpHost = new HttpHost(this.proxyHost, this.proxyPort); - httpClientBuilder.setProxy(httpHost); - } - CloseableHttpClient httpClient = httpClientBuilder.build(); - paymentRequest.setMerchantId(merchantId); - paymentRequest.setKeyVersion(keyVersion); - paymentRequest.setSeal(SealCalculator.calculate( - SealCalculator.getSealString(paymentRequest), secretKey)); - StringEntity requestEntity = new StringEntity( - ObjectMapperHolder.INSTANCE.get().writerFor(PaymentRequest.class) - .writeValueAsString(paymentRequest), - ContentType.APPLICATION_JSON); - - HttpPost postMethod = new HttpPost(getEnvironmentUrl()); - postMethod.setEntity(requestEntity); - - CloseableHttpResponse rawResponse = httpClient.execute(postMethod); - InitializationResponse initializationResponse = - ObjectMapperHolder.INSTANCE.get().readerFor(InitializationResponse.class) - .readValue(EntityUtils.toString(rawResponse.getEntity())); - - verifySeal(initializationResponse); - - return initializationResponse; - - } catch (JsonParseException | JsonMappingException e) { - throw new PaymentInitializationException("Exception while parsing PaymentRequest!", e); - } catch (IOException | ParseException e) { - throw new PaymentInitializationException("Exception while processing response from server!", e); - } - - } - - /** - * Decode a payment response for further processing. After the payment is made, the API will preform a - * POST request to the URL as defined in the {@link PaymentRequest}. - * - * @param parameters the content of the received request, mapped as key-value pairs. - * @return The API 's response for the preformed payment. - * @throws IncorrectSealException when the response has been tampered with. - * @see PaypageResponse - */ - public PaypageResponse decodeResponse(Map parameters) throws IncorrectSealException { - verifySeal(parameters.get("Data"), parameters.get("Seal")); - return ObjectMapperHolder.INSTANCE.get().copy() - .convertValue(parameters, PaypageResponse.class); - } - - private URI getEnvironmentUrl() { - return URI.create(environment.getUrl()); - } - - /** - * Verify the seal of an initialization response. To avoid tampered responses when a session is initialized, - * the seal for the received response should always be verified before returning the object to the user. - * - * @param initializationResponse the received response upon initialization - * @throws IncorrectSealException when the received seal is different from the one calculated - * @throws SealCalculationException when seal calculation fails, see inner excpetion for details. - */ - private void verifySeal(InitializationResponse initializationResponse) throws IncorrectSealException, SealCalculationException { - if (initializationResponse.getSeal() != null) { - String correctSeal = SealCalculator.calculate( - SealCalculator.getSealString(initializationResponse), secretKey); - if (!StringUtils.equals(correctSeal, initializationResponse.getSeal())) { - throw new IncorrectSealException("The initialization response has been tampered with!"); - } - } - } - - /** - * Verify the seal of a payment page response.To avoid tampered data for processed payments, - * the seal for the received response should always be verified before returning the object to the user. - * - * @param data the received response's Data attribute - * @param seal the received response's Seal attribute - * @throws IncorrectSealException when the received seal is different from the one calculated - */ - private void verifySeal(String data, String seal) throws IncorrectSealException { - String correctSeal = DigestUtils.sha256Hex(data + secretKey); - if (!StringUtils.equals(correctSeal, seal)) { - throw new IncorrectSealException("The payment page response has been tampered with!"); - } - } - -} diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectProxyConfException.java b/payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectProxyConfException.java deleted file mode 100644 index 80afc9e..0000000 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectProxyConfException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.worldline.sips.api.exception; - -public class IncorrectProxyConfException extends Exception { - - public IncorrectProxyConfException(String message){ - super(message); - } -} diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/PaymentInitializationException.java b/payment-sdk/src/main/java/com/worldline/sips/api/exception/PaymentInitializationException.java deleted file mode 100644 index 9a01bbe..0000000 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/PaymentInitializationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.worldline.sips.api.exception; - -public class PaymentInitializationException extends Exception { - public PaymentInitializationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentConfiguration.java b/payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentConfiguration.java new file mode 100644 index 0000000..1a90191 --- /dev/null +++ b/payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentConfiguration.java @@ -0,0 +1,15 @@ +package com.worldline.sips.configuration; + +/** + * Container for global configuration values + */ +public class PaymentConfiguration { + /** + * The targeted version of the API + */ + public static final String INTERFACE_VERSION = "IR_WS_2.46"; + + private PaymentConfiguration() { + // Nothing to see here + } +} diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/configuration/Environment.java b/payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentEnvironment.java similarity index 59% rename from payment-sdk/src/main/java/com/worldline/sips/api/configuration/Environment.java rename to payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentEnvironment.java index 774df0f..21473d2 100644 --- a/payment-sdk/src/main/java/com/worldline/sips/api/configuration/Environment.java +++ b/payment-sdk/src/main/java/com/worldline/sips/configuration/PaymentEnvironment.java @@ -1,16 +1,18 @@ -package com.worldline.sips.api.configuration; +package com.worldline.sips.configuration; + +import com.worldline.sips.model.SipsEnvironment; /** - * The different environments available for the Worldline SIPS API. + * The different environments available for the Worldline SIPS payment API. */ -public enum Environment { +public enum PaymentEnvironment implements SipsEnvironment { SIMU("https://payment-webinit.simu.sips-services.com/rs-services/v2/paymentInit"), TEST("https://payment-webinit.test.sips-services.com/rs-services/v2/paymentInit"), PROD("https://payment-webinit.sips-services.com/rs-services/v2/paymentInit"); private final String url; - Environment(String url) { + PaymentEnvironment(String url) { this.url = url; } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java b/payment-sdk/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java similarity index 72% rename from payment-sdk-common/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java rename to payment-sdk/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java index d1544e8..1726049 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java +++ b/payment-sdk/src/main/java/com/worldline/sips/helper/ResponseDataDeserializer.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.worldline.sips.model.ResponseData; +import com.worldline.sips.model.data.ResponseData; import com.worldline.sips.util.ObjectMapperHolder; import org.apache.commons.lang3.StringUtils; @@ -21,18 +21,18 @@ public ResponseData deserialize(JsonParser jsonParser, DeserializationContext de } final String value = jsonParser.getText().trim(); final Map mapped = Arrays.stream(value.split("\\|")) - .map(element -> element.split("=", 2)) - .filter(pair -> isNotNullOrEmpty(pair[1])) - .collect(Collectors.toMap(pair -> pair[0], pair -> pair[1])); + .map(element -> element.split("=", 2)) + .filter(pair -> isNotNullOrEmpty(pair[1])) + .collect(Collectors.toMap(pair -> pair[0], pair -> pair[1])); return ObjectMapperHolder.INSTANCE.get().copy() - .convertValue(mapped, ResponseData.class); + .convertValue(mapped, ResponseData.class); } private boolean isNotNullOrEmpty(final CharSequence cs) { - return !StringUtils.isBlank(cs) && !StringUtils.equals(cs, "null"); + return ! StringUtils.isBlank(cs) && ! StringUtils.equals(cs, "null"); } } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java b/payment-sdk/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java similarity index 94% rename from payment-sdk-common/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java rename to payment-sdk/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java index 420c5b4..2e22578 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java +++ b/payment-sdk/src/main/java/com/worldline/sips/helper/RuleResultListDeserializer.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.worldline.sips.model.RuleResult; +import com.worldline.sips.model.data.RuleResult; import com.worldline.sips.util.ObjectMapperHolder; import java.io.IOException; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/AcquirerResponseCode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/AcquirerResponseCode.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/AcquirerResponseCode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/AcquirerResponseCode.java index bf05bb5..f3b56b4 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/AcquirerResponseCode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/AcquirerResponseCode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/Address.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/Address.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/Address.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/Address.java index 4876900..ae6cc4e 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/Address.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/Address.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/CaptureMode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/CaptureMode.java similarity index 64% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/CaptureMode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/CaptureMode.java index fa61182..0d2a9cd 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/CaptureMode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/CaptureMode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum CaptureMode { AUTHOR_CAPTURE, IMMEDIATE, VALIDATION diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/CardCSCResultCode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/CardCSCResultCode.java similarity index 96% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/CardCSCResultCode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/CardCSCResultCode.java index fef6ab8..cfb8de1 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/CardCSCResultCode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/CardCSCResultCode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/Contact.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/Contact.java similarity index 97% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/Contact.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/Contact.java index 9acf6c3..f545eb5 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/Contact.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/Contact.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/Currency.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/Currency.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/Currency.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/Currency.java index 5b4888d..36e725c 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/Currency.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/Currency.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerAddress.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerAddress.java similarity index 92% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerAddress.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerAddress.java index 7ad5542..105a668 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerAddress.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerAddress.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerContact.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerContact.java similarity index 95% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerContact.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerContact.java index 67745c7..b6d4615 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/CustomerContact.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/CustomerContact.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/GuaranteeIndicator.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/GuaranteeIndicator.java similarity index 90% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/GuaranteeIndicator.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/GuaranteeIndicator.java index e7a272f..9a4a54d 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/GuaranteeIndicator.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/GuaranteeIndicator.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentMethod.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentMethod.java similarity index 92% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentMethod.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentMethod.java index b760b94..0c464ed 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentMethod.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentMethod.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentProgram.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentProgram.java similarity index 94% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentProgram.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentProgram.java index 912fab3..c3b07c3 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentProgram.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentProgram.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentStatus.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentStatus.java similarity index 95% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentStatus.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentStatus.java index a347920..9b4f005 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/HolderAuthentStatus.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/HolderAuthentStatus.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/Language.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/Language.java similarity index 97% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/Language.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/Language.java index 9c3a1ce..6c1c2ec 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/Language.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/Language.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/OrderChannel.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/OrderChannel.java similarity index 58% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/OrderChannel.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/OrderChannel.java index 3e42adc..abe8bc4 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/OrderChannel.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/OrderChannel.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum OrderChannel { INAPP, INTERNET, MOTO diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PanEntryMode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/PanEntryMode.java similarity index 62% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/PanEntryMode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/PanEntryMode.java index 6f365e8..726f071 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PanEntryMode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/PanEntryMode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum PanEntryMode { MANUAL, OEMPAY, VIRTUAL, WALLET diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentPattern.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/PaymentPattern.java similarity index 68% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentPattern.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/PaymentPattern.java index 10bd71d..29e88f7 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentPattern.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/PaymentPattern.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum PaymentPattern { ONE_SHOT, RECURRING_1, RECURRING_N, INSTALMENT diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageData.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/PaypageData.java similarity index 92% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageData.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/PaypageData.java index eb33d29..36ad861 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageData.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/PaypageData.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RedirectionStatusCode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RedirectionStatusCode.java similarity index 96% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/RedirectionStatusCode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/RedirectionStatusCode.java index ade3891..6d2ab1c 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RedirectionStatusCode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RedirectionStatusCode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseCode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseCode.java similarity index 97% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseCode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseCode.java index 6be7acb..3adcaf6 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseCode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseCode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseData.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseData.java similarity index 94% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseData.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseData.java index b1f276b..5bf94e5 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/ResponseData.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/ResponseData.java @@ -1,10 +1,11 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.worldline.sips.helper.BooleanDeserializer; import com.worldline.sips.helper.RuleResultListDeserializer; - +import com.worldline.sips.model.PaymentMeanBrand; +import com.worldline.sips.model.PaymentMeanType; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.YearMonth; @@ -71,8 +72,16 @@ public class ResponseData { private String orderId; private String returnContext; + private int s10TransactionId; + private LocalDate s10TransactionIdDate; + public int getS10TransactionId() { + return s10TransactionId; + } + public LocalDate getS10TransactionIdDate() { + return s10TransactionIdDate; + } public int getAmount() { return amount; @@ -277,4 +286,4 @@ public String getOrderId() { public String getReturnContext() { return returnContext; } -} \ No newline at end of file +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleCode.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleCode.java similarity index 93% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/RuleCode.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/RuleCode.java index ef570ac..411c732 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleCode.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleCode.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum RuleCode { @@ -76,4 +76,4 @@ public enum RuleCode { PQ, PR, QP -} \ No newline at end of file +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResult.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResult.java similarity index 94% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResult.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResult.java index 0e2532a..29c6725 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResult.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResult.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public class RuleResult { private RuleCode ruleCode; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResultIndicator.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResultIndicator.java similarity index 89% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResultIndicator.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResultIndicator.java index 1bb5803..af41528 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleResultIndicator.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleResultIndicator.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleSetting.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleSetting.java similarity index 89% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/RuleSetting.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/RuleSetting.java index 46e9a81..cb6c1c8 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/RuleSetting.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleSetting.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleType.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleType.java new file mode 100644 index 0000000..dc408b7 --- /dev/null +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/RuleType.java @@ -0,0 +1,5 @@ +package com.worldline.sips.model.data; + +public enum RuleType { + G, N +} diff --git a/payment-sdk/src/main/java/com/worldline/sips/model/data/S10TransactionReference.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/S10TransactionReference.java new file mode 100644 index 0000000..5f74753 --- /dev/null +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/S10TransactionReference.java @@ -0,0 +1,36 @@ +package com.worldline.sips.model.data; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.time.LocalDate; + + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class S10TransactionReference { + private int s10TransactionId; + private LocalDate s10TransactionIdDate; + + public S10TransactionReference() { + + } + + public S10TransactionReference(int s10TransactionId, LocalDate date) { + this.s10TransactionId = s10TransactionId; + this.s10TransactionIdDate = date; + } + + public int getS10TransactionId() { + return s10TransactionId; + } + + public void setS10TransactionId(int s10TransactionId) { + this.s10TransactionId = s10TransactionId; + } + + public LocalDate getS10TransactionIdDate() { + return s10TransactionIdDate; + } + + public void setS10TransactionIdDate(LocalDate s10TransactionIdDate) { + this.s10TransactionIdDate = s10TransactionIdDate; + } +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/ScoreColor.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/ScoreColor.java similarity index 91% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/ScoreColor.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/ScoreColor.java index a6abf98..d321907 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/ScoreColor.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/ScoreColor.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; import com.fasterxml.jackson.annotation.JsonCreator; import org.apache.commons.lang3.StringUtils; diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/WalletType.java b/payment-sdk/src/main/java/com/worldline/sips/model/data/WalletType.java similarity index 70% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/WalletType.java rename to payment-sdk/src/main/java/com/worldline/sips/model/data/WalletType.java index ee31dd6..2fa6582 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/WalletType.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/data/WalletType.java @@ -1,4 +1,4 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.data; public enum WalletType { BCMCMOBILE, MASTERPASS, MERCHANT_WALLET, PAYLIB, VISACHECKOUT diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentRequest.java b/payment-sdk/src/main/java/com/worldline/sips/model/request/PaymentRequest.java similarity index 82% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentRequest.java rename to payment-sdk/src/main/java/com/worldline/sips/model/request/PaymentRequest.java index d0b5b17..d8b51b8 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaymentRequest.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/request/PaymentRequest.java @@ -1,10 +1,21 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.request; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.worldline.sips.configuration.Configuration; - +import com.worldline.sips.SIPSRequest; +import com.worldline.sips.configuration.PaymentConfiguration; +import com.worldline.sips.model.PaymentMeanBrand; +import com.worldline.sips.model.data.Address; +import com.worldline.sips.model.data.CaptureMode; +import com.worldline.sips.model.data.Contact; +import com.worldline.sips.model.data.Currency; +import com.worldline.sips.model.data.CustomerAddress; +import com.worldline.sips.model.data.CustomerContact; +import com.worldline.sips.model.data.Language; +import com.worldline.sips.model.data.OrderChannel; +import com.worldline.sips.model.data.PaypageData; +import com.worldline.sips.model.data.S10TransactionReference; +import com.worldline.sips.model.response.InitializationResponse; import java.net.URL; import java.util.TreeSet; @@ -12,12 +23,11 @@ * Request to initialize a session with the Worldline SIPS API. * The possible values of each field are described in the API doc. * - * @see Configuration + * @see PaymentConfiguration */ @JsonPropertyOrder(alphabetic = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class PaymentRequest { - private static final String INTERFACE_VERSION = Configuration.INTERFACE_VERSION; +public class PaymentRequest extends SIPSRequest { private final TreeSet paymentMeanBrandList = new TreeSet<>(); @@ -49,12 +59,26 @@ public class PaymentRequest { private String returnContext; private String transactionOrigin; private String transactionReference; - private String seal; private String statementReference; private String templateName; private PaypageData paypageData; + private S10TransactionReference s10TransactionReference; + + private String interfaceVersion = PaymentConfiguration.INTERFACE_VERSION; + + public PaymentRequest() { + super(""); + } - public Integer getAmount() { + public String getInterfaceVersion() { + return interfaceVersion; + } + + public void setInterfaceVersion(String interfaceVersion) { + this.interfaceVersion = interfaceVersion; + } + + public Integer getAmount() { return amount; } @@ -190,11 +214,6 @@ public void setHolderContact(Contact holderContact) { this.holderContact = holderContact; } - @JsonProperty("interfaceVersion") - public String getInterfaceVersion() { - return INTERFACE_VERSION; - } - public String getIntermediateServiceProviderId() { return intermediateServiceProviderId; } @@ -255,6 +274,10 @@ public String getOrderId() { return orderId; } + public void setOrderId(String orderId) { + this.orderId = orderId; + } + public String getReturnContext() { return returnContext; } @@ -271,10 +294,6 @@ public void setTransactionOrigin(String transactionOrigin) { this.transactionOrigin = transactionOrigin; } - public void setOrderId(String orderId) { - this.orderId = orderId; - } - public TreeSet getPaymentMeanBrandList() { return paymentMeanBrandList; } @@ -287,14 +306,6 @@ public void setTransactionReference(String transactionReference) { this.transactionReference = transactionReference; } - public String getSeal() { - return seal; - } - - public void setSeal(String seal) { - this.seal = seal; - } - public String getStatementReference() { return statementReference; } @@ -318,4 +329,17 @@ public PaypageData getPaypageData() { public void setPaypageData(PaypageData paypageData) { this.paypageData = paypageData; } + + public S10TransactionReference getS10TransactionReference() { + return s10TransactionReference; + } + + public void setS10TransactionReference(S10TransactionReference s10TransactionReference) { + this.s10TransactionReference = s10TransactionReference; + } + + @Override + public Class getResponseType() { + return InitializationResponse.class; + } } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/InitializationResponse.java b/payment-sdk/src/main/java/com/worldline/sips/model/response/InitializationResponse.java similarity index 78% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/InitializationResponse.java rename to payment-sdk/src/main/java/com/worldline/sips/model/response/InitializationResponse.java index 4d05df2..1e603a2 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/InitializationResponse.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/response/InitializationResponse.java @@ -1,20 +1,19 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.response; +import com.worldline.sips.SIPSResponse; +import com.worldline.sips.model.data.RedirectionStatusCode; import java.net.URL; /** * The server's response to a session initialization request. */ -public class InitializationResponse { +public class InitializationResponse extends SIPSResponse { private String errorFieldName; private String redirectionData; private RedirectionStatusCode redirectionStatusCode; private String redirectionStatusMessage; private URL redirectionUrl; private String redirectionVersion; - private ResponseCode responseCode; - private String seal; - public String getErrorFieldName() { return errorFieldName; } @@ -63,19 +62,4 @@ public void setRedirectionVersion(String redirectionVersion) { this.redirectionVersion = redirectionVersion; } - public ResponseCode getResponseCode() { - return responseCode; - } - - public void setResponseCode(ResponseCode responseCode) { - this.responseCode = responseCode; - } - - public String getSeal() { - return seal; - } - - public void setSeal(String seal) { - this.seal = seal; - } } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageResponse.java b/payment-sdk/src/main/java/com/worldline/sips/model/response/PaypageResponse.java similarity index 82% rename from payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageResponse.java rename to payment-sdk/src/main/java/com/worldline/sips/model/response/PaypageResponse.java index f4d1da4..0e2c87e 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/model/PaypageResponse.java +++ b/payment-sdk/src/main/java/com/worldline/sips/model/response/PaypageResponse.java @@ -1,16 +1,18 @@ -package com.worldline.sips.model; +package com.worldline.sips.model.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.worldline.sips.SIPSResponse; import com.worldline.sips.helper.ResponseDataDeserializer; +import com.worldline.sips.model.data.ResponseData; /** * The result of payment made via the SIPS payment page. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class PaypageResponse { +public class PaypageResponse extends SIPSResponse { @JsonProperty("Data") @JsonUnwrapped @@ -20,8 +22,6 @@ public class PaypageResponse { private String encode; @JsonProperty("InterfaceVersion") private String interFaceVersion; - @JsonProperty("Seal") - private String seal; public ResponseData getData() { return data; @@ -46,12 +46,4 @@ public String getInterFaceVersion() { public void setInterFaceVersion(String interFaceVersion) { this.interFaceVersion = interFaceVersion; } - - public String getSeal() { - return seal; - } - - public void setSeal(String seal) { - this.seal = seal; - } } diff --git a/payment-sdk/src/test/java/com/worldline/sips/api/PaypageClientTest.java b/payment-sdk/src/test/java/com/worldline/sips/api/PaypageClientTest.java index f3a6e73..2818960 100644 --- a/payment-sdk/src/test/java/com/worldline/sips/api/PaypageClientTest.java +++ b/payment-sdk/src/test/java/com/worldline/sips/api/PaypageClientTest.java @@ -1,61 +1,113 @@ package com.worldline.sips.api; -import com.worldline.sips.api.configuration.Environment; -import com.worldline.sips.api.exception.IncorrectProxyConfException; -import com.worldline.sips.model.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import com.worldline.sips.SipsClient; +import com.worldline.sips.configuration.PaymentEnvironment; +import com.worldline.sips.exception.IncorrectProxyConfException; +import com.worldline.sips.model.data.Currency; +import com.worldline.sips.model.data.OrderChannel; +import com.worldline.sips.model.data.RedirectionStatusCode; +import com.worldline.sips.model.data.S10TransactionReference; +import com.worldline.sips.model.request.PaymentRequest; +import com.worldline.sips.model.response.InitializationResponse; +import com.worldline.sips.model.response.PaypageResponse; +import com.worldline.sips.security.SealCalculator; +import com.worldline.sips.util.ObjectMapperHolder; import java.net.URL; +import java.time.LocalDate; import java.util.HashMap; import java.util.Map; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class PaypageClientTest { - private PaypageClient paypageClient; + + public static final String SECRET_KEY = "002001000000003_KEY1"; + private SipsClient paypageClient; + private SipsClient decodeClient; private PaymentRequest paymentRequest; private Map responseParameters; @BeforeEach void setUp() throws Exception { - paypageClient = new PaypageClient(Environment.SIMU, "002001000000001", 1, "002001000000001_KEY1"); + paypageClient = new SipsClient(PaymentEnvironment.SIMU, "002001000000003", 1, SECRET_KEY); + decodeClient = new SipsClient(PaymentEnvironment.SIMU, "002001000000001", 1, "002001000000001_KEY1"); paymentRequest = new PaymentRequest(); paymentRequest.setAmount(2); + paymentRequest.setCurrencyCode(Currency.EUR); paymentRequest.setOrderChannel(OrderChannel.INTERNET); paymentRequest.setNormalReturnUrl(new URL("http://localhost")); - paymentRequest.setTransactionReference(UUID.randomUUID().toString().substring(0, 12).replace("-", "")); + paymentRequest.setS10TransactionReference(new S10TransactionReference(ThreadLocalRandom.current().nextInt(0, 1_000_000), LocalDate.now())); +// paymentRequest.setTransactionReference("1"); responseParameters = new HashMap<>(); responseParameters.put("InterfaceVersion", "HP_2.0"); - } @Test - void testClientProxyException(){ - assertThrows(IncorrectProxyConfException.class,()-> new PaypageClient(Environment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, "monProxy", null)); + void testClientProxyException() { + assertThrows(IncorrectProxyConfException.class, () -> new SipsClient(PaymentEnvironment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, "monProxy", null)); - assertThrows(IncorrectProxyConfException.class,()-> new PaypageClient(Environment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, "", 3128)); + assertThrows(IncorrectProxyConfException.class, () -> new SipsClient(PaymentEnvironment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, "", 3128)); - assertThrows(IncorrectProxyConfException.class,()-> new PaypageClient(Environment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, null, null)); + assertThrows(IncorrectProxyConfException.class, () -> new SipsClient(PaymentEnvironment.TEST, "002001000000001", 1, "002001000000001_KEY1", true, null, null)); } @Test void execute() throws Exception { - InitializationResponse initializationResponse = paypageClient.initialize(paymentRequest); - assertEquals(RedirectionStatusCode.TRANSACTION_INITIALIZED, initializationResponse.getRedirectionStatusCode(), "Initialization failed!"); + InitializationResponse initializationResponse = paypageClient.send(paymentRequest); + + System.out.println(ObjectMapperHolder.INSTANCE.get().writerFor(PaymentRequest.class) + .writeValueAsString(paymentRequest)); + System.err.println(ObjectMapperHolder.INSTANCE.get().writerFor(InitializationResponse.class) + .writeValueAsString(initializationResponse)); + assertEquals(RedirectionStatusCode.TRANSACTION_INITIALIZED, initializationResponse.getRedirectionStatusCode(), "Initialization failed! " + initializationResponse.getRedirectionStatusCode().name()); + } + + @Test + void computeSealFromDoc_success() throws Exception { + PaymentRequest request = ObjectMapperHolder.INSTANCE.get().readerFor(PaymentRequest.class).readValue("{\n" + + " \"amount\": \"2500\",\n" + + " \"automaticResponseUrl\": \"https://automatic-response-url.fr/\",\n" + + " \"normalReturnUrl\": \"https://normal-return-url/\",\n" + + " \"captureDay\": \"0\",\n" + + " \"captureMode\": \"AUTHOR_CAPTURE\",\n" + + " \"currencyCode\": \"978\",\n" + + " \"customerContact\":{\n" + + " \"email\":\"customer@email.com\"\n" + + " },\n" + + " \"interfaceVersion\": \"IR_WS_2.22\",\n" + + " \"keyVersion\": \"1\",\n" + + " \"merchantId\": \"011223344550000\",\n" + + " \"orderChannel\": \"INTERNET\",\n" + + " \"orderId\": \"ORD101\",\n" + + " \"returnContext\": \"ReturnContext\",\n" + + " \"transactionOrigin\": \"SO_WEBAPPLI\",\n" + + " \"transactionReference\": \"TREFEXA2012\",\n" + + " \"seal\": \"322b943d833417c1570e0a282641e8e29d6a5b968c9b846694b5610e18ab5b81\"\n" + + "}"); + assertEquals("2500https://automatic-response-url.fr/0AUTHOR_CAPTURE978customer@email.comIR_WS_2.22011223344550000https://normal-return-url/INTERNETORD101ReturnContextSO_WEBAPPLITREFEXA2012", SealCalculator.getSealString(request), "Seal strings don't match"); + assertEquals("322b943d833417c1570e0a282641e8e29d6a5b968c9b846694b5610e18ab5b81", SealCalculator.calculate(SealCalculator.getSealString(request), "secret123")); } @Test void decodeResponse_with_succeeded_request() throws Exception { responseParameters.put("Data", "captureDay=0|captureMode=AUTHOR_CAPTURE|currencyCode=978|merchantId=002001000000001|orderChannel=INTERNET|responseCode=00|transactionDateTime=2018-02-06T07:54:23+01:00|transactionReference=b4fb98a9c2c|keyVersion=1|acquirerResponseCode=00|amount=2|authorisationId=12345|guaranteeIndicator=Y|cardCSCResultCode=4D|panExpiryDate=201902|paymentMeanBrand=VISA|paymentMeanType=CARD|customerIpAddress=194.78.195.168|maskedPan=4500#############01|holderAuthentRelegation=N|holderAuthentStatus=3D_SUCCESS|tokenPan=g011040a730424d1ba6|transactionOrigin=INTERNET|paymentPattern=ONE_SHOT"); responseParameters.put("Seal", "56bddfce68695b9b8a9de51c426aae31bb303fb15570f343975eaa3bd33c8c59"); - PaypageResponse paypageResponse = paypageClient.decodeResponse(responseParameters); + PaypageResponse paypageResponse = decodeClient.decodeResponse(PaypageResponse.class, responseParameters); + assertNotNull(paypageResponse.getData(), "Data field is empty!"); + assertNotNull(paypageResponse.getData().getResponseCode()); + + String rawJson = responseParameters.entrySet().stream().map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"").collect(Collectors.joining(",", "{", "}")); + + paypageResponse = decodeClient.decodeResponse(PaypageResponse.class, rawJson); assertNotNull(paypageResponse.getData(), "Data field is empty!"); assertNotNull(paypageResponse.getData().getResponseCode()); } @@ -64,7 +116,16 @@ void decodeResponse_with_succeeded_request() throws Exception { void decodeResponse_with_cancelled_request() throws Exception { responseParameters.put("Data", "captureDay=0|captureMode=AUTHOR_CAPTURE|currencyCode=978|merchantId=002001000000001|orderChannel=INTERNET|responseCode=17|transactionDateTime=2018-02-06T07:43:55+01:00|transactionReference=e1445438c15|keyVersion=1|amount=2|customerIpAddress=194.78.195.168|paymentPattern=ONE_SHOT"); responseParameters.put("Seal", "8f488030781e3196726ce0658dbc26f19781f7c7fbe212b39d63d3f4d1d77301"); - PaypageResponse paypageResponse = paypageClient.decodeResponse(responseParameters); + PaypageResponse paypageResponse = decodeClient.decodeResponse(PaypageResponse.class, responseParameters); + assertNotNull(paypageResponse.getData(), "Data field is empty!"); + assertNotNull(paypageResponse.getData().getResponseCode()); + } + + @Test + void decodeResponse_with_cancelled_request_unknown() throws Exception { + responseParameters.put("Data", "captureDay=0|captureMode=AUTHOR_CAPTURE|currencyCode=978|merchantId=011122211100002|orderChannel=INTERNET|responseCode=17|transactionDateTime=2022-11-21T10:58:26+01:00|transactionReference=20221121896|keyVersion=1|amount=99|paymentMeanBrand=UNKNOWN|paymentMeanType=CARD|customerEmail=dl-fr-rcs-store@worldline.com|customerId=ATCBE606_196784528|customerIpAddress=160.92.8.86|merchantWalletId=ATCBE606_196784528|orderId=ATCBE0007452752|returnContext=b2261cc1c58baa7bc95ed414d48c9282cdf008094d56457bc3c3bbafdd82e168#SHOP_BE_REF#1626#WEB#false#false#false#true#FR#147.161.183.78#null#null#false#null#null#null#0#FR#fr|paymentPattern=ONE_SHOT|customerMobilePhone=null|mandateAuthentMethod=null|mandateUsage=null|transactionActors=null|mandateId=null|captureLimitDate=null|dccStatus=null|dccResponseCode=null|dccAmount=null|dccCurrencyCode=null|dccExchangeRate=null|dccExchangeRateValidity=null|dccProvider=null|statementReference=null|panEntryMode=null|walletType=null|holderAuthentMethod=null|holderAuthentProgram=null|paymentMeanId=null|instalmentNumber=null|instalmentDatesList=null|instalmentTransactionReferencesList=null|instalmentAmountsList=null|settlementMode=null|mandateCertificationType=null|valueDate=null|creditorId=null|acquirerResponseIdentifier=null|acquirerResponseMessage=null|paymentMeanTradingName=null|additionalAuthorisationNumber=null|issuerWalletInformation=null|s10TransactionId=896|s10TransactionIdDate=20221121|preAuthenticationColor=null|preAuthenticationInfo=null|preAuthenticationProfile=null|preAuthenticationThreshold=null|preAuthenticationValue=null|invoiceReference=null|s10transactionIdsList=null|cardProductCode=null|cardProductName=null|cardProductProfile=null|issuerCode=null|issuerCountryCode=null|acquirerNativeResponseCode=null|settlementModeComplement=null|preAuthorisationProfile=null|preAuthorisationProfileValue=null|preAuthorisationRuleResultList=null|preAuthenticationProfileValue=null|preAuthenticationRuleResultList=null|paymentMeanBrandSelectionStatus=null|transactionPlatform=PROD|avsAddressResponseCode=null|avsPostcodeResponseCode=null|customerCompanyName=null|customerBusinessName=null|customerLegalId=null|customerPositionOccupied=null|paymentAttemptNumber=1|holderContactEmail=null|installmentIntermediateServiceProviderOperationIdsList=null|holderAuthentType=null|acquirerContractNumber=null|secureReference=null|authentExemptionReasonList=null|paymentAccountReference=null|schemeTransactionIdentifier=null|guaranteeLimitDateTime=null|paymentMeanDataProvider=null"); + responseParameters.put("Seal", "14aba0bb3002a8fa88fc3e8fb1b61e2af103009d9489b0685f7f235590b00580"); + PaypageResponse paypageResponse = decodeClient.decodeResponse(PaypageResponse.class, responseParameters); assertNotNull(paypageResponse.getData(), "Data field is empty!"); assertNotNull(paypageResponse.getData().getResponseCode()); } @@ -73,8 +134,8 @@ void decodeResponse_with_cancelled_request() throws Exception { void decodeResponse_with_refused_request() throws Exception { responseParameters.put("Data", "captureDay=0|captureMode=AUTHOR_CAPTURE|currencyCode=978|merchantId=002001000000001|orderChannel=INTERNET|responseCode=05|transactionDateTime=2018-02-06T07:50:34+01:00|transactionReference=8bd59312ff4|keyVersion=1|amount=2|guaranteeIndicator=N|panExpiryDate=201803|paymentMeanBrand=VISA|paymentMeanType=CARD|customerIpAddress=194.78.195.168|maskedPan=4500#############01|holderAuthentRelegation=N|holderAuthentStatus=3D_FAILURE|tokenPan=g011040a730424d1ba6|transactionOrigin=INTERNET|paymentPattern=ONE_SHOT"); responseParameters.put("Seal", "e8c5bf4551ec60ce9b8ece6a98bdb1b5fde511539a391bc4ba314aaeac93b5be"); - PaypageResponse paypageResponse = paypageClient.decodeResponse(responseParameters); + PaypageResponse paypageResponse = decodeClient.decodeResponse(PaypageResponse.class, responseParameters); assertNotNull(paypageResponse.getData(), "Data field is empty!"); assertNotNull(paypageResponse.getData().getResponseCode()); } -} \ No newline at end of file +} diff --git a/payment-sdk-common/src/test/java/com/worldline/sips/util/SealCalculatorTest.java b/payment-sdk/src/test/java/com/worldline/sips/api/SealCalculatorTest.java similarity index 56% rename from payment-sdk-common/src/test/java/com/worldline/sips/util/SealCalculatorTest.java rename to payment-sdk/src/test/java/com/worldline/sips/api/SealCalculatorTest.java index 8797290..44c1a1d 100644 --- a/payment-sdk-common/src/test/java/com/worldline/sips/util/SealCalculatorTest.java +++ b/payment-sdk/src/test/java/com/worldline/sips/api/SealCalculatorTest.java @@ -1,22 +1,30 @@ -package com.worldline.sips.util; +package com.worldline.sips.api; -import com.worldline.sips.exception.SealCalculationException; -import com.worldline.sips.model.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import com.worldline.sips.exception.SealCalculationException; +import com.worldline.sips.model.NamedPaymentMeanBrand; +import com.worldline.sips.model.data.Address; +import com.worldline.sips.model.data.Currency; +import com.worldline.sips.model.data.CustomerAddress; +import com.worldline.sips.model.data.CustomerContact; +import com.worldline.sips.model.data.RedirectionStatusCode; +import com.worldline.sips.model.request.PaymentRequest; +import com.worldline.sips.model.response.InitializationResponse; +import com.worldline.sips.security.SealCalculator; import java.net.MalformedURLException; import java.net.URL; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class SealCalculatorTest { private static final String ENCODED_REQUEST_SEAL = "198e5f278e3f8548e174e84492953c4871732278b7e2aa2cbf20bb1ab85914ea"; - private static final String ENCODED_RESPONSE_SEAL = "dd6eb8dd6c951b1ddc1af121007aaabe8ad4fda1d15ce386cfa455821d602025"; + private static final String ENCODED_RESPONSE_SEAL = "c3eb508b0d419e3bd2bb1a0416c2a48585fa6d683aa8d1a3dfba8ee877079f9f"; private static final String DEMO_KEY = "superSafeSecretKey"; private static final String RESPONSE_DEMO_KEY = "002001000000002_KEY1"; + private PaymentRequest paymentRequest; @BeforeEach @@ -31,7 +39,7 @@ void setUp() throws Exception { @Test void getSealString() { String actual = SealCalculator.getSealString(paymentRequest); - String expected = "10http://test.comcustomerIdIR_WS_2.19customCSS.css"; + String expected = "10http://test.comcustomerIdIR_WS_2.46customCSS.css"; assertEquals(expected, actual, "Sealstring is incorrect!"); } @@ -39,16 +47,16 @@ void getSealString() { void getSealString_with_Currency() { paymentRequest.setCurrencyCode(Currency.EUR); String actual = SealCalculator.getSealString(paymentRequest); - String expected = "10http://test.com978customerIdIR_WS_2.19customCSS.css"; + String expected = "10http://test.com978customerIdIR_WS_2.46customCSS.css"; assertEquals(expected, actual, "Sealstring is incorrect!"); } @Test void getSealString_with_list() { - paymentRequest.getPaymentMeanBrandList().add(PaymentMeanBrand.VISA); - paymentRequest.getPaymentMeanBrandList().add(PaymentMeanBrand.BCMC); + paymentRequest.getPaymentMeanBrandList().add(NamedPaymentMeanBrand.VISA); + paymentRequest.getPaymentMeanBrandList().add(NamedPaymentMeanBrand.BCMC); String actual = SealCalculator.getSealString(paymentRequest); - String expected = "10http://test.comcustomerIdIR_WS_2.19BCMCVISAcustomCSS.css"; + String expected = "10http://test.comcustomerIdIR_WS_2.46BCMCVISAcustomCSS.css"; assertEquals(expected, actual, "Sealstring is incorrect!"); } @@ -70,7 +78,7 @@ void getSealString_with_Container() { paymentRequest.setCustomerContact(customerContact); String actual = SealCalculator.getSealString(paymentRequest); - String expected = "10http://test.comcustomerBusinessNamecustomerCityfirstNamelastNamecustomerIddeliveryCompanydeliveryZipcodeIR_WS_2.19customCSS.css"; + String expected = "10http://test.comcustomerBusinessNamecustomerCityfirstNamelastNamecustomerIddeliveryCompanydeliveryZipcodeIR_WS_2.46customCSS.css"; assertEquals(expected, actual, "Sealstring is incorrect!"); } @@ -78,7 +86,7 @@ void getSealString_with_Container() { void getSealString_with_ignoredField() { paymentRequest.setKeyVersion(200); String actual = SealCalculator.getSealString(paymentRequest); - String expected = "10http://test.comcustomerIdIR_WS_2.19customCSS.css"; + String expected = "10http://test.comcustomerIdIR_WS_2.46customCSS.css"; assertEquals(expected, actual, "Sealstring is incorrect!"); } @@ -104,11 +112,10 @@ void calculate_response_seal() throws MalformedURLException, SealCalculationExce initializationResponse.setRedirectionStatusCode(RedirectionStatusCode.TRANSACTION_INITIALIZED); initializationResponse.setRedirectionStatusMessage("INITIALISATION REQUEST ACCEPTED"); initializationResponse.setRedirectionVersion("IR_WS_2.0"); - initializationResponse.setRedirectionUrl(new URL("https://payment-web.simu.sips-services.com/payment")); - initializationResponse.setRedirectionData("FTEx33MrE9Nc0gvtIS0aydJngXH8uuirg3ZpFD_KRM22C9e3IFDdBdSZB3kdQwcyIjWhKpM9mHYTCaCCy7Vm0YWrLscP-TJ895c2GXsTKlwEkO_VEU-2j2VGVcbYAKE9kcoV6jnVU5OJE1AMXEXnm1AkMrm3riB35p7x-WUdnaU0oKIhsit2M4_mci3fWH9WNTqo-A07qPd9-5zRKCr2F91Z8R4HogmLYdx_xh6BOyXKp693Smrq-2H64A2BOC6n89JQT-e9GNBM_up1YMa-vvS3-b6bMoZ01ngO7n2-NttxvXn78XEDDDVQfYZGPrpjywvDhGTfhBun1mdbSfzGPOwd6dcLre4Nguht3JCsT27EyLOxGEjCDwMjKJ0Gb2toChEMRv7TtX2SlXGMuyUr24oToHGbdt6zOm3q1R7XnCqnVYT_YNnNPf6lppQCW81FVvdX5zfbW65tQzja0CFC2ss-kyT0v4owm8LLNMy2rEU2JYnQQrT2qhQYf8lGxL2qkC0S1TpPLRzK-ry3mkEcBgwxFKbB2DX4kRamx4n6v_Yke2PS1PUPoWbdruRkK1mDJAJhSorV4O3LeeM1B3LkuPNU2xZw__I55zaOyIy2sWz52Su6gapGcsw8qTNOhcOwQi1sYaSGviCfKF75zC-GCjbHGoyHfO5RBswwmqBMNbVc7l2FIAw8ZDwn18E07pDWNeMwaeWYrYhR_n1BfhaW03H5TojvjnEMMSfd11nab0PjFE5zWB3r--OadfE7m-NqHL70WKLPYNfvKdzXzoI3ejc3SmWycXMpJgENZAFVzy4MojbVby3prA4IXQ_xSEisoWoGJb1Kg2LxEUih9Jqpi9vinH_nqt29lHFKnWSIuhT8UdsBYvFRGYyrbxPOOz1eNBqvHO49eI2hpKeL280_eR4HmLPiiDASog3BgDFB2_Ed6upm_N1PUEdQ1xmwXkiL7fqnaFABXySxsTVhxyRgXGSAmA0gmeYwFf7bGIKUc-FPwqWewDxRnIfnz6Eh3qDG41xEr5URpc0LCzzo5XjFt5h7wmp3FftmSai4rCFIM6B4Jd9lljeXgAMayzfig77_S_JMtEDw6oqzbX6QpBsKnfnrAtxk26LL07qB5Yw9qj-NEbWf28GOPN9--xY5AeUbya5nyNQiT-3TyebxnjUysTrTuq1DptPflIrUuIwAewT6K1I-XRYDpgdTRwWFzTIi2kItzGcbozxMJi0S6eJnOQNb9zNSrUBysDlSGVct1UsHqYQO56_-uyAwZ7lv23WIZdgbUaGELoPsba8Jg9rzR-aZ0zEe9owORSbAh5qkk0ByvOQniLqGYn36AJbKiSrSjRG1n9vBjjzffvsWOQHlG_tPV6WPVaXvanjaHrg_v2tjtqWPCUCRFg6seM4KiZ3G5-WPkPX0LW19iu4ypazFsfgElIfF7--XuN2wnrbf5tvPxpk-EHACkcFUQ7ozBw5xjRG1n9vBjjzffvsWOQHlGzGBbU3BAx_KVN_SQuZCNZ66O6O6FdOnZF_dC9vfuhZx5livIUUGTQOiFt4JQv_tJ3gl32WWN5eAAxrLN-KDvv_z_69h-FQfvm0DChFKn5h9Jb54QR0qamngPqTNqRQxNcmK3onHxmG7IIS9Gsltfie7sc4Lt9p_hqLAC28DDefH03yQ0l15QSQSA7t8x3rtGuflj5D19C1tfYruMqWsxbF_OuasNvoqSnJcHI_HyMMnf7eGnYHEcQk4vutjrpZhcH1TRZ3nQYrrxzId10uYeGeB7NXgInfbYearASIu2_vnMuJj1F84sjXcMM29qxh5KvOAaCle_EK7_SCamPY6SzkmTCyJprTaIBkhUUjUDdem4rN_XaLIav0orq8E4IwU740RtZ_bwY483377FjkB5RsKKPYjzvh_RlAEOH7vo0AIf7eGnYHEcQk4vutjrpZhcI0RtZ_bwY483377FjkB5Rv07-b-FhxhFkrKQcO0J0LIaKn33u4Hp88PYNHX40sIyNK3sqPZPiYGDU7q6T76kxla1JZwz-5wSgABK-wV16UlA1v3M1KtQHKwOVIZVy3VSwephA7nr_67IDBnuW_bdYjqqqfJtu70VoEtZOvxckajWtSWcM_ucEoAASvsFdelJcaipDQGiSLyw4gWeiGVvJAJHG6sS8x21XuBIuCbQ8l9Orcf_-QEVEHjGdswxxpSUovuFIRWVkRniMqau0PF2uF2Vi5vBX5AQYvblDS0I-Nk5mOOxtF3rdTMKSdmHaylZGleYPX4zPU6HmEV4ipdMcHx_vB89SS_gwRtQ9PWLJh3HJwvIO2GJs6lrufoKpeHVk9jne_ybEoDL1qOawB6ESQK5q5UXSwdR3YaB8M0qw-UPPibFlDSsbVF-zNvfbA8IBQL83Jj6EwBHBwT0RHvrvQ9-0nLg03cckYVc3IJh-dMok6Z5Ga3fsi_953xGMoOX4_z-gTn5pXhDYgI-0g_CKeAtgLeNsxz-PlTwW_U_kfW6Q3V7HDgOi-QZxxNSTMSkQpAdC3JcbHIGuEHVGxs5zWWT5tmsYkXbS11VEduPVXb9NpuUzUB5GLL8W3y9w3hTgLI1bKcCSsF4b3mgLEwNNGbEzYudvCLn3MP8g5BKQ-GlMMZU93_ANjsgNXUNK3j0SHC1NaycxVaWqmVP9uWp1U"); - + initializationResponse.setRedirectionUrl(new URL("https://payment-gateway.net/")); + initializationResponse.setRedirectionData("4AgbsrffvPgzDghQysbOJIZBJTZsk1KNlTmoOCtSORkMfzQgSR5OEw0gAE2bAAFbHuYQXuBmiEfuwD81QlmDInPmanHWkKNA3X3jUbC8Jh9oPTfgoPO4PNo20aNt6yb5z8cDOX8J_rNvwzfJetyCxEVrB93g9YRFX4n3mM85FC5o"); String sealString = SealCalculator.getSealString(initializationResponse); String actual = SealCalculator.calculate(sealString, RESPONSE_DEMO_KEY); assertEquals(ENCODED_RESPONSE_SEAL, actual, "Encoded seal is incorrect!"); } -} \ No newline at end of file +} diff --git a/payment-sdk-common/build.gradle b/sdk-common/build.gradle similarity index 79% rename from payment-sdk-common/build.gradle rename to sdk-common/build.gradle index e2ec979..3e5bdac 100644 --- a/payment-sdk-common/build.gradle +++ b/sdk-common/build.gradle @@ -3,4 +3,5 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.1' implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.2' } diff --git a/sdk-common/src/main/java/com/worldline/sips/SIPSRequest.java b/sdk-common/src/main/java/com/worldline/sips/SIPSRequest.java new file mode 100644 index 0000000..eedcc56 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/SIPSRequest.java @@ -0,0 +1,89 @@ +package com.worldline.sips; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.worldline.sips.exception.SealCalculationException; +import com.worldline.sips.security.SealCalculator; +import com.worldline.sips.security.Sealable; + +/** + * An abstract object containing the basis of every SIPS requests + * + * @param the type of the response that should be returned by sending this request to SIPS + * @see Sealable + * @see SIPSResponse + */ +@JsonPropertyOrder(alphabetic = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public abstract class SIPSRequest implements Sealable { + private final String endpoint; + private String seal; + private String merchantId; + private Integer keyVersion; + private String sealAlgorithm = "HMAC-SHA-256"; + + /** + * @param endpoint the http endpoint targeted by this request + */ + public SIPSRequest(String endpoint) { + this.endpoint = endpoint; + } + + /** + * Compute the seal of this request. + * + * @param secretKey the secret key that will be used to generate this request's seal + * @throws SealCalculationException when seal calculation fails, see inner exception for details. + */ + public void calculateSeal(String secretKey) throws SealCalculationException { + this.seal = SealCalculator.calculate(SealCalculator.getSealString(this), secretKey); + } + + /** + * Should be provided by subclasses to permit the deserialization of the response + * + * @return the response's type + */ + @JsonIgnore + public abstract Class getResponseType(); + + @JsonIgnore + final Class getRealType() { + return this.getClass(); + } + + @JsonIgnore + public String getEndpoint() { + return endpoint; + } + + public String getSealAlgorithm() { + return sealAlgorithm; + } + + public void setSealAlgorithm(String sealAlgorithm) { + this.sealAlgorithm = sealAlgorithm; + } + + public String getMerchantId() { + return merchantId; + } + + public void setMerchantId(String merchantId) { + this.merchantId = merchantId; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getSeal() { + return seal; + } + +} diff --git a/sdk-common/src/main/java/com/worldline/sips/SIPSResponse.java b/sdk-common/src/main/java/com/worldline/sips/SIPSResponse.java new file mode 100644 index 0000000..db5d8f6 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/SIPSResponse.java @@ -0,0 +1,52 @@ +package com.worldline.sips; + +import com.worldline.sips.exception.IncorrectSealException; +import com.worldline.sips.exception.SealCalculationException; +import com.worldline.sips.security.SealCalculator; +import com.worldline.sips.security.Sealable; +import org.apache.commons.lang3.StringUtils; + +/** + * An abstract response from SIPS that contains the seal mechanism logic + */ +public abstract class SIPSResponse implements Sealable { + private String seal; + + /** + * Check that this object's data has not been tempered and is conformed to its {@link #seal} + * + * @param secretKey the secret key used to create this response (it is the same as the one use to create the request + * that induced this response + * @throws SealCalculationException if a seal calculation failed + * @throws IncorrectSealException if the response's seal is incorrect + */ + public void verifySeal(String secretKey) throws IncorrectSealException, SealCalculationException { + if (seal != null) { + String sealString = SealCalculator.getSealString(this); + String correctSeal = SealCalculator.calculate(sealString, secretKey); + if (! StringUtils.equals(correctSeal, seal)) { + throw new IncorrectSealException("The response has been tampered with!"); + } + } + } + + /** + * @return this response's seal + * @see Sealable + */ + public String getSeal() { + return seal; + } + + /** + * Set the seal of this response + * + * @param seal the new seal + * @deprecated /!\ Using this method is discouraged and could lead to security false positives; + * do not use it if you are not sure of what you're doing + */ + @Deprecated + public void setSeal(String seal) { + this.seal = seal; + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/SipsClient.java b/sdk-common/src/main/java/com/worldline/sips/SipsClient.java new file mode 100644 index 0000000..165f3bf --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/SipsClient.java @@ -0,0 +1,263 @@ +package com.worldline.sips; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.worldline.sips.exception.IncorrectProxyConfException; +import com.worldline.sips.exception.IncorrectSealException; +import com.worldline.sips.exception.InvalidEnvironmentException; +import com.worldline.sips.exception.InvalidKeyException; +import com.worldline.sips.exception.InvalidMerchantException; +import com.worldline.sips.exception.SealCalculationException; +import com.worldline.sips.exception.SipsException; +import com.worldline.sips.exception.SipsRequestException; +import com.worldline.sips.model.SipsEnvironment; +import com.worldline.sips.util.ObjectMapperHolder; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Interact with SIPS APIs. + */ +public class SipsClient { + + private final SipsEnvironment environment; + private final boolean proxyEnabled; + private final String proxyHost; + private final Integer proxyPort; + private final String merchantId; + private final Integer keyVersion; + private final String secretKey; + + /** + * Construct a new instance of the client for a given {@link SipsEnvironment}. + * + * @param environment the API environment to connect to. + * @param merchantId the merchant's ID. + * @param keyVersion the version of the secret key to use. + * @param secretKey the merchant's secret key. + * @throws IncorrectProxyConfException when the proxy configuration is incorrect. + * @throws InvalidEnvironmentException when an unknown environment is specified. + * @throws InvalidKeyException when the key version is null, or a key is blank, empty or null. + * @throws InvalidMerchantException when the key version is null, or a key is blank, empty or null. + */ + public SipsClient(SipsEnvironment environment, String merchantId, Integer keyVersion, String secretKey) + throws InvalidEnvironmentException, IncorrectProxyConfException, InvalidMerchantException, InvalidKeyException { + this(environment, merchantId, keyVersion, secretKey, false, null, null); + } + + /** + * Construct a new instance of the client for a given {@link SipsEnvironment} with a defined proxy. + * + * @param environment the API environment to connect to. + * @param merchantId the merchant's ID. + * @param keyVersion the version of the secret key to use. + * @param secretKey the merchant's secret key. + * @param proxyEnabled true if a proxy configuration is provided. + * @param proxyHost the host of the proxy; if proxyEnabled is true this should net be blank, empty or null otherwise it should be null. + * @param proxyPort the port of the proxy; if proxyEnabled is true this should net be blank, empty or null otherwise it should be null. + * @throws IncorrectProxyConfException when the proxy configuration is incorrect. + * @throws InvalidEnvironmentException when an unknown environment is specified. + * @throws InvalidKeyException when the key version is null, or a key is blank, empty or null. + * @throws InvalidMerchantException when the key version is null, or a key is blank, empty or null. + */ + public SipsClient(SipsEnvironment environment, String merchantId, Integer keyVersion, String secretKey, + boolean proxyEnabled, String proxyHost, Integer proxyPort) + throws InvalidEnvironmentException, IncorrectProxyConfException, InvalidMerchantException, InvalidKeyException { + if (environment == null) { + throw new InvalidEnvironmentException("Invalid environment specified!"); + } + + if (proxyEnabled) { + if (StringUtils.isBlank(proxyHost) || proxyPort == null) { + throw new IncorrectProxyConfException("ProxyEnabled is true but proxyHost or proxyPort not filled"); + } + } + + if (StringUtils.isBlank(merchantId)) { + throw new InvalidMerchantException("Invalid merchant ID specified!"); + } + + if (keyVersion == null) { + throw new InvalidKeyException("Invalid key version specified!"); + } + + if (StringUtils.isBlank(secretKey)) { + throw new InvalidKeyException("Invalid key specified!"); + } + this.environment = environment; + this.proxyEnabled = proxyEnabled; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.merchantId = merchantId; + this.keyVersion = keyVersion; + this.secretKey = secretKey; + } + + /** + * Decode a SIPS response object from a map of parameters. + * + * @param responseClass the type of the response to construct + * @param parameters the content of the received request, mapped as key-value pairs. + * @param secretKey the secret key used to create the request that induced this response + * @param the type of the response + * @return The constructed response. + * @throws IncorrectSealException - If the response has been tampered with. + * @throws IllegalArgumentException – If conversion fails due to incompatible type; if so, root cause will contain underlying + * checked exception data binding functionality threw + */ + public static Response decodeResponse(Class responseClass, + Map parameters, String secretKey) throws IncorrectSealException, IllegalArgumentException { + verifySeal(parameters.get("Data"), parameters.get("Seal"), secretKey); + return ObjectMapperHolder.INSTANCE.get().copy() + .convertValue(parameters, responseClass); + } + + /** + * Decode a SIPS response object from a json string. + * + * @param responseClass the type of the response to construct + * @param rawJson the content of the received request as raw JSON. + * @param the type of the response + * @return The constructed response. + * @throws IncorrectSealException - If the response has been tampered with. + * @throws IllegalArgumentException – If conversion fails due to incompatible type; if so, root cause will contain underlying + * checked exception data binding functionality threw + */ + public Response decodeResponse(Class responseClass, + String rawJson) throws IncorrectSealException, IllegalArgumentException { + return decodeResponse(responseClass, rawJson, secretKey); + } + /** + * Decode a SIPS response object from a json string. + * + * @param responseClass the type of the response to construct + * @param rawJson the content of the received request as raw JSON. + * @param secretKey the secret key used to create the request that induced this response + * @param the type of the response + * @return The constructed response. + * @throws IncorrectSealException - If the response has been tampered with. + * @throws IllegalArgumentException – If conversion fails due to incompatible type; if so, root cause will contain underlying + * checked exception data binding functionality threw + */ + public static Response decodeResponse(Class responseClass, + String rawJson, String secretKey) throws IncorrectSealException, IllegalArgumentException { + ObjectMapper copy = ObjectMapperHolder.INSTANCE.get().copy(); + JsonNode jsonObj; + try { + jsonObj = copy.readTree(rawJson); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + verifySeal(jsonObj.get("Data").textValue(), jsonObj.get("Seal").textValue(), secretKey); + return copy.convertValue(jsonObj, responseClass); + } + + /** + * Verify the seal of a sips response. To avoid tampered data, + * the seal for the received response should always be verified before returning the object to the user. + * + * @param data the received response's Data attribute + * @param seal the received response's Seal attribute + * @throws IncorrectSealException when the received seal is different from the one calculated + */ + private static void verifySeal(String data, String seal, String secretKey) throws IncorrectSealException { + String correctSeal = DigestUtils.sha256Hex(data + secretKey); + if (! StringUtils.equals(correctSeal, seal)) { + throw new IncorrectSealException("The payment page response has been tampered with!"); + } + } + + /** + * Send a request to sips and get the response synchronously. + *

+ * The seal of the request is calculated, the request is sent, the response is received and its seal is checked. + * + * @param request the request that will be sent to SIPS + * @param the type of the response + * @return a response object mapping the response sent by Sips + * @throws SipsRequestException if an error occurred while serializing or sending the request + * @throws SipsException if an error occurred while receiving or deserializing the response + * @throws SealCalculationException if a seal calculation failed + * @throws IncorrectSealException if the response's seal is incorrect + */ + public Response send(SIPSRequest request) + throws SipsRequestException, SipsException, SealCalculationException, IncorrectSealException { + String fullPath = environment.getUrl() + "/" + request.getEndpoint(); + try { + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + if (this.proxyEnabled) { + HttpHost httpHost = new HttpHost(this.proxyHost, this.proxyPort); + httpClientBuilder.setProxy(httpHost); + } + CloseableHttpClient httpClient = httpClientBuilder.build(); + request.setMerchantId(merchantId); + request.setKeyVersion(keyVersion); + request.calculateSeal(secretKey); + StringEntity requestEntity = new StringEntity( + ObjectMapperHolder.INSTANCE.get().writerFor(request.getRealType()) + .writeValueAsString(request), + ContentType.APPLICATION_JSON); + + HttpPost postMethod = new HttpPost(new URI(fullPath)); + postMethod.setEntity(requestEntity); + + CloseableHttpResponse rawResponse = httpClient.execute(postMethod); + Response response = + ObjectMapperHolder.INSTANCE.get().readerFor(request.getResponseType()) + .readValue(EntityUtils.toString(rawResponse.getEntity())); + + response.verifySeal(secretKey); + return response; + } catch (JsonParseException | JsonMappingException e) { + throw new SipsRequestException("Exception while parsing request!", e); + } catch (IOException | ParseException e) { + throw new SipsException("Exception while processing response from server!", e); + } catch (URISyntaxException e) { + throw new SipsRequestException("Invalid endpoint: '" + fullPath + "' !", e); + } + } + + /** + * Decode a SIPS response object from a map of parameters. + * + * @param responseClass the type of the response to construct + * @param parameters the content of the received request, mapped as key-value pairs. + * @param the type of the response + * @return The constructed response. + * @throws IncorrectSealException - If the response has been tampered with. + * @throws IllegalArgumentException – If conversion fails due to incompatible type; if so, root cause will contain underlying + * checked exception data binding functionality threw + */ + public Response decodeResponse(Class responseClass, Map parameters) + throws IncorrectSealException, IllegalArgumentException { + return decodeResponse(responseClass, parameters, secretKey); + } + + /** + * Verify the seal of a sips response. To avoid tampered responses, + * the seal for the received response should always be verified before returning the object to the user. + * + * @param response the received response upon initialization + * @throws IncorrectSealException when the received seal is different from the one calculated + * @throws SealCalculationException when seal calculation fails, see inner exception for details. + * @see SIPSResponse#verifySeal(String) = identical + */ + private void verifySeal(SIPSResponse response) throws IncorrectSealException, SealCalculationException { + response.verifySeal(secretKey); + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/exception/IncorrectProxyConfException.java b/sdk-common/src/main/java/com/worldline/sips/exception/IncorrectProxyConfException.java new file mode 100644 index 0000000..b577412 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/exception/IncorrectProxyConfException.java @@ -0,0 +1,9 @@ +package com.worldline.sips.exception; + +public class IncorrectProxyConfException extends Exception { + + public IncorrectProxyConfException(String message) { + super(message); + } + +} diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectSealException.java b/sdk-common/src/main/java/com/worldline/sips/exception/IncorrectSealException.java similarity index 77% rename from payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectSealException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/IncorrectSealException.java index fdf5468..7a95910 100644 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/IncorrectSealException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/IncorrectSealException.java @@ -1,8 +1,9 @@ -package com.worldline.sips.api.exception; +package com.worldline.sips.exception; public class IncorrectSealException extends Exception { public IncorrectSealException(String message) { super(message); } + } diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidEnvironmentException.java b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidEnvironmentException.java similarity index 78% rename from payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidEnvironmentException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/InvalidEnvironmentException.java index 9bf973e..1072375 100644 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidEnvironmentException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidEnvironmentException.java @@ -1,8 +1,9 @@ -package com.worldline.sips.api.exception; +package com.worldline.sips.exception; public class InvalidEnvironmentException extends Exception { public InvalidEnvironmentException(String message) { super(message); } + } diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidKeyException.java b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidKeyException.java similarity index 76% rename from payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidKeyException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/InvalidKeyException.java index 62a1d03..820afb0 100644 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidKeyException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidKeyException.java @@ -1,8 +1,9 @@ -package com.worldline.sips.api.exception; +package com.worldline.sips.exception; public class InvalidKeyException extends Exception { public InvalidKeyException(String message) { super(message); } + } diff --git a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidMerchantException.java b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidMerchantException.java similarity index 77% rename from payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidMerchantException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/InvalidMerchantException.java index 1d4bdf5..58fe480 100644 --- a/payment-sdk/src/main/java/com/worldline/sips/api/exception/InvalidMerchantException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/InvalidMerchantException.java @@ -1,8 +1,10 @@ -package com.worldline.sips.api.exception; +package com.worldline.sips.exception; public class InvalidMerchantException extends Exception { public InvalidMerchantException(String message) { super(message); } + } + diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java b/sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java similarity index 99% rename from payment-sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java index 5b33148..98d15ef 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/SealCalculationException.java @@ -1,7 +1,9 @@ package com.worldline.sips.exception; public class SealCalculationException extends Exception { + public SealCalculationException(String message, Throwable cause) { super(message, cause); } + } diff --git a/sdk-common/src/main/java/com/worldline/sips/exception/SipsException.java b/sdk-common/src/main/java/com/worldline/sips/exception/SipsException.java new file mode 100644 index 0000000..1686678 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/exception/SipsException.java @@ -0,0 +1,9 @@ +package com.worldline.sips.exception; + +public class SipsException extends Exception { + + public SipsException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/sdk-common/src/main/java/com/worldline/sips/exception/SipsRequestException.java b/sdk-common/src/main/java/com/worldline/sips/exception/SipsRequestException.java new file mode 100644 index 0000000..12e9b87 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/exception/SipsRequestException.java @@ -0,0 +1,9 @@ +package com.worldline.sips.exception; + +public class SipsRequestException extends Exception { + + public SipsRequestException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java b/sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java index eb16a1b..41c56bf 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/UnknownStatusException.java @@ -1,7 +1,9 @@ package com.worldline.sips.exception; public class UnknownStatusException extends Exception { + public UnknownStatusException(String message) { super(message); } + } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java b/sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java index 1706e03..0cee7ed 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedCurrencyException.java @@ -1,7 +1,9 @@ package com.worldline.sips.exception; public class UnsupportedCurrencyException extends Exception { + public UnsupportedCurrencyException(String message) { super(message); } + } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java b/sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java similarity index 98% rename from payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java rename to sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java index 499e908..a2c31f8 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java +++ b/sdk-common/src/main/java/com/worldline/sips/exception/UnsupportedLanguageException.java @@ -1,7 +1,9 @@ package com.worldline.sips.exception; public class UnsupportedLanguageException extends Exception { + public UnsupportedLanguageException(String message) { super(message); } + } diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalFieldComparator.java b/sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalFieldComparator.java similarity index 100% rename from payment-sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalFieldComparator.java rename to sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalFieldComparator.java diff --git a/sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java b/sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java new file mode 100644 index 0000000..87bd153 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/helper/AlphabeticalReflectionToStringBuilder.java @@ -0,0 +1,17 @@ +package com.worldline.sips.helper; + +import org.apache.commons.lang3.builder.ToStringStyle; + +public class AlphabeticalReflectionToStringBuilder extends SortedReflectionToStringBuilder { + + private AlphabeticalReflectionToStringBuilder(Object object, ToStringStyle style) { + super(object, style); + setComparator(new AlphabeticalFieldComparator()); + } + + public static AlphabeticalReflectionToStringBuilder newInstance(Object object, SealStringStyle sealStringStyle) { + AlphabeticalReflectionToStringBuilder res = new AlphabeticalReflectionToStringBuilder(object, sealStringStyle); + sealStringStyle.setReflectionToStringBuilder(res); + return res; + } +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/helper/BooleanDeserializer.java b/sdk-common/src/main/java/com/worldline/sips/helper/BooleanDeserializer.java similarity index 100% rename from payment-sdk-common/src/main/java/com/worldline/sips/helper/BooleanDeserializer.java rename to sdk-common/src/main/java/com/worldline/sips/helper/BooleanDeserializer.java diff --git a/sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java b/sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java new file mode 100644 index 0000000..c644a70 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/helper/SealStringStyle.java @@ -0,0 +1,64 @@ +package com.worldline.sips.helper; + +import java.time.YearMonth; +import java.util.Arrays; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.RecursiveToStringStyle; + +/** + * A {@link RecursiveToStringStyle} parametrized to the seal format + * + * @see com.worldline.sips.security.Sealable + */ +public final class SealStringStyle extends RecursiveToStringStyle { + + private SortedReflectionToStringBuilder reflectionToStringBuilder; + + public void setReflectionToStringBuilder(SortedReflectionToStringBuilder reflectionToStringBuilder) { + this.reflectionToStringBuilder = reflectionToStringBuilder; + } + + public SealStringStyle() { + super(); + setUseClassName(false); + setUseIdentityHashCode(false); + setUseFieldNames(false); + setNullText(StringUtils.EMPTY); + setContentStart(StringUtils.EMPTY); + setContentEnd(StringUtils.EMPTY); + setFieldSeparator(StringUtils.EMPTY); + setArrayStart(StringUtils.EMPTY); + setArrayEnd(StringUtils.EMPTY); + setArraySeparator(StringUtils.EMPTY); + } + + @Override + public void appendDetail(StringBuffer buffer, String fieldName, Object value) { + if (! ClassUtils.isPrimitiveWrapper(value.getClass()) && + ! String.class.equals(value.getClass()) && + accept(value.getClass())) { + buffer.append(reflectionToStringBuilder.initFrom(value).toString()); + } else { + if (value instanceof YearMonth) { + value = ((YearMonth) value).toString().replace("-", ""); + } + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(StringBuffer buffer, String fieldName, Object[] array) { + try { + Arrays.sort(array); + } catch (ClassCastException ignored) { + System.out.println("test"); + } + super.appendDetail(buffer, fieldName, array); + } + + @Override + protected boolean accept(Class clazz) { + return ! clazz.isEnum() && clazz.getPackage().getName().startsWith("com.worldline"); + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java b/sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java new file mode 100644 index 0000000..f349913 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/helper/SortedReflectionToStringBuilder.java @@ -0,0 +1,95 @@ +package com.worldline.sips.helper; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.commons.lang3.builder.ToStringSummary; + +public class SortedReflectionToStringBuilder extends ReflectionToStringBuilder { + + private Comparator comparator; + private final Map, Function> customSerializers = new HashMap<>(); + + public SortedReflectionToStringBuilder(Object object, ToStringStyle style) { + super(object, style); + } + + /** + * Get all the fields of a given type (include private and inherited fields from the class hierarchy) + * + * @param type + * @return a list containing all the fields of the given type + */ + private static List getAllFields(Class type) { + List fields = new ArrayList<>(); + List> temp = new ArrayList<>(); + for (Class c = type; c != null; c = c.getSuperclass()) { + temp.add(Arrays.asList(c.getDeclaredFields())); + } + for (int i = temp.size() - 1; i >= 0; i--) { + fields.addAll(temp.get(i)); + } + return fields; + } + + public void setComparator(Comparator comparator) { + this.comparator = comparator; + } + + @Override + public String toString() { + if (this.getObject() == null) { + return super.toString(); + } + Class clazz = this.getObject().getClass(); + List allFields = getAllFields(clazz); + if (comparator != null) { + allFields.sort(comparator); + } + for (final Field field : allFields) { + final String fieldName = field.getName(); + if (this.accept(field)) { + try { + field.setAccessible(true); + // Warning: Field.get(Object) creates wrappers objects + // for primitive types. + Object fieldValue = this.getValue(field); + Function serializer = customSerializers.get(field.getType()); + if (serializer != null) { + fieldValue = serializer.apply(fieldValue); + } + if (! isExcludeNullValues() || fieldValue != null) { + this.append(fieldName, fieldValue, ! field.isAnnotationPresent(ToStringSummary.class)); + } + } catch (final IllegalAccessException ex) { + //this can't happen. Would get a Security exception + // instead + //throw a runtime exception in case the impossible + // happens. + throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage()); + } + } + } + getStyle().appendEnd(this.getStringBuffer(), this.getObject()); + return this.getStringBuffer().toString(); + } + + public void addSerializer(Class clazz, Function serializer) { + customSerializers.put(clazz, serializer); + } + public SortedReflectionToStringBuilder initFrom(Object value) { + SortedReflectionToStringBuilder copy = new SortedReflectionToStringBuilder(value, getStyle()); + copy.comparator = this.comparator; + copy.customSerializers.putAll(customSerializers); + return copy; + } +} + + diff --git a/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanBrand.java b/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanBrand.java new file mode 100644 index 0000000..cadcaee --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanBrand.java @@ -0,0 +1,96 @@ +package com.worldline.sips.model; + +import static com.worldline.sips.model.NamedPaymentMeanType.CARD; +import static com.worldline.sips.model.NamedPaymentMeanType.CREDIT_TRANSFER; +import static com.worldline.sips.model.NamedPaymentMeanType.DIRECT_DEBIT; +import static com.worldline.sips.model.NamedPaymentMeanType.EMPTY; +import static com.worldline.sips.model.NamedPaymentMeanType.ONLINE_CREDIT; +import static com.worldline.sips.model.NamedPaymentMeanType.PROVIDER; +import static com.worldline.sips.model.NamedPaymentMeanType.VOUCHER; +import static com.worldline.sips.model.NamedPaymentMeanType.WALLET; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum NamedPaymentMeanBrand implements PaymentMeanBrand { + _1EUROCOM(ONLINE_CREDIT), + _3XCBCOFINOGA(CARD), + ACCEPTGIRO(CREDIT_TRANSFER), + ACCORD(CARD), + ACCORD_KDO(CARD), + ACCORD_3X(ONLINE_CREDIT), + ACCORD_4X(ONLINE_CREDIT), + AMEX(CARD), + AURORE(CARD), + BCACB_3X(ONLINE_CREDIT), + BCACB_4X(ONLINE_CREDIT), + BCACUP(CARD), + BCMC(CARD), + CACF_3X(ONLINE_CREDIT), + CACF_4X(ONLINE_CREDIT), + CADHOC(CARD), + CADOCARTE(CARD), + CB(CARD), + CBCONLINE(CREDIT_TRANSFER), + CETELEM_3X(ONLINE_CREDIT), + CETELEM_4X(ONLINE_CREDIT), + COFIDIS_3X(ONLINE_CREDIT), + COFIDIS_4X(ONLINE_CREDIT), + CONECS(VOUCHER), + CUP(CARD), + CVA(CARD), + CVCO(VOUCHER), + DINNERS(CARD), + ECV(VOUCHER), + ELV(DIRECT_DEBIT), + FIVORY(WALLET), + FRANFINANCE_3X(ONLINE_CREDIT), + FRANFINANCE_4X(ONLINE_CREDIT), + GIROPAY(CREDIT_TRANSFER), + IDEAL(CREDIT_TRANSFER), + ILLICADO(CARD), + INCASSO(DIRECT_DEBIT), + JCB(CARD), + KBCONLINE(CREDIT_TRANSFER), + LEPOTCOMMUN(CARD), + LYDIA(PROVIDER), + MAESTRO(CARD), + MASTERCARD(CARD), + MASTERPASS(EMPTY), + PASSCADO(CARD), + PAY_BY_BANK(CREDIT_TRANSFER), + PAYLIB(EMPTY), + PAYPAL(WALLET), + PAYTRAIL(CREDIT_TRANSFER), + POSTFINANCE(CARD), + PRESTO(ONLINE_CREDIT), + SEPA_DIRECT_DEBIT(DIRECT_DEBIT), + SOFINCO(CARD), + SOFORTUBERWEISUNG(CREDIT_TRANSFER), + SPIRITOFCADEAU(CARD), + UNKNOWN(EMPTY), + VISA(CARD), + VISACHECKOUT(WALLET), + VISA_ELECTRON(CARD), + VPAY(CARD); + + final String realName; + final NamedPaymentMeanType namedPaymentMeanType; + + NamedPaymentMeanBrand(NamedPaymentMeanType namedPaymentMeanType) { + String name = name(); + this.namedPaymentMeanType = namedPaymentMeanType; + this.realName = name.charAt(0) == '_' ? name.substring(1) : name; + } + + @Override + @JsonValue + public String getRealName() { + return this.realName; + } + + @JsonIgnore + public NamedPaymentMeanType getPaymentMeanType() { + return namedPaymentMeanType; + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanType.java b/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanType.java new file mode 100644 index 0000000..6a9d116 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/NamedPaymentMeanType.java @@ -0,0 +1,11 @@ +package com.worldline.sips.model; + +public enum NamedPaymentMeanType implements PaymentMeanType { + CARD, CREDIT_TRANSFER, DIRECT_DEBIT, + VOUCHER, WALLET, ONLINE_CREDIT, EMPTY, PROVIDER; + + @Override + public String getName() { + return name(); + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java b/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java new file mode 100644 index 0000000..4076e7d --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanBrand.java @@ -0,0 +1,22 @@ +package com.worldline.sips.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public interface PaymentMeanBrand { + + @JsonValue + String getRealName(); + + @JsonCreator + static PaymentMeanBrand fromRealName(String string) { + if (string == null) return null; + for (NamedPaymentMeanBrand responseCode : NamedPaymentMeanBrand.values()) { + if (responseCode.getRealName().equals(string)) { + return responseCode; + } + } + + return new UnknownPaymentMeanBrand(string); + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java b/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java new file mode 100644 index 0000000..d96d0ba --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/PaymentMeanType.java @@ -0,0 +1,21 @@ +package com.worldline.sips.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.apache.commons.lang3.StringUtils; + +public interface PaymentMeanType { + String getName(); + + + @JsonCreator + static PaymentMeanType fromValue(String value) { + if (StringUtils.isBlank(value)) { + return NamedPaymentMeanType.EMPTY; + } + try { + return NamedPaymentMeanType.valueOf(value); + } catch (IllegalArgumentException e) { + return new UnknownPaymentMeanType(value); + } + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/SipsEnvironment.java b/sdk-common/src/main/java/com/worldline/sips/model/SipsEnvironment.java new file mode 100644 index 0000000..2a4de17 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/SipsEnvironment.java @@ -0,0 +1,8 @@ +package com.worldline.sips.model; + +/** + * Contains the target URL for a SIPS Environment + */ +public interface SipsEnvironment { + String getUrl(); +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanBrand.java b/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanBrand.java new file mode 100644 index 0000000..32634a0 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanBrand.java @@ -0,0 +1,14 @@ +package com.worldline.sips.model; + +public class UnknownPaymentMeanBrand implements PaymentMeanBrand { + private final String realName; + + public UnknownPaymentMeanBrand(String realName) { + this.realName = realName; + } + + @Override + public String getRealName() { + return realName; + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanType.java b/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanType.java new file mode 100644 index 0000000..eb99b9e --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/model/UnknownPaymentMeanType.java @@ -0,0 +1,14 @@ +package com.worldline.sips.model; + +public class UnknownPaymentMeanType implements PaymentMeanType { + private final String name; + + public UnknownPaymentMeanType(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } +} diff --git a/sdk-common/src/main/java/com/worldline/sips/security/SealCalculator.java b/sdk-common/src/main/java/com/worldline/sips/security/SealCalculator.java new file mode 100644 index 0000000..6d8d786 --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/security/SealCalculator.java @@ -0,0 +1,82 @@ +package com.worldline.sips.security; + +import com.worldline.sips.SIPSRequest; +import com.worldline.sips.SIPSResponse; +import com.worldline.sips.exception.SealCalculationException; +import com.worldline.sips.helper.AlphabeticalReflectionToStringBuilder; +import com.worldline.sips.helper.SealStringStyle; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Hex; + +/** + * Utility class to compute seals. + * + * @see Sealable + */ +public class SealCalculator { + private SealCalculator() { + // Nothing to see here + } + + /** + * Calculate the encrypted seal for a {@link SIPSRequest} based on a given seal string. + * + * @param sealString the seal string for the {@link SIPSRequest} that needs to be signed + * @param key the merchant's secret key + * @return the encrypted seal for the request + * @throws SealCalculationException when the encryption fails (e.g. algorithm missing, invalid key specified) + * @see #getSealString(SIPSRequest) + */ + public static String calculate(String sealString, String key) throws SealCalculationException { + try { + Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + hmacSHA256.init(secretKeySpec); + return Hex.encodeHexString(hmacSHA256.doFinal(sealString.getBytes(StandardCharsets.UTF_8))); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new SealCalculationException("Seal could not be calculated!", e); + } + } + + /** + * Sort & concatenate the fields of a given {@link SIPSRequest}, needed to correctly sign the request. + * + * @param request the request that's needs to be signed + * @return a String, formatted as described in the API docs. + */ + public static String getSealString(SIPSRequest request) { + AlphabeticalReflectionToStringBuilder reflectionToStringBuilder = AlphabeticalReflectionToStringBuilder.newInstance(request, new SealStringStyle()); + configure(reflectionToStringBuilder); + return reflectionToStringBuilder.toString(); + } + + /** + * Sort & concatenate the fields of a given {@link SIPSResponse}, needed to correctly verify a response. + * + * @param response the response that's needs to be verified + * @return a String, formatted as described in the API docs. + */ + public static String getSealString(SIPSResponse response) { + AlphabeticalReflectionToStringBuilder reflectionToStringBuilder = AlphabeticalReflectionToStringBuilder.newInstance(response, new SealStringStyle()); + configure(reflectionToStringBuilder); + return reflectionToStringBuilder.toString(); + } + + private static void configure(AlphabeticalReflectionToStringBuilder reflectionToStringBuilder) { + reflectionToStringBuilder.addSerializer(YearMonth.class,o -> DateTimeFormatter.ofPattern("yyyyMM").format((YearMonth) o)); + reflectionToStringBuilder.addSerializer(LocalDateTime.class,o -> DateTimeFormatter.ISO_OFFSET_DATE_TIME.format((LocalDateTime) o)); + reflectionToStringBuilder.addSerializer(LocalDate.class,o -> DateTimeFormatter.BASIC_ISO_DATE.format((LocalDate) o)); + reflectionToStringBuilder.setExcludeFieldNames("keyVersion", "endpoint", "sealAlgorithm", "seal"); + reflectionToStringBuilder.setExcludeNullValues(true); + } + +} + diff --git a/sdk-common/src/main/java/com/worldline/sips/security/Sealable.java b/sdk-common/src/main/java/com/worldline/sips/security/Sealable.java new file mode 100644 index 0000000..3fe390c --- /dev/null +++ b/sdk-common/src/main/java/com/worldline/sips/security/Sealable.java @@ -0,0 +1,10 @@ +package com.worldline.sips.security; + +/** + * Something that contains a seal that validates its integrity + */ +public interface Sealable { + + String getSeal(); + +} diff --git a/payment-sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java b/sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java similarity index 55% rename from payment-sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java rename to sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java index 193c44a..11e8d03 100644 --- a/payment-sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java +++ b/sdk-common/src/main/java/com/worldline/sips/util/ObjectMapperHolder.java @@ -1,17 +1,24 @@ package com.worldline.sips.util; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.YearMonthDeserializer; - +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.YearMonthSerializer; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.YearMonth; import java.time.format.DateTimeFormatter; - +/** + * Container for the global mapper object instance + * + * @see ObjectMapper + */ public enum ObjectMapperHolder { INSTANCE; @@ -23,10 +30,14 @@ public enum ObjectMapperHolder { private static ObjectMapper create() { return new ObjectMapper() - .registerModule(new JavaTimeModule() + .configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false) + .registerModule(new JavaTimeModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.BASIC_ISO_DATE)) - .addDeserializer(YearMonth.class, new YearMonthDeserializer(DateTimeFormatter.ofPattern("yyyyMM")))); + .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.BASIC_ISO_DATE)) + .addDeserializer(YearMonth.class, new YearMonthDeserializer(DateTimeFormatter.ofPattern("yyyyMM"))) + .addSerializer(YearMonth.class, new YearMonthSerializer(DateTimeFormatter.ofPattern("yyyyMM")))); } public ObjectMapper get() { diff --git a/settings.gradle b/settings.gradle index 46f54d3..683f005 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ -include(':payment-sdk-common') include(':payment-sdk') +include(':office-sdk') +include 'sdk-common' +