diff --git a/CHANGES.md b/CHANGES.md old mode 100644 new mode 100755 index c458279..498ecb4 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +### Version 2.6.0 - March 2023 +* Introducing new v2 Reporting APIs. Reports allow you to retrieve consolidated data about Amazon Pay transactions and settlements. In addition to managing and downloading reports using Seller Central, Amazon Pay offers APIs to manage and retrieve your reports. +* Introducing new signature generation algorithm AMZN-PAY-RSASSA-PSS-V2 & increasing salt length from 20 to 32. +* Added support for handling new parameter 'shippingAddressList' in Checkout Session response. Change is fully backwards compatible. +* Enabled Connection Pooling Support. +* Adding Error code 408 to API retry logic +* Note : To use new algorithm AMZN-PAY-RSASSA-PSS-V2, "algorithm" needs to be provided as an additional field in "payConfiguration" and also while rendering Amazon Pay button in "createCheckoutSessionConfig". The changes are backwards-compatible, SDK will use AMZN-PAY-RSASSA-PSS by default. + #### Version 2.5.1 - January 2022 * Applied patch to address issues occurred in Version 2.5.0. * **Please dont use Version 2.5.0** diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 6af1a87..52c37cf --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ To use the SDK in a Maven project, add a reference in your pom.xml software.amazon.pay amazon-pay-api-sdk-java - 2.5.1 + 2.6.0 ``` @@ -25,7 +25,7 @@ To use the SDK in a Maven project, add a reference in your pom.xml To use the SDK in a Gradle project, add the following line to your build.gradle file:: ``` -implementation 'software.amazon.pay:amazon-pay-api-sdk-java:2.5.1' +implementation 'software.amazon.pay:amazon-pay-api-sdk-java:2.6.0' ``` For legacy projects, you can just grab the binary [jar file](https://github.com/amzn/amazon-pay-api-sdk-java/releases) from the GitHub Releases page. @@ -106,7 +106,8 @@ try { .setPublicKeyId("YOUR_PUBLIC_KEY_ID") .setRegion(Region.YOUR_REGION_CODE) .setPrivateKey("YOUR_PRIVATE_KEY_STRING".toCharArray()) - .setEnvironment(Environment.SANDBOX); + .setEnvironment(Environment.SANDBOX) + .setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); // Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified } catch (AmazonPayClientException e) { e.printStackTrace(); } @@ -117,7 +118,8 @@ try { payConfiguration = new PayConfiguration() .setPublicKeyId("YOUR_PUBLIC_KEY_ID") // LIVE-XXXXX or SANDBOX-XXXXX .setRegion(Region.YOUR_REGION_CODE) - .setPrivateKey("YOUR_PRIVATE_KEY_STRING".toCharArray()); + .setPrivateKey("YOUR_PRIVATE_KEY_STRING".toCharArray()) + .setAlgorithm('AMZN-PAY-RSASSA-PSS-V2'); // Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified } catch (AmazonPayClientException e) { e.printStackTrace(); } @@ -129,7 +131,8 @@ try { .setPublicKeyId("YOUR_PUBLIC_KEY_ID") .setRegion(Region.YOUR_REGION_CODE) .setPrivateKey(new String(Files.readAllBytes(Paths.get("private.pem"))).toCharArray()) - .setEnvironment(Environment.SANDBOX); + .setEnvironment(Environment.SANDBOX) + .setAlgorithm('AMZN-PAY-RSASSA-PSS-V2'); // Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified } catch (AmazonPayClientException e) { e.printStackTrace(); } @@ -143,7 +146,8 @@ try { .setPublicKeyId("YOUR_PUBLIC_KEY_ID") .setRegion(Region.YOUR_REGION_CODE) .setPrivateKey(privateKey) - .setEnvironment(Environment.SANDBOX); + .setEnvironment(Environment.SANDBOX) + .setAlgorithm('AMZN-PAY-RSASSA-PSS-V2'); // Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified } catch (AmazonPayClientException e) { e.printStackTrace(); } @@ -162,10 +166,25 @@ try { .setRegion(Region.YOUR_REGION_CODE) .setPrivateKey("YOUR_PRIVATE_KEY_STRING".toCharArray()) .setEnvironment(Environment.SANDBOX) + .setAlgorithm('AMZN-PAY-RSASSA-PSS-V2'); // Amazon Signing Algorithm, Optional: uses AMZN-PAY-RSASSA-PSS if not specified .setProxySettings(proxySettings); } catch (AmazonPayClientException e) { e.printStackTrace(); } + +// If you want to enable the Custom Connection Pool, you can set it in the payConfiguration in the following way: + +try { + int MAX_CLIENT_CONNECTIONS = 30; // This value should be decided according to your requirement + payConfiguration = new PayConfiguration() + .setPublicKeyId("YOUR_PUBLIC_KEY_ID") + .setRegion(Region.YOUR_REGION_CODE) + .setPrivateKey("YOUR_PRIVATE_KEY_STRING".toCharArray()) + .setEnvironment(Environment.SANDBOX) + .setClientConnections(MAX_CLIENT_CONNECTIONS); // Default is set to 20 +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} ``` # Convenience Functions (Overview) @@ -767,3 +786,120 @@ while ((inputLine = in.readLine()) != null) { String chargePermissionId = JSONObject.fromObject(response.toString()).getString("chargePermissionId"); ``` + +# Reporting APIs code samples + +## Amazon Checkout v2 Reporting APIs - GetReports API +```java +AmazonPayResponse response = null; + +Map> queryParameters = new HashMap<>(); +List reportTypes = new ArrayList<>(); +reportTypes.add("_GET_FLAT_FILE_OFFAMAZONPAYMENTS_SETTLEMENT_DATA_"); +List processingStatuses = new ArrayList<>(); +processingStatuses.add("COMPLETED"); + +queryParameters.put("reportTypes", reportTypes); +queryParameters.put("reportTypes", processingStatuses); + +try { + response = webstoreClient.getReports(queryParameters); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - GetReportById API +```java +AmazonPayResponse response = null; +String reportId = "1234567890"; + +try { + response = webstoreClient.getReportById(reportId); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - GetReportDocument API +```java +AmazonPayResponse response = null; +String reportDocumentId = "1234567890"; + +try { + response = webstoreClient.getReportDocument(reportDocumentId); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - GetReportSchedules API +```java +AmazonPayResponse response = null; +String reportTypes = "_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_,_GET_FLAT_FILE_OFFAMAZONPAYMENTS_BILLING_AGREEMENT_DATA_"; + +try { + response = webstoreClient.getReportSchedules(reportTypes); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - GetReportScheduleById API +```java +AmazonPayResponse response = null; +String reportScheduleId = "1234567890"; + +try { + response = webstoreClient.getReportScheduleById(reportScheduleId); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - CreateReport API +```java +AmazonPayResponse response = null; +JSONObject requestPayload = new JSONObject(); +requestPayload.put("reportType", "_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_"); +requestPayload.put("startTime", "20221114T074550Z"); +requestPayload.put("endTime", "20221202T150350Z"); +Map header = new HashMap(); +header.put("x-amz-pay-idempotency-key", UUID.randomUUID().toString().replace("-", "")); + +try { + response = webstoreClient.createReport(requestPayload, header); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - CreateReportSchedule API +```java +AmazonPayResponse response = null; +JSONObject requestPayload = new JSONObject(); +requestPayload.put("reportType", "_GET_FLAT_FILE_OFFAMAZONPAYMENTS_ORDER_REFERENCE_DATA_"); +requestPayload.put("scheduleFrequency", "P14D"); +requestPayload.put("nextReportCreationTime", "20221202T150350Z"); +requestPayload.put("deleteExistingSchedule", "false"); +Map header = new HashMap(); +header.put("x-amz-pay-idempotency-key", UUID.randomUUID().toString().replace("-", "")); + +try { + response = webstoreClient.createReportSchedule(requestPayload, header); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` + +## Amazon Checkout v2 Reporting APIs - CancelReportSchedule API +```java +AmazonPayResponse response = null; +String reportScheduleId = "1234567890"; + +try { + response = webstoreClient.cancelReportSchedule(reportScheduleId); +} catch (AmazonPayClientException e) { + e.printStackTrace(); +} +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 162dd08..3ead808 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ software.amazon.pay amazon-pay-api-sdk-java jar - 2.5.1 + 2.6.0 diff --git a/src/com/amazon/pay/api/AmazonPayClient.java b/src/com/amazon/pay/api/AmazonPayClient.java old mode 100644 new mode 100755 index c287bc5..ec29644 --- a/src/com/amazon/pay/api/AmazonPayClient.java +++ b/src/com/amazon/pay/api/AmazonPayClient.java @@ -35,8 +35,10 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; +import org.apache.commons.lang.StringUtils; import com.amazon.pay.api.exceptions.AmazonPayClientException; +import com.amazon.pay.api.types.AmazonSignatureAlgorithm; import org.json.JSONException; import org.json.JSONObject; @@ -129,9 +131,10 @@ public String generateButtonSignature(final JSONObject payload) throws AmazonPay public String generateButtonSignature(final String payload) throws AmazonPayClientException { String signature = null; final SignatureHelper signatureHelper = new SignatureHelper(payConfiguration); + final AmazonSignatureAlgorithm algorithm = payConfiguration.getAlgorithm(); try { - final String stringToSign = signatureHelper.createStringToSign(payload); - signature = signatureHelper.generateSignature(stringToSign, payConfiguration.getPrivateKey()); + final String stringToSign = signatureHelper.createStringToSign(payload, algorithm.getName()); + signature = signatureHelper.generateSignature(stringToSign, payConfiguration.getPrivateKey(), algorithm); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException @@ -210,7 +213,9 @@ private AmazonPayResponse processRequest(final URI uri, if (response.get(ServiceConstants.RESPONSE_STRING) != null) { // Converting the response string into a JSONObject rawResponseObject = response.get(ServiceConstants.RESPONSE_STRING); - jsonResponse = new JSONObject(response.get(ServiceConstants.RESPONSE_STRING)); + if(!StringUtils.isEmpty(response.get(ServiceConstants.RESPONSE_STRING))) { + jsonResponse = new JSONObject(response.get(ServiceConstants.RESPONSE_STRING)); + } } } catch (InterruptedException | JSONException e) { throw new AmazonPayClientException(e.getMessage(), e); @@ -241,8 +246,8 @@ private List sendRequest(final URI uri, String requestId = null; int responseCode = 0; try (final CloseableHttpClient client = Optional.ofNullable(payConfiguration.getProxySettings()).isPresent() - ? Util.getCloseableHttpClientWithProxy(payConfiguration.getProxySettings()) - : HttpClients.createDefault()) { + ? Util.getCloseableHttpClientWithProxy(payConfiguration.getProxySettings(), payConfiguration) + : Util.getHttpClientWithConnectionPool(payConfiguration)) { final HttpUriRequest httpUriRequest = Util.getHttpUriRequest(uri, httpMethodName, payload); for (Map.Entry entry : headers.entrySet()) { httpUriRequest.addHeader(entry.getKey(), entry.getValue()); diff --git a/src/com/amazon/pay/api/PayConfiguration.java b/src/com/amazon/pay/api/PayConfiguration.java old mode 100644 new mode 100755 index b7ff743..4cff908 --- a/src/com/amazon/pay/api/PayConfiguration.java +++ b/src/com/amazon/pay/api/PayConfiguration.java @@ -17,6 +17,7 @@ import com.amazon.pay.api.exceptions.AmazonPayClientException; import com.amazon.pay.api.types.Environment; import com.amazon.pay.api.types.Region; +import com.amazon.pay.api.types.AmazonSignatureAlgorithm; import java.security.PrivateKey; @@ -25,10 +26,12 @@ public class PayConfiguration { private String publicKeyId; private PrivateKey privateKey; private Environment environment; + private AmazonSignatureAlgorithm algorithm; private int maxRetries = 3; private boolean userAgentRedaction = false; private ProxySettings proxySettings; protected String overrideServiceURL; + private Integer clientConnections; /** * @return Returns region code from PayConfiguration @@ -98,6 +101,26 @@ public PayConfiguration setPrivateKey(PrivateKey privateKey) { return this; } + /** + * @return returns Algorithm from PayConfiguration, default algorithm is AMZN-PAY-RSASSA-PSS if not set + */ + public AmazonSignatureAlgorithm getAlgorithm() { + if(algorithm != null) { + return algorithm; + } else { + return AmazonSignatureAlgorithm.DEFAULT; + } + } + + /** + * @param algorithm the Amazon Signature Algorithm + * @return the PayConfiguration object + */ + public PayConfiguration setAlgorithm(final String algorithm) { + this.algorithm = AmazonSignatureAlgorithm.returnIfValidAlgorithm(algorithm); + return this; + } + /** * @return returns the environment from the PayConfiguration */ @@ -179,4 +202,24 @@ public String getOverrideServiceURL() { return overrideServiceURL; } + /** + * @return Returns clientConnections from PayConfiguration + */ + public Integer getClientConnections() { + if(clientConnections != null && clientConnections != 0) { + return clientConnections; + } else { + return ServiceConstants.MAX_CLIENT_CONNECTIONS; + } + } + + /** + * @param clientConnections Sets the maximum number of Client Connections to be made + * @return the PayConfiguration object + */ + public PayConfiguration setClientConnections(int clientConnections) { + this.clientConnections = clientConnections; + return this; + } + } diff --git a/src/com/amazon/pay/api/RequestSigner.java b/src/com/amazon/pay/api/RequestSigner.java old mode 100644 new mode 100755 index d9ae0a6..3a22399 --- a/src/com/amazon/pay/api/RequestSigner.java +++ b/src/com/amazon/pay/api/RequestSigner.java @@ -15,6 +15,7 @@ package com.amazon.pay.api; import com.amazon.pay.api.exceptions.AmazonPayClientException; +import com.amazon.pay.api.types.AmazonSignatureAlgorithm; import java.net.URI; import java.security.InvalidAlgorithmParameterException; @@ -56,14 +57,15 @@ public Map signRequest(final URI uri, final String requestPayload, final Map header) throws AmazonPayClientException { final String publicKeyId = payConfiguration.getPublicKeyId(); + final AmazonSignatureAlgorithm algorithm = payConfiguration.getAlgorithm(); final Map> preSignedHeaders = signatureHelper.createPreSignedHeaders(uri, header); final String userAgent = buildUserAgentHeader(); String signature = null; try { final String canonicalRequest = signatureHelper.createCanonicalRequest(uri, httpMethodName, queryParameters, requestPayload, preSignedHeaders); - final String stringToSign = signatureHelper.createStringToSign(canonicalRequest); - signature = signatureHelper.generateSignature(stringToSign, privateKey); + final String stringToSign = signatureHelper.createStringToSign(canonicalRequest, algorithm.getName()); + signature = signatureHelper.generateSignature(stringToSign, privateKey, algorithm); } catch (NoSuchAlgorithmException | NoSuchProviderException @@ -73,7 +75,7 @@ public Map signRequest(final URI uri, throw new AmazonPayClientException(e.getMessage(), e); } - final String authorizationHeader = buildAuthorizationHeader(publicKeyId, preSignedHeaders, signature); + final String authorizationHeader = buildAuthorizationHeader(publicKeyId, preSignedHeaders, signature, algorithm); Map postSignedHeadersMap = new HashMap<>(); @@ -109,12 +111,13 @@ private String buildUserAgentHeader() { * @param publicKeyId the public key id from the PayConfiguration * @param preSignedHeaders the set of pre signed headers * @param signature the generated signature + * @param algorithm the Amazon Signature Algorithm from payConfiguration * @return the authorization string * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue */ private String buildAuthorizationHeader(String publicKeyId, Map> preSignedHeaders, - String signature) throws AmazonPayClientException { + String signature, AmazonSignatureAlgorithm algorithm) throws AmazonPayClientException { // The parameters being null should never happen at this point, because that // case should be caught by other exceptions upstream. @@ -123,7 +126,7 @@ private String buildAuthorizationHeader(String publicKeyId, throw new AmazonPayClientException("Invalid parameter while constructing authorization header"); } - final StringBuilder authorizationBuilder = new StringBuilder(ServiceConstants.AMAZON_SIGNATURE_ALGORITHM) + final StringBuilder authorizationBuilder = new StringBuilder(algorithm.getName()) .append(" PublicKeyId=").append(publicKeyId).append(", ").append("SignedHeaders=") .append(signatureHelper.getSignedHeadersString(preSignedHeaders)) .append(", Signature=").append(signature); diff --git a/src/com/amazon/pay/api/ServiceConstants.java b/src/com/amazon/pay/api/ServiceConstants.java old mode 100644 new mode 100755 index 3f480b7..89ad831 --- a/src/com/amazon/pay/api/ServiceConstants.java +++ b/src/com/amazon/pay/api/ServiceConstants.java @@ -26,11 +26,12 @@ public class ServiceConstants { public static final Map endpointMappings; public static final Map serviceErrors; - public static final String APPLICATION_LIBRARY_VERSION = "2.5.1"; + public static final String APPLICATION_LIBRARY_VERSION = "2.6.0"; public static final String GITHUB_SDK_NAME = "amazon-pay-api-sdk-java"; public static final String AMAZON_PAY_API_VERSION = "v2"; - public static final String AMAZON_SIGNATURE_ALGORITHM = "AMZN-PAY-RSASSA-PSS"; + public static final String DEFAULT_AMAZON_SIGNATURE_ALGORITHM = "AMZN-PAY-RSASSA-PSS"; + public static final String AMAZON_SIGNATURE_ALGORITHM_V2 = "AMZN-PAY-RSASSA-PSS-V2"; public static final String HASH_ALGORITHM = "SHA-256"; public static final String SIGNATURE_ALGORITHM = "SHA256WithRSA/PSS"; public static final String MASK_GENERATION_FUNCTION = "MGF1"; @@ -58,6 +59,12 @@ public class ServiceConstants { public static final int RESPONSE_STATUS_CODE = 0; public static final int RESPONSE_STRING = 1; public static final int REQUEST_ID = 2; + public static final Integer MAX_CLIENT_CONNECTIONS = 20; + + // CV2 Reporting APIs Constants + public static final String REPORTS = AMAZON_PAY_API_VERSION + "/reports"; + public static final String REPORT_DOCUMENT = AMAZON_PAY_API_VERSION + "/report-documents"; + public static final String REPORT_SCHEDULES = AMAZON_PAY_API_VERSION + "/report-schedules"; static { Map endpointMappingsMap = new HashMap<>(); @@ -74,7 +81,10 @@ public class ServiceConstants { serviceErrorsMap.put("Too Many Requests", 429); serviceErrorsMap.put("HTTP Bad Gateway", HttpURLConnection.HTTP_BAD_GATEWAY); serviceErrorsMap.put("HTTP Gateway Timeout", HttpURLConnection.HTTP_GATEWAY_TIMEOUT); + serviceErrorsMap.put("Request Timeout", HttpURLConnection.HTTP_CLIENT_TIMEOUT); serviceErrors = Collections.unmodifiableMap(serviceErrorsMap); } public static final String X_AMZ_PAY_REQUEST_ID = "X-Amz-Pay-Request-Id"; + + public static final char[] privateKeyArray = {'p','r','i','v','a','t','e','K','e','y'}; } diff --git a/src/com/amazon/pay/api/SignatureHelper.java b/src/com/amazon/pay/api/SignatureHelper.java old mode 100644 new mode 100755 index 20e6cce..87e5d5c --- a/src/com/amazon/pay/api/SignatureHelper.java +++ b/src/com/amazon/pay/api/SignatureHelper.java @@ -15,6 +15,7 @@ package com.amazon.pay.api; import com.amazon.pay.api.exceptions.AmazonPayClientException; +import com.amazon.pay.api.types.AmazonSignatureAlgorithm; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Hex; @@ -42,7 +43,6 @@ public class SignatureHelper { private final PayConfiguration payConfiguration; private final String LINE_SEPARATOR = "\n"; - public final static int SALT_LENGTH = 20; public final static int TRAILER_FIELD = 1; public SignatureHelper(final PayConfiguration payConfiguration) { @@ -89,14 +89,15 @@ public String createCanonicalRequest(final URI uri, /** * Creates the string that is going to be signe * @param canonicalRequest The canonical request generated using the createCanonicalRequest() method + * @param algorithm The Amazon Signature Algorithm from payConfiguration * @return the string to be signed * @throws NoSuchAlgorithmException exception thrown when the cryptographic * algorithm requested is not available in the environment */ - public String createStringToSign(final String canonicalRequest) throws NoSuchAlgorithmException { + public String createStringToSign(final String canonicalRequest, final String algorithm) throws NoSuchAlgorithmException { final String hashedCanonicalRequest = hashThenHexEncode(canonicalRequest); - final StringBuilder stringToSignBuilder = new StringBuilder(ServiceConstants.AMAZON_SIGNATURE_ALGORITHM); + final StringBuilder stringToSignBuilder = new StringBuilder(algorithm); stringToSignBuilder.append(LINE_SEPARATOR) .append(hashedCanonicalRequest); @@ -107,6 +108,7 @@ public String createStringToSign(final String canonicalRequest) throws NoSuchAlg * Generates a signature for the string passed in * @param stringToSign the string to be signed * @param privateKey the private key to use for signing + * @param algorithm the Amazon Signature Algorithm from payConfiguration * @return the signature * @throws NoSuchAlgorithmException exception thrown when the cryptographic * algorithm requested is not available in the environment @@ -116,13 +118,15 @@ public String createStringToSign(final String canonicalRequest) throws NoSuchAlg * @throws InvalidKeyException exception for invalid keys * @throws SignatureException signature exception */ - public String generateSignature(final String stringToSign, final PrivateKey privateKey) throws NoSuchAlgorithmException, + public String generateSignature(final String stringToSign, final PrivateKey privateKey, final AmazonSignatureAlgorithm + algorithm) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, SignatureException { + final Integer saltLength = algorithm.getSaltLength(); final Signature signature = Signature.getInstance(ServiceConstants.SIGNATURE_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); final MGF1ParameterSpec mgf1ParameterSpec = new MGF1ParameterSpec(ServiceConstants.HASH_ALGORITHM); final PSSParameterSpec pssParameterSpec = new PSSParameterSpec(ServiceConstants.HASH_ALGORITHM, - ServiceConstants.MASK_GENERATION_FUNCTION, mgf1ParameterSpec, SALT_LENGTH, TRAILER_FIELD); + ServiceConstants.MASK_GENERATION_FUNCTION, mgf1ParameterSpec, saltLength, TRAILER_FIELD); signature.setParameter(pssParameterSpec); signature.initSign(privateKey); signature.update(stringToSign.getBytes()); diff --git a/src/com/amazon/pay/api/Util.java b/src/com/amazon/pay/api/Util.java old mode 100644 new mode 100755 index e809df2..733969e --- a/src/com/amazon/pay/api/Util.java +++ b/src/com/amazon/pay/api/Util.java @@ -52,6 +52,7 @@ import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -63,6 +64,10 @@ import com.amazon.pay.api.exceptions.AmazonPayClientException; import com.amazon.pay.api.types.Environment; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + public class Util { public static final String JAVA_VERSION = System.getProperty("java.version"); @@ -341,9 +346,10 @@ public static HttpUriRequest getHttpUriRequest(final URI uri, final String httpM * Returns the CloseableHttpClient object based on the given proxy settings. * * @param proxySettings the ProxySettings + * @param payConfiguration the PayConfiguration * @return the CloseableHttpClient */ - public static CloseableHttpClient getCloseableHttpClientWithProxy(final ProxySettings proxySettings) { + public static CloseableHttpClient getCloseableHttpClientWithProxy(final ProxySettings proxySettings, final PayConfiguration payConfiguration) { final HttpHost proxy = new HttpHost(proxySettings.getProxyHost(), proxySettings.getProxyPort()); final Credentials credentials = new UsernamePasswordCredentials(proxySettings.getProxyUser(), @@ -353,7 +359,50 @@ public static CloseableHttpClient getCloseableHttpClientWithProxy(final ProxySet final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(authScope, credentials); final HttpClientBuilder httpClientBuilder = HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider) - .setProxy(proxy); + .setProxy(proxy) + .setMaxConnTotal(payConfiguration.getClientConnections()) + .setMaxConnPerRoute(payConfiguration.getClientConnections()); + return httpClientBuilder.build(); + } + + /** + * Returns the CloseableHttpClient object with Connection Pool based on the Payconfiguration + * + * @param payConfiguration the PayConfiguration + * @return the CloseableHttpClient + */ + public static CloseableHttpClient getHttpClientWithConnectionPool(final PayConfiguration payConfiguration) { + final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(payConfiguration.getClientConnections()); + connectionManager.setDefaultMaxPerRoute(payConfiguration.getClientConnections()); + final HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setConnectionManager(connectionManager); return httpClientBuilder.build(); } + + /** + * Enhances checkoutSession response by parsing shippingAddressList to JSONObject + * + * @param amazonPayResponse the response of the API request + * @return the enhanced amazonPayResponse + * @throws AmazonPayClientException + */ + public static AmazonPayResponse enhanceResponseWithShippingAddressList(final AmazonPayResponse amazonPayResponse) throws AmazonPayClientException { + try { + JSONObject response = amazonPayResponse.getResponse(); + JSONArray shippingAddressList = response.optJSONArray("shippingAddressList"); + + if (shippingAddressList != null) { + for (int i = 0; i < shippingAddressList.length(); ++i) { + shippingAddressList.put(i, new JSONObject(shippingAddressList.getString(i))); + } + amazonPayResponse.setRawResponse(response.toString()); + } + } + catch(JSONException e) { + throw new AmazonPayClientException(e.getMessage(), e); + } + return amazonPayResponse; + } + } diff --git a/src/com/amazon/pay/api/WebstoreClient.java b/src/com/amazon/pay/api/WebstoreClient.java old mode 100644 new mode 100755 index b62bd29..94b99bc --- a/src/com/amazon/pay/api/WebstoreClient.java +++ b/src/com/amazon/pay/api/WebstoreClient.java @@ -18,8 +18,10 @@ import org.json.JSONObject; import java.net.URI; +import java.util.Arrays; import java.util.Map; - +import java.util.List; +import java.util.HashMap; public class WebstoreClient extends AmazonPayClient { @@ -96,7 +98,8 @@ public AmazonPayResponse createCheckoutSession(final JSONObject payload) throws public AmazonPayResponse getCheckoutSession(final String checkoutSessionId, final Map header) throws AmazonPayClientException { final URI checkoutSessionURI = Util.getServiceURI(payConfiguration, ServiceConstants.CHECKOUT_SESSIONS); final URI getCheckoutSessionURI = checkoutSessionURI.resolve(checkoutSessionURI.getPath() + "/" + checkoutSessionId); - return callAPI(getCheckoutSessionURI, "GET", null, "", header); + final AmazonPayResponse response = callAPI(getCheckoutSessionURI, "GET", null, "", header); + return Util.enhanceResponseWithShippingAddressList(response); } /** @@ -125,7 +128,8 @@ public AmazonPayResponse getCheckoutSession(final String checkoutSessionId) thro public AmazonPayResponse updateCheckoutSession(final String checkoutSessionId, final JSONObject payload, final Map header) throws AmazonPayClientException { final URI checkoutSessionURI = Util.getServiceURI(payConfiguration, ServiceConstants.CHECKOUT_SESSIONS); final URI updateCheckoutSessionURI = checkoutSessionURI.resolve(checkoutSessionURI.getPath() + "/" + checkoutSessionId); - return callAPI(updateCheckoutSessionURI, "PATCH", null, payload.toString(), header); + final AmazonPayResponse response = callAPI(updateCheckoutSessionURI, "PATCH", null, payload.toString(), header); + return Util.enhanceResponseWithShippingAddressList(response); } /** @@ -154,7 +158,8 @@ public AmazonPayResponse updateCheckoutSession(final String checkoutSessionId, f public AmazonPayResponse completeCheckoutSession(final String checkoutSessionId, final JSONObject payload, final Map header) throws AmazonPayClientException { final URI checkoutSessionURI = Util.getServiceURI(payConfiguration, ServiceConstants.CHECKOUT_SESSIONS); final URI completeCheckoutSessionURI = checkoutSessionURI.resolve(checkoutSessionURI.getPath() + "/" + checkoutSessionId + "/" + "complete"); - return callAPI(completeCheckoutSessionURI, "POST", null, payload.toString(), header); + final AmazonPayResponse response = callAPI(completeCheckoutSessionURI, "POST", null, payload.toString(), header); + return Util.enhanceResponseWithShippingAddressList(response); } /** @@ -429,4 +434,180 @@ public AmazonPayResponse getRefund(final String refundId, final Map> queryParameters, final Map header) throws AmazonPayClientException { + final URI getReportsURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORTS); + final URI getReportsFinalURI = getReportsURI.resolve(getReportsURI.getPath() + "/?" + convertQueryParamters(queryParameters)); + return callAPI(getReportsFinalURI, "GET", queryParameters, "", header); + } + + public AmazonPayResponse getReports(final Map> queryParameters) throws AmazonPayClientException { + return getReports(queryParameters, null); + } + + public AmazonPayResponse getReports() throws AmazonPayClientException { + return getReports(null, null); + } + + /** + * The getReportById operation is used to get report details for the given reportId. + * + * @param reportId Report ID provided while calling the API + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse getReportById(final String reportId, final Map header) throws AmazonPayClientException { + final URI getReportByIdURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORTS); + final URI getReportByIdFinalURI = getReportByIdURI.resolve(getReportByIdURI.getPath() + "/" + reportId); + return callAPI(getReportByIdFinalURI, "GET", null, "", header); + } + + public AmazonPayResponse getReportById(final String reportId) throws AmazonPayClientException { + return getReportById(reportId, null); + } + + /** + * The getReportDocument operation is used to return the pre-signed S3 URL for the report. The report can be downloaded using this URL. + * + * @param reportDocumentId Report Document ID provided while calling the API + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse getReportDocument(final String reportDocumentId, final Map header) throws AmazonPayClientException { + final URI getReportDocumentURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORT_DOCUMENT); + final URI getReportDocumentFinalURI = getReportDocumentURI.resolve(getReportDocumentURI.getPath() + "/" + reportDocumentId); + return callAPI(getReportDocumentFinalURI, "GET", null, "", header); + } + + public AmazonPayResponse getReportDocument(final String reportDocumentId) throws AmazonPayClientException { + return getReportDocument(reportDocumentId, null); + } + + /** + * The getReportSchedules operation is used to return the pre-signed S3 URL for the report. The report can be downloaded using this URL. + * + * @param reportTypes Report Types provided while calling the API comma-seperated list of ReportType + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse getReportSchedules(final String reportTypes, final Map header) throws AmazonPayClientException { + final URI getReportScheduleURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORT_SCHEDULES); + final Map> queryParameters = new HashMap<>(); + if (!reportTypes.isEmpty()) { + queryParameters.put("reportTypes", Arrays.asList(reportTypes)); + } + + final URI getReportSchedulesFinalURI = getReportScheduleURI.resolve(getReportScheduleURI.getPath() + "/?" + convertQueryParamters(queryParameters)); + return callAPI(getReportSchedulesFinalURI, "GET", queryParameters, "", header); + } + + public AmazonPayResponse getReportSchedules(final String reportTypes) throws AmazonPayClientException { + return getReportSchedules(reportTypes, null); + } + + public AmazonPayResponse getReportSchedules() throws AmazonPayClientException { + return getReportSchedules("", null); + } + + /** + * The getReportScheduleById operation is used to get report schedule details that match the given ID. + * + * @param reportScheduleId Report Schedule ID provided while calling the API + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse getReportScheduleById(final String reportScheduleId, final Map header) throws AmazonPayClientException { + final URI getReportScheduleByIdURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORT_SCHEDULES); + final URI getReportScheduleByIdFinalURI = getReportScheduleByIdURI.resolve(getReportScheduleByIdURI.getPath() + "/" + reportScheduleId); + return callAPI(getReportScheduleByIdFinalURI, "GET", null, "", header); + } + + public AmazonPayResponse getReportScheduleById(final String reportScheduleId) throws AmazonPayClientException { + return getReportScheduleById(reportScheduleId, null); + } + + /** + * The createReport operation is used to submit a request to generate a report based on the reportType and date range specified. + * + * @param payload JSONObject request body + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse createReport(final JSONObject payload, final Map header) throws AmazonPayClientException { + final URI createReportURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORTS); + return callAPI(createReportURI, "POST", null, payload.toString(), header); + } + + /** + * The createReport operation is used to create a report schedule for the given reportType. Only one schedule per report type allowed. + * + * @param payload JSONObject request body + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse createReportSchedule(final JSONObject payload, final Map header) throws AmazonPayClientException { + final URI createReportScheduleURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORT_SCHEDULES); + return callAPI(createReportScheduleURI, "POST", null, payload.toString(), header); + } + + /** + * The cancelReportSchedule operation is used to cancel the report schedule with the given reportScheduleId. + * + * @param reportScheduleId Report Schedule ID provided while calling the API + * @param header Map<String, String> containing key-value pair of required headers (e.g., keys such as x-amz-pay-idempotency-key, x-amz-pay-authtoken) + * @return The response from the getReports service API, as + * returned by Amazon Pay. + * @throws AmazonPayClientException When an error response is returned by Amazon Pay due to bad request or other issue + */ + public AmazonPayResponse cancelReportSchedule(final String reportScheduleId, final Map header) throws AmazonPayClientException { + final URI cancelReportScheduleURI = Util.getServiceURI(payConfiguration, ServiceConstants.REPORT_SCHEDULES); + final URI cancelReportScheduleFinalURI = cancelReportScheduleURI.resolve(cancelReportScheduleURI.getPath() + "/" + reportScheduleId); + return callAPI(cancelReportScheduleFinalURI, "DELETE", null, "", header); + } + + public AmazonPayResponse cancelReportSchedule(final String reportScheduleId) throws AmazonPayClientException { + return cancelReportSchedule(reportScheduleId, null); + } + + // Convenience function to convert List of Query parameters to String to be attached to URL + public String convertQueryParamters(final Map> parameters) throws AmazonPayClientException { + if(parameters == null || parameters.isEmpty()) + return ""; + final StringBuilder result = new StringBuilder(); + for (Map.Entry> entry : parameters.entrySet()) { + for (String value : entry.getValue()) { + if (result.length() > 0) { + result.append("&"); + } + result.append(entry.getKey()) + .append("=") + .append(value); + } + } + return result.toString(); + } } diff --git a/src/com/amazon/pay/api/types/AmazonSignatureAlgorithm.java b/src/com/amazon/pay/api/types/AmazonSignatureAlgorithm.java new file mode 100644 index 0000000..f07389e --- /dev/null +++ b/src/com/amazon/pay/api/types/AmazonSignatureAlgorithm.java @@ -0,0 +1,58 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.pay.api.types; + +import java.util.Arrays; + +/** + * This represents an enum class identifying the Amazon Signature Algorithms: + * AMZN-PAY-RSASSA-PSS and AMZN-PAY-RSASSA-PSS-V2 + */ +public enum AmazonSignatureAlgorithm { + DEFAULT("AMZN-PAY-RSASSA-PSS", 20), + V2("AMZN-PAY-RSASSA-PSS-V2", 32); + + private final String name; + private final int saltLength; + + private AmazonSignatureAlgorithm(String name, int saltLength){ + this.name = name; + this.saltLength = saltLength; + } + + /** + * @return Returns name of the Amazon Signing Algorithm + */ + public String getName(){ + return this.name; + } + + /** + * @return Returns salt length used by the Amazon Signing Algorithm + */ + public int getSaltLength(){ + return this.saltLength; + } + + /** + * @param algorithm the Algorithm passed + * @return returns Amazon Signature Algorithm object if algorithm is valid + * @throws IllegalArgumentException if invalid algorithm is passed + */ + public static AmazonSignatureAlgorithm returnIfValidAlgorithm(String algorithm){ + return Arrays.stream(values()).filter(signatureAlgorithm -> signatureAlgorithm.getName().equals(algorithm)).findFirst().orElseThrow(() -> new IllegalArgumentException("Not a valid algorithm")); + } +} \ No newline at end of file diff --git a/tst/com/amazon/pay/api/CreateStringToSignTest.java b/tst/com/amazon/pay/api/CreateStringToSignTest.java old mode 100644 new mode 100755 index fae10b5..2600f7d --- a/tst/com/amazon/pay/api/CreateStringToSignTest.java +++ b/tst/com/amazon/pay/api/CreateStringToSignTest.java @@ -43,12 +43,15 @@ public void ValidateAllTestCases() throws URISyntaxException, NoSuchAlgorithmExc URI uri = new URI(testCase.optString("uri")); Map> queryParams = getParameters((JSONObject) testCase.get("parameters")); String payload = testCase.optString("payload"); + String stringToSign = testCase.optString("stringToSign"); Map> preSignedHeaders = mockedPreSignedHeaders(uri); + String algorithm = stringToSign.startsWith("AMZN-PAY-RSASSA-PSS-V2") ? + stringToSign.substring(0,22) : stringToSign.substring(0,19); String actualCanonicalRequest = signatureHelper.createCanonicalRequest(uri, method, queryParams, payload, preSignedHeaders); String expectedCanonicalRequest = testCase.optString("canonicalRequest"); - String actualStringToSign = signatureHelper.createStringToSign(actualCanonicalRequest); + String actualStringToSign = signatureHelper.createStringToSign(actualCanonicalRequest,algorithm); String expectedStringToSign = testCase.optString("stringToSign"); Assert.assertEquals("Test Case Name : " + name, expectedCanonicalRequest, actualCanonicalRequest); @@ -58,33 +61,48 @@ public void ValidateAllTestCases() throws URISyntaxException, NoSuchAlgorithmExc } - private Map> mockedPreSignedHeaders(URI uri) throws URISyntaxException { + private Map> mockedPreSignedHeaders(final URI uri) throws URISyntaxException { Map> headers = new HashMap<>(); - List acceptHeaderValue = new ArrayList<>(); - acceptHeaderValue.add("application/json"); + List acceptHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("accept", acceptHeaderValue); - List contentHeaderValue = new ArrayList<>(); - contentHeaderValue.add("application/json"); + List contentHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("content-type", contentHeaderValue); - List regionHeaderValue = new ArrayList<>(); - regionHeaderValue.add(Region.EU.toString()); + List regionHeaderValue = new ArrayList(){ + { + add(Region.EU.toString()); + } + }; headers.put("x-amz-pay-region", regionHeaderValue); - List dateHeaderValue = new ArrayList<>(); - dateHeaderValue.add("20180524T223710Z"); + List dateHeaderValue = new ArrayList(){ + { + add("20180524T223710Z"); + } + }; headers.put("x-amz-pay-date", dateHeaderValue); - List hostHeaderValue = new ArrayList<>(); - hostHeaderValue.add("pay-api.amazon.eu"); + List hostHeaderValue = new ArrayList(){ + { + add("pay-api.amazon.eu"); + } + }; headers.put("x-amz-pay-host", hostHeaderValue); return headers; } - private HashMap> getParameters(JSONObject jsonObject) throws JSONException { + private HashMap> getParameters(final JSONObject jsonObject) throws JSONException { HashMap> parameters = new HashMap<>(); Iterator keys = jsonObject.keys(); while (keys.hasNext()) { diff --git a/tst/com/amazon/pay/api/GenerateButtonSignatureTest.java b/tst/com/amazon/pay/api/GenerateButtonSignatureTest.java index 29f4de6..2881a02 100755 --- a/tst/com/amazon/pay/api/GenerateButtonSignatureTest.java +++ b/tst/com/amazon/pay/api/GenerateButtonSignatureTest.java @@ -38,11 +38,12 @@ import java.security.spec.X509EncodedKeySpec; public class GenerateButtonSignatureTest { - private WebstoreClient client; + private WebstoreClient clientWithAlgorithm; private Signature signature; - + private Signature signatureWithAlgorithm; private static final String PLAIN_TEXT = "AMZN-PAY-RSASSA-PSS\n8dec52d799607be40f82d5c8e7ecb6c171e6591c41b1111a576b16076c89381c"; + private static final String PLAIN_TEXT_WITH_V2_ALGORITHM = "AMZN-PAY-RSASSA-PSS-V2\n8dec52d799607be40f82d5c8e7ecb6c171e6591c41b1111a576b16076c89381c"; @Before public void setUp() throws Exception { @@ -55,6 +56,16 @@ public void setUp() throws Exception { .setEnvironment(Environment.SANDBOX); client = new WebstoreClient(config); + // With Algorithm set in payConfiguration + final PayConfiguration configWithAlgorithm = new PayConfiguration() + .setPrivateKey(new String(Files.readAllBytes( + Paths.get("tst/com/amazon/pay/api/unit_test_private_key.txt"))).toCharArray()) + .setRegion(Region.NA) + .setPublicKeyId("ABCDEF0000000000000") + .setEnvironment(Environment.SANDBOX) + .setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); + clientWithAlgorithm = new WebstoreClient(configWithAlgorithm); + // Load public key and prepare verifier Security.addProvider(new BouncyCastleProvider()); final PemReader pemReader = new PemReader(new StringReader(new String(Files.readAllBytes( @@ -66,22 +77,38 @@ public void setUp() throws Exception { final PublicKey publicKey = keyFactory.generatePublic(spec); final MGF1ParameterSpec mgf1ParameterSpec = new MGF1ParameterSpec(ServiceConstants.HASH_ALGORITHM); final PSSParameterSpec pssParameterSpec = new PSSParameterSpec(ServiceConstants.HASH_ALGORITHM, - ServiceConstants.MASK_GENERATION_FUNCTION, mgf1ParameterSpec, SignatureHelper.SALT_LENGTH, SignatureHelper.TRAILER_FIELD); + ServiceConstants.MASK_GENERATION_FUNCTION, mgf1ParameterSpec, 20, SignatureHelper.TRAILER_FIELD); signature = Signature.getInstance(ServiceConstants.SIGNATURE_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); signature.setParameter(pssParameterSpec); signature.initVerify(publicKey); + + final PSSParameterSpec pssParameterSpecWithAlgorithm = new PSSParameterSpec(ServiceConstants.HASH_ALGORITHM, + ServiceConstants.MASK_GENERATION_FUNCTION, mgf1ParameterSpec, 32, SignatureHelper.TRAILER_FIELD); + signatureWithAlgorithm = Signature.getInstance(ServiceConstants.SIGNATURE_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME); + signatureWithAlgorithm.setParameter(pssParameterSpecWithAlgorithm); + signatureWithAlgorithm.initVerify(publicKey); } @Test public void testButtonSignatureWithString() throws Exception { - final String payload = "{\"storeId\":\"amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"webCheckoutDetails\":{\"checkoutReviewReturnUrl\":\"https://localhost/test/CheckoutReview.php\",\"checkoutResultReturnUrl\":\"https://localhost/test/CheckoutResult.php\"}}"; - final String signatureString = client.generateButtonSignature(payload); - signature.update(PLAIN_TEXT.getBytes()); - Assert.assertTrue(signature.verify(Base64.decode(signatureString))); + testButtonSignatureWithString(client, signature, PLAIN_TEXT); + testButtonSignatureWithString(clientWithAlgorithm, signatureWithAlgorithm, PLAIN_TEXT_WITH_V2_ALGORITHM); } @Test public void testButtonSignatureWithJSONObject() throws Exception { + testButtonSignatureWithJSONObject(client, signature, PLAIN_TEXT); + testButtonSignatureWithJSONObject(clientWithAlgorithm, signatureWithAlgorithm, PLAIN_TEXT_WITH_V2_ALGORITHM); + } + + private void testButtonSignatureWithString(final WebstoreClient wsClient, final Signature sign, final String plainText) throws Exception { + final String payload = "{\"storeId\":\"amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"webCheckoutDetails\":{\"checkoutReviewReturnUrl\":\"https://localhost/test/CheckoutReview.php\",\"checkoutResultReturnUrl\":\"https://localhost/test/CheckoutResult.php\"}}"; + final String signatureString = wsClient.generateButtonSignature(payload); + sign.update(plainText.getBytes()); + Assert.assertTrue(sign.verify(Base64.decode(signatureString))); + } + + private void testButtonSignatureWithJSONObject(final WebstoreClient client, final Signature signature, final String PLAIN_TEXT) throws Exception { final JSONObject payload = new JSONObject(); payload.put("storeId", "amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); final JSONObject webCheckoutDetails = new JSONObject(); diff --git a/tst/com/amazon/pay/api/RequestSignerTest.java b/tst/com/amazon/pay/api/RequestSignerTest.java old mode 100644 new mode 100755 index 940bba6..be66f72 --- a/tst/com/amazon/pay/api/RequestSignerTest.java +++ b/tst/com/amazon/pay/api/RequestSignerTest.java @@ -38,6 +38,7 @@ public class RequestSignerTest { private static final SignatureHelper signatureHelper = Mockito.mock(SignatureHelper.class); private PayConfiguration payConfiguration = new PayConfiguration(); + private PayConfiguration payConfigurationWithAlgorithm = new PayConfiguration(); private URI uri; private String payload; private Map> parameters = new HashMap<>(); @@ -46,8 +47,10 @@ public class RequestSignerTest { private String signature; private String signedHeaderString; private String authorizationHeader; + private String authorizationHeaderWithAlgorithm; private Map> headers; private Map postSignedHeadersMap; + private Map postSignedHeadersMapWithAlgorithm; private String authToken; private Map header; @@ -59,7 +62,9 @@ public void setUp() throws Exception { PrivateKey mockKey = Mockito.mock(PrivateKey.class); mockStatic(Util.class); Mockito.when(Util.buildPrivateKey(Mockito.any(char[].class))).thenReturn(mockKey); - payConfiguration.setRegion(Region.EU).setPublicKeyId("ADGUHQIH9988").setPrivateKey(new char[] {'p','r','i','v','a','t','e','K','e','y'}); + + payConfiguration.setRegion(Region.EU).setPublicKeyId("ADGUHQIH9988").setPrivateKey(ServiceConstants.privateKeyArray); + payConfigurationWithAlgorithm.setRegion(Region.EU).setPublicKeyId("ADGUHQIH9988").setPrivateKey(ServiceConstants.privateKeyArray).setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); setUpMockValues(); } @@ -89,25 +94,42 @@ private void setUpMockValues() throws Exception { authorizationHeader = "AMZN-PAY-RSASSA-PSS PublicKeyId=ADGUHQIH9988, SignedHeaders=accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region, Signature=c062NjivoUW+TcHegKebFamCX8Cpmpmy6EiPmKwdpEuZZIpOHJYO"; + authorizationHeaderWithAlgorithm = "AMZN-PAY-RSASSA-PSS-V2 PublicKeyId=ADGUHQIH9988, SignedHeaders=accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region, Signature=c062NjivoUW+TcHegKebFamCX8Cpmpmy6EiPmKwdpEuZZIpOHJYO"; + headers = new HashMap<>(); - List acceptHeaderValue = new ArrayList<>(); - acceptHeaderValue.add("application/json"); + List acceptHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("accept", acceptHeaderValue); - List contentHeaderValue = new ArrayList<>(); - contentHeaderValue.add("application/json"); + List contentHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("content-type", contentHeaderValue); - List regionHeaderValue = new ArrayList<>(); - regionHeaderValue.add(payConfiguration.getRegion().toString()); + List regionHeaderValue = new ArrayList(){ + { + add(payConfiguration.getRegion().toString()); + } + }; headers.put("x-amz-pay-region", regionHeaderValue); - List dateHeaderValue = new ArrayList<>(); - dateHeaderValue.add(Util.getFormattedTimestamp()); + List dateHeaderValue = new ArrayList(){ + { + add(Util.getFormattedTimestamp()); + } + }; headers.put("x-amz-pay-date", dateHeaderValue); - List hostHeaderValue = new ArrayList<>(); - hostHeaderValue.add(uri.getHost()); + List hostHeaderValue = new ArrayList(){ + { + add(uri.getHost()); + } + }; headers.put("x-amz-pay-host", hostHeaderValue); for (Map.Entry entry : header.entrySet()) { @@ -118,23 +140,38 @@ private void setUpMockValues() throws Exception { PowerMockito.whenNew(SignatureHelper.class).withAnyArguments().thenReturn(signatureHelper); Mockito.when(signatureHelper.createCanonicalRequest(Mockito.anyObject(), Mockito.anyString(), Mockito.anyMap(), Mockito.anyString(), Mockito.anyMap())).thenReturn(canonicalRequest); - Mockito.when(signatureHelper.createStringToSign(Mockito.anyString())).thenReturn(stringToSign); - Mockito.when(signatureHelper.generateSignature(Mockito.anyObject(), Mockito.anyObject())).thenReturn(signature); + Mockito.when(signatureHelper.createStringToSign(Mockito.anyString(),Mockito.anyString())).thenReturn(stringToSign); + Mockito.when(signatureHelper.generateSignature(Mockito.anyObject(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(signature); Mockito.when(signatureHelper.getSignedHeadersString(Mockito.anyMap())).thenReturn(signedHeaderString); Mockito.when(signatureHelper.createPreSignedHeaders(Mockito.anyObject(), Mockito.anyMap())).thenReturn(headers); - postSignedHeadersMap = new HashMap<>(); - postSignedHeadersMap.put("accept", "application/json"); - postSignedHeadersMap.put("content-type", "application/json"); - postSignedHeadersMap.put("x-amz-pay-host", "pay-api.amazon.eu"); - postSignedHeadersMap.put("x-amz-pay-date", dateHeaderValue.get(0)); - postSignedHeadersMap.put("x-amz-pay-region", "EU"); - postSignedHeadersMap.put("authorization", authorizationHeader); - postSignedHeadersMap.put("user-agent", "amazon-pay-api-sdk-java/" + ServiceConstants.APPLICATION_LIBRARY_VERSION + " (Java/1.8.0_172; Linux/4.9.93-0.1.ac.178.67.327.metal1.x86_64)"); + postSignedHeadersMap = new HashMap(){ + { + put("accept", "application/json"); + put("content-type", "application/json"); + put("x-amz-pay-host", "pay-api.amazon.eu"); + put("x-amz-pay-date", dateHeaderValue.get(0)); + put("x-amz-pay-region", "EU"); + put("authorization", authorizationHeader); + put("user-agent", "amazon-pay-api-sdk-java/" + ServiceConstants.APPLICATION_LIBRARY_VERSION + " (Java/1.8.0_172; Linux/4.9.93-0.1.ac.178.67.327.metal1.x86_64)"); + } + }; + + postSignedHeadersMapWithAlgorithm = new HashMap(){ + { + putAll(postSignedHeadersMap); + put("authorization", authorizationHeaderWithAlgorithm); + } + }; } @Test public void signRequestWithEmptyHeader() throws Exception { + signRequestWithEmptyHeader(payConfiguration, postSignedHeadersMap); + signRequestWithEmptyHeader(payConfigurationWithAlgorithm, postSignedHeadersMapWithAlgorithm); + } + + private void signRequestWithEmptyHeader(final PayConfiguration payConfiguration, final Map postSignedHeadersMap) throws Exception { RequestSigner requestSigner = new RequestSigner(payConfiguration); Map header = new HashMap(); Map actualHeaders = requestSigner.signRequest(uri, "POST", parameters, payload, header); diff --git a/tst/com/amazon/pay/api/RequestSignerWithHeaderTest.java b/tst/com/amazon/pay/api/RequestSignerWithHeaderTest.java old mode 100644 new mode 100755 index 802741e..de6174f --- a/tst/com/amazon/pay/api/RequestSignerWithHeaderTest.java +++ b/tst/com/amazon/pay/api/RequestSignerWithHeaderTest.java @@ -39,6 +39,7 @@ public class RequestSignerWithHeaderTest { private static final SignatureHelper signatureHelper = Mockito.mock(SignatureHelper.class); private PayConfiguration payConfiguration = new PayConfiguration(); + private PayConfiguration payConfigurationWithAlgorithm = new PayConfiguration(); private URI uri; private String payload; private Map> parameters = new HashMap<>(); @@ -47,9 +48,10 @@ public class RequestSignerWithHeaderTest { private String signature; private String signedHeaderString; private String authorizationHeader; + private String authorizationHeaderWithAlgorithm; private Map> headers; - private Map postSignedHeadersMap; private Map postSignedWithHeadersMap; + private Map postSignedWithHeadersMapWithAlgorithm; private String authToken; private Map header; @@ -61,14 +63,19 @@ public void setUp() throws Exception { PrivateKey mockKey = Mockito.mock(PrivateKey.class); mockStatic(Util.class); Mockito.when(Util.buildPrivateKey(Mockito.any(char[].class))).thenReturn(mockKey); - payConfiguration.setRegion(Region.NA).setPublicKeyId("ADGUHQIH9988").setPrivateKey(new char[] {'p','r','i','v','a','t','e','K','e','y'}); + + payConfiguration.setRegion(Region.NA).setPublicKeyId("ADGUHQIH9988").setPrivateKey(ServiceConstants.privateKeyArray); + payConfigurationWithAlgorithm.setRegion(Region.NA).setPublicKeyId("ADGUHQIH9988").setPrivateKey(ServiceConstants.privateKeyArray).setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); setUpMockValues(); } private void setUpMockValues() throws Exception { authToken = "eyJhbGciOiJIbWFjU0hBMjU2IiwidHlwIjoiSldUIn0="; - header = new HashMap(); - header.put("x-amz-pay-idempotency-key", "23GGJHGB668344"); + header = new HashMap(){ + { + put("x-amz-pay-idempotency-key", "23GGJHGB668344"); + } + }; uri = new URI("https://pay-api.amazon.com/sandbox/v2/refunds"); payload = "payload"; @@ -94,25 +101,42 @@ private void setUpMockValues() throws Exception { authorizationHeader = "AMZN-PAY-RSASSA-PSS PublicKeyId=ADGUHQIH9988, SignedHeaders=accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-idempotency-key;x-amz-pay-region, Signature=BsnrBn7R4QvpWqPzElKnxK8KLm7BzglICqRsWDcj7okwVpHrpZnoOm4D3v2+naryg2vIzP2iIWvscNm3MbX7vR3nClgcB+vVUQZLEu9yg0IJA4QCiybh9etgLHSRv2jwR9ByFe9U5FMdhr7omDG3Q1lAjvvxiPHt9UtL3h1LJ7rirOuQUWp/zL5QDWsIvTty3zEKksdRJuPeCGwijwo0LPuIf2plZGv9TJ5CJBxssw3+phj5Nvo9HWuzFRkJsC1jgknO0+eSTSn5RM6R2Px0mkz3qbd5ZpSX3tIoK937vkmNZALNm/euqYnIKjviGVuSEDo1ite84foCvSqpTmiVrg=="; + authorizationHeaderWithAlgorithm = "AMZN-PAY-RSASSA-PSS-V2 PublicKeyId=ADGUHQIH9988, SignedHeaders=accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-idempotency-key;x-amz-pay-region, Signature=BsnrBn7R4QvpWqPzElKnxK8KLm7BzglICqRsWDcj7okwVpHrpZnoOm4D3v2+naryg2vIzP2iIWvscNm3MbX7vR3nClgcB+vVUQZLEu9yg0IJA4QCiybh9etgLHSRv2jwR9ByFe9U5FMdhr7omDG3Q1lAjvvxiPHt9UtL3h1LJ7rirOuQUWp/zL5QDWsIvTty3zEKksdRJuPeCGwijwo0LPuIf2plZGv9TJ5CJBxssw3+phj5Nvo9HWuzFRkJsC1jgknO0+eSTSn5RM6R2Px0mkz3qbd5ZpSX3tIoK937vkmNZALNm/euqYnIKjviGVuSEDo1ite84foCvSqpTmiVrg=="; + headers = new HashMap<>(); - List acceptHeaderValue = new ArrayList<>(); - acceptHeaderValue.add("application/json"); + List acceptHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("accept", acceptHeaderValue); - List contentHeaderValue = new ArrayList<>(); - contentHeaderValue.add("application/json"); + List contentHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("content-type", contentHeaderValue); - List regionHeaderValue = new ArrayList<>(); - regionHeaderValue.add(payConfiguration.getRegion().toString()); + List regionHeaderValue = new ArrayList(){ + { + add(payConfiguration.getRegion().toString()); + } + }; headers.put("x-amz-pay-region", regionHeaderValue); - List dateHeaderValue = new ArrayList<>(); - dateHeaderValue.add(Util.getFormattedTimestamp()); + List dateHeaderValue = new ArrayList(){ + { + add(Util.getFormattedTimestamp()); + } + }; headers.put("x-amz-pay-date", dateHeaderValue); - List hostHeaderValue = new ArrayList<>(); - hostHeaderValue.add(uri.getHost()); + List hostHeaderValue = new ArrayList(){ + { + add(uri.getHost()); + } + }; headers.put("x-amz-pay-host", hostHeaderValue); for (Map.Entry entry : header.entrySet()) { @@ -123,24 +147,39 @@ private void setUpMockValues() throws Exception { PowerMockito.whenNew(SignatureHelper.class).withAnyArguments().thenReturn(signatureHelper); Mockito.when(signatureHelper.createCanonicalRequest(Mockito.anyObject(), Mockito.anyString(), Mockito.anyMap(), Mockito.anyString(), Mockito.anyMap())).thenReturn(canonicalRequest); - Mockito.when(signatureHelper.createStringToSign(Mockito.anyString())).thenReturn(stringToSign); - Mockito.when(signatureHelper.generateSignature(Mockito.anyObject(), Mockito.anyObject())).thenReturn(signature); + Mockito.when(signatureHelper.createStringToSign(Mockito.anyString(),Mockito.anyString())).thenReturn(stringToSign); + Mockito.when(signatureHelper.generateSignature(Mockito.anyObject(), Mockito.anyObject(), Mockito.anyObject())).thenReturn(signature); Mockito.when(signatureHelper.getSignedHeadersString(Mockito.anyMap())).thenReturn(signedHeaderString); Mockito.when(signatureHelper.createPreSignedHeaders(Mockito.anyObject(), Mockito.anyMap())).thenReturn(headers); - postSignedWithHeadersMap = new HashMap<>(); - postSignedWithHeadersMap.put("accept", "application/json"); - postSignedWithHeadersMap.put("content-type", "application/json"); - postSignedWithHeadersMap.put("x-amz-pay-host", "pay-api.amazon.com"); - postSignedWithHeadersMap.put("x-amz-pay-date", dateHeaderValue.get(0)); - postSignedWithHeadersMap.put("x-amz-pay-region", "NA"); - postSignedWithHeadersMap.put("authorization", authorizationHeader); - postSignedWithHeadersMap.put("x-amz-pay-idempotency-key", "23GGJHGB668344"); - postSignedWithHeadersMap.put("user-agent", "amazon-pay-api-sdk-java/" + ServiceConstants.APPLICATION_LIBRARY_VERSION + " (Java/1.8.0_172; Linux/4.9.93-0.1.ac.178.67.327.metal1.x86_64)"); + postSignedWithHeadersMap = new HashMap(){ + { + put("accept", "application/json"); + put("content-type", "application/json"); + put("x-amz-pay-host", "pay-api.amazon.com"); + put("x-amz-pay-date", dateHeaderValue.get(0)); + put("x-amz-pay-region", "NA"); + put("authorization", authorizationHeader); + put("x-amz-pay-idempotency-key", "23GGJHGB668344"); + put("user-agent", "amazon-pay-api-sdk-java/" + ServiceConstants.APPLICATION_LIBRARY_VERSION + " (Java/1.8.0_172; Linux/4.9.93-0.1.ac.178.67.327.metal1.x86_64)"); + } + }; + + postSignedWithHeadersMapWithAlgorithm = new HashMap(){ + { + putAll(postSignedWithHeadersMap); + put("authorization", authorizationHeaderWithAlgorithm); + } + }; } @Test public void signRequestWithHeader() throws AmazonPayClientException { + signRequestWithHeader(payConfiguration, postSignedWithHeadersMap); + signRequestWithHeader(payConfigurationWithAlgorithm, postSignedWithHeadersMapWithAlgorithm); + } + + private void signRequestWithHeader(final PayConfiguration payConfiguration, final Map postSignedWithHeadersMap) throws AmazonPayClientException { RequestSigner requestSigner = new RequestSigner(payConfiguration); Map actualHeaders = requestSigner.signRequest(uri, "POST", parameters, payload, header); diff --git a/tst/com/amazon/pay/api/SignatureHelperTest.java b/tst/com/amazon/pay/api/SignatureHelperTest.java old mode 100644 new mode 100755 index 0de1516..988fc0c --- a/tst/com/amazon/pay/api/SignatureHelperTest.java +++ b/tst/com/amazon/pay/api/SignatureHelperTest.java @@ -38,7 +38,9 @@ @PrepareForTest(SignatureHelper.class) public class SignatureHelperTest { private SignatureHelper spy; + private SignatureHelper spyWithAlgorithm; private PayConfiguration payConfiguration = new PayConfiguration(); + private PayConfiguration payConfigurationWithAlgorithm = new PayConfiguration(); URI uri; private Map> preSignedHeaders; private Map header; @@ -50,78 +52,78 @@ public void setUp() throws Exception { uri = new URI("https://pay-api.amazon.eu/live/v2/in-store/refund"); header = new HashMap(); preSignedHeaders = mockedPreSignedHeaders(uri, header); + + //With Algorithm set in payConfiguration + payConfigurationWithAlgorithm.setRegion(Region.EU).setPublicKeyId("").setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); + spyWithAlgorithm = spy(new SignatureHelper(payConfigurationWithAlgorithm)); } @Test public void createStringToSign() throws Exception { - String canonicalRequest = "POST\n/live/v2/in-store/refund\naccept:application/json\ncontent-type:application/json\naccept;content-type\n95b0d65e9efb9f0b9e8c2f3b7744628c765477"; - PowerMockito.when(spy, method(SignatureHelper.class, "hashThenHexEncode")).withArguments(canonicalRequest).thenReturn("95b0d65e9efb9f0b9e8c2f3b77"); - - String stringToSign = spy.createStringToSign(canonicalRequest); - String expectedString = "AMZN-PAY-RSASSA-PSS" + "\n" + "95b0d65e9efb9f0b9e8c2f3b77"; - - Assert.assertEquals(stringToSign, expectedString); + createStringToSign(payConfiguration, spy); + createStringToSign(payConfigurationWithAlgorithm, spyWithAlgorithm); } @Test public void createPreSignedHeaders() throws Exception { - Map> actualHeaders = spy.createPreSignedHeaders(uri, header); - Assert.assertEquals(preSignedHeaders, actualHeaders); - + createPreSignedHeaders(spy); + createPreSignedHeaders(spyWithAlgorithm); } @Test public void getSignedHeadersString() throws Exception { - String expectedHeadersString = "accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region"; - - String actualHeadersString = spy.getSignedHeadersString(preSignedHeaders); - Assert.assertEquals(expectedHeadersString, actualHeadersString); + getSignedHeadersString(spy); + getSignedHeadersString(spyWithAlgorithm); } @Test public void getCanonicalizedHeaderString() throws Exception { - String expectedCanonicalHeaderString = "accept:application/json\ncontent-type:application/json\nx-amz-pay-date:" + preSignedHeaders.get("x-amz-pay-date").get(0) + "\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n"; - String actualCanonicalHeaderString = spy.getCanonicalizedHeaderString(preSignedHeaders); - - Assert.assertEquals(expectedCanonicalHeaderString, actualCanonicalHeaderString); + getCanonicalizedHeaderString(spy); + getCanonicalizedHeaderString(spyWithAlgorithm); } @Test public void getCanonicalizedQueryString() throws Exception { - Map> parametersMap = new HashMap<>(); - List amount = new ArrayList<>(); - amount.add("100.50"); - parametersMap.put("amount", amount); - List info = new ArrayList<>(); - info.add("Information about order"); - parametersMap.put("customInformation", info); - String expectedCanonicalQueryString = "amount=100.50&customInformation=Information%20about%20order"; - String actualCanonicalQueryString = spy.getCanonicalizedQueryString(parametersMap); - - Assert.assertEquals(expectedCanonicalQueryString, actualCanonicalQueryString); + getCanonicalizedQueryString(spy); + getCanonicalizedQueryString(spyWithAlgorithm); } - private Map> mockedPreSignedHeaders(URI uri, Map header) throws URISyntaxException { + private Map> mockedPreSignedHeaders(final URI uri, final Map header) throws URISyntaxException { Map> headers = new HashMap<>(); - List acceptHeaderValue = new ArrayList<>(); - acceptHeaderValue.add("application/json"); + List acceptHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("accept", acceptHeaderValue); - List contentHeaderValue = new ArrayList<>(); - contentHeaderValue.add("application/json"); + List contentHeaderValue = new ArrayList(){ + { + add("application/json"); + } + }; headers.put("content-type", contentHeaderValue); - List regionHeaderValue = new ArrayList<>(); - regionHeaderValue.add(payConfiguration.getRegion().toString()); + List regionHeaderValue = new ArrayList(){ + { + add(payConfiguration.getRegion().toString()); + } + }; headers.put("x-amz-pay-region", regionHeaderValue); - List dateHeaderValue = new ArrayList<>(); - dateHeaderValue.add(Util.getFormattedTimestamp()); + List dateHeaderValue = new ArrayList(){ + { + add(Util.getFormattedTimestamp()); + } + }; headers.put("x-amz-pay-date", dateHeaderValue); - List hostHeaderValue = new ArrayList<>(); - hostHeaderValue.add(uri.getHost()); + List hostHeaderValue = new ArrayList(){ + { + add(uri.getHost()); + } + }; headers.put("x-amz-pay-host", hostHeaderValue); if (header.isEmpty() || header == null) @@ -135,4 +137,54 @@ private Map> mockedPreSignedHeaders(URI uri, Map> actualHeaders = spy.createPreSignedHeaders(uri, header); + Assert.assertEquals(preSignedHeaders, actualHeaders); + } + + private void getSignedHeadersString(final SignatureHelper spy) throws Exception { + String expectedHeadersString = "accept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region"; + + String actualHeadersString = spy.getSignedHeadersString(preSignedHeaders); + Assert.assertEquals(expectedHeadersString, actualHeadersString); + + } + + private void getCanonicalizedHeaderString(final SignatureHelper spy) throws Exception { + String expectedCanonicalHeaderString = "accept:application/json\ncontent-type:application/json\nx-amz-pay-date:" + preSignedHeaders.get("x-amz-pay-date").get(0) + "\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n"; + String actualCanonicalHeaderString = spy.getCanonicalizedHeaderString(preSignedHeaders); + + Assert.assertEquals(expectedCanonicalHeaderString, actualCanonicalHeaderString); + } + + + private void getCanonicalizedQueryString(final SignatureHelper spy) throws Exception { + Map> parametersMap = new HashMap<>(); + List amount = new ArrayList(){ + { + add("100.50"); + } + }; + parametersMap.put("amount", amount); + List info = new ArrayList(){ + { + add("Information about order"); + } + }; + parametersMap.put("customInformation", info); + String expectedCanonicalQueryString = "amount=100.50&customInformation=Information%20about%20order"; + String actualCanonicalQueryString = spy.getCanonicalizedQueryString(parametersMap); + + Assert.assertEquals(expectedCanonicalQueryString, actualCanonicalQueryString); + } + } diff --git a/tst/com/amazon/pay/api/UtilTest.java b/tst/com/amazon/pay/api/UtilTest.java old mode 100644 new mode 100755 index 4ab7b9d..2746e05 --- a/tst/com/amazon/pay/api/UtilTest.java +++ b/tst/com/amazon/pay/api/UtilTest.java @@ -35,6 +35,10 @@ import java.util.List; import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONException; + public class UtilTest { @Test public void urlEncode() throws Exception { @@ -116,6 +120,14 @@ public void getServiceURI() throws Exception { actualURL = Util.getServiceURI(payConfiguration3, ServiceConstants.INSTORE_REFUND); Assert.assertEquals(expectedURL, actualURL); + // With Algorithm set in payConfiguration + final PayConfiguration payConfigurationWithAlgorithm = new PayConfiguration() + .setPublicKeyId("XXXXXXXXXXXXXXXXXXXXXXXX") + .setRegion(Region.EU) + .setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); + + actualURL = Util.getServiceURI(payConfigurationWithAlgorithm, ServiceConstants.INSTORE_REFUND); + Assert.assertEquals(expectedURL, actualURL); } @Test @@ -136,6 +148,14 @@ public void getServiceURIForEnvironmentSpecificKeys() throws Exception { actualURL = Util.getServiceURI(payConfiguration2, ServiceConstants.INSTORE_REFUND); Assert.assertEquals(expectedURL, actualURL); + // With Algorithm set in payConfiguration + final PayConfiguration payConfigurationWithAlgorithm = new PayConfiguration() + .setPublicKeyId("SANDBOX-XXXXXXXXXXXXXXXXXXXXXXXX") + .setRegion(Region.EU) + .setAlgorithm("AMZN-PAY-RSASSA-PSS-V2"); + + actualURL = Util.getServiceURI(payConfigurationWithAlgorithm, ServiceConstants.INSTORE_REFUND); + Assert.assertEquals(expectedURL, actualURL); } @Test @@ -191,13 +211,63 @@ public void testGetCloseableHttpClientWithProxyMethod() { .setProxyUser(proxyUser) .setProxyPassword(proxyPassword); final PayConfiguration payConfiguration = new PayConfiguration() - .setProxySettings(proxySettings); - final CloseableHttpClient httpClient = Util.getCloseableHttpClientWithProxy(proxySettings); + .setProxySettings(proxySettings) + .setClientConnections(ServiceConstants.MAX_CLIENT_CONNECTIONS); + final CloseableHttpClient httpClient = Util.getCloseableHttpClientWithProxy(proxySettings, payConfiguration); // Assertions Assert.assertEquals(proxyhost, payConfiguration.getProxySettings().getProxyHost()); Assert.assertEquals(proxyPort, payConfiguration.getProxySettings().getProxyPort()); Assert.assertEquals(proxyUser, payConfiguration.getProxySettings().getProxyUser()); Assert.assertEquals(proxyPassword, payConfiguration.getProxySettings().getProxyPassword()); + assertClientConnections(payConfiguration); + Assert.assertNotNull(httpClient); + } + + @Test + public void testEnhanceResponseWithShippingAddressList() throws AmazonPayClientException, JSONException { + JSONObject testShippingAddressListResponse = new JSONObject(); + testShippingAddressListResponse.put("shippingAddressList", new JSONArray().put("{\"addressId\":\"amzn1.address.ABC\",\"name\":\"DEF\",\"addressLine1\":\"GHI\",\"addressLine2\":\"JKL\",\"addressLine3\":null,\"city\":null,\"county\":null,\"district\":null,\"stateOrRegion\":\"MNO\",\"postalCode\":\"123-4567\",\"countryCode\":\"JP\",\"phoneNumber\":\"8910111213\"}")); + + final AmazonPayResponse testCheckoutSessionResponse = new AmazonPayResponse(); + testCheckoutSessionResponse.setResponse(testShippingAddressListResponse); + + final AmazonPayResponse actualCheckoutSessionResponseAfterEnhancing = Util.enhanceResponseWithShippingAddressList(testCheckoutSessionResponse); + + final JSONObject expectedShippingAddress = new JSONObject(); + expectedShippingAddress.put("stateOrRegion", "MNO"); + expectedShippingAddress.put("phoneNumber", "8910111213"); + expectedShippingAddress.put("city", JSONObject.NULL); + expectedShippingAddress.put("countryCode", "JP"); + expectedShippingAddress.put("district", JSONObject.NULL); + expectedShippingAddress.put("postalCode", "123-4567"); + expectedShippingAddress.put("name", "DEF"); + expectedShippingAddress.put("county", JSONObject.NULL); + expectedShippingAddress.put("addressLine1", "GHI"); + expectedShippingAddress.put("addressLine2", "JKL"); + expectedShippingAddress.put("addressLine3", JSONObject.NULL); + expectedShippingAddress.put("addressId", "amzn1.address.ABC"); + + JSONObject expectedShippingAddressListResponse = new JSONObject(); + expectedShippingAddressListResponse.put("shippingAddressList", new JSONArray().put(expectedShippingAddress)); + + AmazonPayResponse expectedCheckoutSessionResponse = new AmazonPayResponse(); + expectedCheckoutSessionResponse.setResponse(expectedShippingAddressListResponse); + expectedCheckoutSessionResponse.setRawResponse(expectedShippingAddressListResponse.toString()); + + Assert.assertEquals(expectedCheckoutSessionResponse.getRawResponse(), actualCheckoutSessionResponseAfterEnhancing.getRawResponse()); + } + + @Test + public void testGetHttpClientWithConnectionPool() { + final PayConfiguration payConfiguration = new PayConfiguration() + .setClientConnections(ServiceConstants.MAX_CLIENT_CONNECTIONS); + final CloseableHttpClient httpClient = Util.getHttpClientWithConnectionPool(payConfiguration); + // Assertions + assertClientConnections(payConfiguration); Assert.assertNotNull(httpClient); } + + public void assertClientConnections(final PayConfiguration payConfiguration) { + Assert.assertEquals(ServiceConstants.MAX_CLIENT_CONNECTIONS, payConfiguration.getClientConnections()); + } } diff --git a/tst/com/amazon/pay/api/testdata.js b/tst/com/amazon/pay/api/testdata.js old mode 100644 new mode 100755 index 4e2d7e3..e1074cc --- a/tst/com/amazon/pay/api/testdata.js +++ b/tst/com/amazon/pay/api/testdata.js @@ -194,4 +194,199 @@ "canonicalRequest" : "POST\n/\nf%20oo=b%20ar\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "stringToSign" : "AMZN-PAY-RSASSA-PSS\n99be3aeb19b5a1aebafc807d59f3740d3e604ae31cf928592fa70739ea64bb32" }, + { + "name" : "get-vanilla", + "uri" : "/", + "method" : "GET", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "GET\n/\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nbb49552975c396f1e49d5510e1555b8b0627dc491bc61aaefdb561f202ad8020" + }, + + { + "name" : "get-slashes", + "uri" : "//foo//", + "method" : "GET", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "GET\n/\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nbb49552975c396f1e49d5510e1555b8b0627dc491bc61aaefdb561f202ad8020" + }, + + { + "name" : "get-empty", + "uri" : "", + "method" : "GET", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "GET\n/\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nbb49552975c396f1e49d5510e1555b8b0627dc491bc61aaefdb561f202ad8020" + }, + + { + "name" : "get-unreserved", + "uri" : "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + "method" : "GET", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "GET\n/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\na0e5dd63e175587f29d51c6b08be5c746a568209ac66ab1f967e0ba515aa80bc" + }, + + { + "name" : "get-vanilla-query", + "uri" : "/", + "method" : "GET", + "parameters" : {}, + "algorithm" : "AMZN-PAY-RSASSA-PSS-V2", + "payload" : "", + "canonicalRequest" : "GET\n/\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nbb49552975c396f1e49d5510e1555b8b0627dc491bc61aaefdb561f202ad8020" + }, + + { + "name" : "get-utf8", + "uri" : "/\u1234", + "method" : "GET", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "GET\n/%E1%88%B4\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n77f6544c8fb336d430895659da1e30415a1558b4b013f4d7926bb3a6275f701d" + + }, + + { + "name" : "get-vanilla-utf8-query", + "uri" : "/", + "method" : "GET", + "parameters" : { + "\u1234" : [ "bar" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\n%E1%88%B4=bar\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nef1007307504f2ca9f0b6310b04056f5d3d6d18e7bf3d90663133f19ec132ecc" + }, + + { + "name" : "post-vanilla", + "uri" : "/", + "method" : "POST", + "parameters" : {}, + "payload" : "", + "canonicalRequest" : "POST\n/\n\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n54156f5badd6da7fa630b2c334c808ad376530001d334ae9960457215aa626d3" + }, + + { + "name" : "post-vanilla-query", + "uri" : "/", + "method" : "POST", + "parameters" : { + "foo" : [ "bar" ] + }, + "payload" : "", + "canonicalRequest" : "POST\n/\nfoo=bar\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\ncc82525870b2d03666c287fd8b2c0d2fc6fa1693d9f357d27f7cac8cbcc12832" + }, + + { + "name" : "get-vanilla-empty-query-key", + "uri" : "/", + "method" : "GET", + "parameters" : { + "foo" : [ "bar" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\nfoo=bar\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n6837f80baf1801c75db8119cffbadc26a89d20adb9cf3f3de4ce0fac31b4ae52" + }, + + { + "name" : "get-vanilla-query-order-value", + "uri" : "/", + "method" : "GET", + "parameters" : { + "foo" : [ "b", "a" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\nfoo=a&foo=b\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\na33266ec31c7f0ca8a0738eb49fb8c81319f14374a0b86d52e6911a9748a6d42" + }, + + { + "name" : "get-vanilla-query-order-key", + "uri" : "/", + "method" : "GET", + "parameters" : { + "a" : [ "foo" ], + "b" : [ "foo" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\na=foo&b=foo\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n6615ba7ca3734e97b35013f77c74d111ce2fa63ab52be161ad41bcd4b010120f" + }, + + { + "name" : "get-vanilla-query-order-sort-by-key", + "uri" : "/", + "method" : "GET", + "parameters" : { + "A.1" : [ "foo" ], + "A.2" : [ "foo" ], + "A.10" : [ "foo" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\nA.1=foo&A.10=foo&A.2=foo\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n29d908d70c4bb50d62272976787245b52ed52e569fdae56ef3aa6d8979978ee7" + }, + + { + "name" : "get-vanilla-query-order-key-case", + "uri" : "/", + "method" : "GET", + "parameters" : { + "foo" : [ "Zoo", "aha" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\nfoo=Zoo&foo=aha\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\nf379cbd2e2fc9d5d50667a07ec22996335f731b474bb8bd366647d05d958c9d5" + }, + + { + "name" : "get-vanilla-query-unreserved", + "uri" : "/", + "method" : "GET", + "parameters" : { + "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" : [ "-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ] + }, + "payload" : "", + "canonicalRequest" : "GET\n/\n-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n892abddd6e79db3feb5695b318af639c3e863494f6b4df32ec24fff4c1562a73" + }, + + { + "name" : "post-vanilla-query-nonunreserved", + "uri" : "/", + "method" : "POST", + "parameters" : { + "@#$%^&+=/,?><`\";:\\|][{} " : [ "@#$%^&+=/,?><`\";:\\|][{} " ] + }, + "algorithm" : "AMZN-PAY-RSASSA-PSS-V2", + "payload" : "", + "canonicalRequest" : "POST\n/\n%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D%20=%40%23%24%25%5E%26%2B%3D%2F%2C%3F%3E%3C%60%22%3B%3A%5C%7C%5D%5B%7B%7D%20\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n66bbbf2b5d6d297a9393abbc0b4f9d62e30666bb0ec0dea8f1d4e45551440e4c" + }, + + { + "name" : "post-vanilla-query-space", + "uri" : "/", + "method" : "POST", + "parameters" : { + "f oo" : [ "b ar" ] + }, + "payload" : "", + "canonicalRequest" : "POST\n/\nf%20oo=b%20ar\naccept:application/json\ncontent-type:application/json\nx-amz-pay-date:20180524T223710Z\nx-amz-pay-host:pay-api.amazon.eu\nx-amz-pay-region:EU\n\naccept;content-type;x-amz-pay-date;x-amz-pay-host;x-amz-pay-region\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stringToSign" : "AMZN-PAY-RSASSA-PSS-V2\n99be3aeb19b5a1aebafc807d59f3740d3e604ae31cf928592fa70739ea64bb32" + }, ] \ No newline at end of file