diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5026f4684..fdee26b77 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @rkewlani @martinsrenato @Aleffio @abhilash-adyen @saquibsayyad @AlexandrosMor +* @rkewlani @martinsrenato @Aleffio @abhilash-adyen @saquibsayyad diff --git a/README.md b/README.md index 4406d0812..75f497dea 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ You can use Maven and add this dependency to your project's POM: com.adyen adyen-java-api-library - 15.0.1 + 15.0.2 ``` diff --git a/pom.xml b/pom.xml index 5dafb27e7..3d21bf269 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.adyen adyen-java-api-library jar - 15.0.1 + 15.0.2 Adyen Java API Library Adyen API Client Library for Java https://github.com/adyen/adyen-java-api-library diff --git a/src/main/java/com/adyen/Client.java b/src/main/java/com/adyen/Client.java index 03225bd54..fbe8d69f9 100644 --- a/src/main/java/com/adyen/Client.java +++ b/src/main/java/com/adyen/Client.java @@ -45,7 +45,7 @@ public class Client { public static final String MARKETPAY_NOTIFICATION_API_VERSION = "v6"; public static final String MARKETPAY_HOP_API_VERSION = "v6"; public static final String LIB_NAME = "adyen-java-api-library"; - public static final String LIB_VERSION = "15.0.1"; + public static final String LIB_VERSION = "15.0.2"; public static final String CHECKOUT_ENDPOINT_TEST = "https://checkout-test.adyen.com/checkout"; public static final String CHECKOUT_ENDPOINT_LIVE_SUFFIX = "-checkout-live.adyenpayments.com/checkout"; public static final String CHECKOUT_API_VERSION = "v67"; diff --git a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java index 89f2f8796..6bee0df8d 100644 --- a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java +++ b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java @@ -48,7 +48,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URI; @@ -70,7 +69,6 @@ public class AdyenHttpClient implements ClientInterface { private static final String CHARSET = "UTF-8"; private static final String TERMINAL_CERTIFICATE_ALIAS = "TerminalCertificate"; - private static final String JAVA_KEYSTORE = "JKS"; private static final String SSL = "SSL"; private Proxy proxy; @@ -150,25 +148,26 @@ private void setHeaders(Config config, RequestOptions requestOptions, HttpReques } } - private HttpRequestBase createHttpRequestBase(URI endpoint, String requestBody, ApiConstants.HttpMethod httpMethod) throws HTTPClientException { - try { - switch (httpMethod) { - case GET: - return new HttpGet(endpoint); - case PATCH: - HttpPatch httpPatch = new HttpPatch(endpoint); - httpPatch.setEntity(new StringEntity(requestBody)); - return httpPatch; - case DELETE: - new HttpDelete(endpoint); - default: - // Default to POST if httpMethod is not provided - HttpPost httpPost = new HttpPost(endpoint); - httpPost.setEntity(new StringEntity(requestBody)); - return httpPost; - } - } catch (UnsupportedEncodingException e) { - throw new HTTPClientException("Unsupported encoding", e); + private HttpRequestBase createHttpRequestBase(URI endpoint, String requestBody, ApiConstants.HttpMethod httpMethod) { + StringEntity requestEntity = null; + if (requestBody != null && !requestBody.isEmpty()) { + requestEntity = new StringEntity(requestBody, CHARSET); + } + + switch (httpMethod) { + case GET: + return new HttpGet(endpoint); + case PATCH: + HttpPatch httpPatch = new HttpPatch(endpoint); + httpPatch.setEntity(requestEntity); + return httpPatch; + case DELETE: + return new HttpDelete(endpoint); + default: + // Default to POST if httpMethod is not provided + HttpPost httpPost = new HttpPost(endpoint); + httpPost.setEntity(requestEntity); + return httpPost; } } @@ -193,7 +192,7 @@ private CloseableHttpClient createCloseableHttpClient(Config config) throws HTTP HttpClientBuilder httpClientBuilder = HttpClients.custom(); // Create new KeyStore for the terminal certificate try { - KeyStore keyStore = KeyStore.getInstance(JAVA_KEYSTORE); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); keyStore.setCertificateEntry(TERMINAL_CERTIFICATE_ALIAS, config.getTerminalCertificate()); @@ -275,4 +274,4 @@ private void setBasicAuthentication(HttpUriRequest httpUriRequest, String userna httpUriRequest.addHeader("Authorization", "Basic " + authStringEnc); } -} \ No newline at end of file +} diff --git a/src/main/java/com/adyen/model/checkout/CreatePaymentLinkRequest.java b/src/main/java/com/adyen/model/checkout/CreatePaymentLinkRequest.java index 87b9b38e3..e98fc6ee0 100755 --- a/src/main/java/com/adyen/model/checkout/CreatePaymentLinkRequest.java +++ b/src/main/java/com/adyen/model/checkout/CreatePaymentLinkRequest.java @@ -14,12 +14,15 @@ * * Adyen Java API Library * - * Copyright (c) 2020 Adyen B.V. + * Copyright (c) 2021 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. */ package com.adyen.model.checkout; +import java.util.Date; +import java.util.Objects; + import com.adyen.model.Address; import com.adyen.model.Amount; import com.adyen.model.Name; @@ -32,17 +35,11 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; - import java.io.IOException; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; - -import static com.adyen.util.Util.toIndentedString; - /** * CreatePaymentLinkRequest */ @@ -79,6 +76,9 @@ public class CreatePaymentLinkRequest { @SerializedName("expiresAt") private String expiresAt = null; + @SerializedName("installmentOptions") + private Map installmentOptions = null; + @SerializedName("lineItems") private List lineItems = null; @@ -92,7 +92,7 @@ public class CreatePaymentLinkRequest { private Map metadata = null; /** - * Defines a recurring payment type. Allowed values: * `Subscription` – A transaction for a fixed or variable amount, which follows a fixed schedule. * `CardOnFile` – With a card-on-file (CoF) transaction, card details are stored to enable one-click or omnichannel journeys, or simply to streamline the checkout process. Any subscription not following a fixed schedule is also considered a card-on-file transaction. * `UnscheduledCardOnFile` – An unscheduled card-on-file (UCoF) transaction is a transaction that occurs on a non-fixed schedule and/or has variable amounts. For example, automatic top-ups when a cardholder's balance drops below a certain amount. + * Defines a recurring payment type. Possible values: * **Subscription** – A transaction for a fixed or variable amount, which follows a fixed schedule. * **CardOnFile** – With a card-on-file (CoF) transaction, card details are stored to enable one-click or omnichannel journeys, or simply to streamline the checkout process. Any subscription not following a fixed schedule is also considered a card-on-file transaction. * **UnscheduledCardOnFile** – An unscheduled card-on-file (UCoF) transaction is a transaction that occurs on a non-fixed schedule and/or has variable amounts. For example, automatic top-ups when a cardholder's balance drops below a certain amount. */ @JsonAdapter(RecurringProcessingModelEnum.Adapter.class) public enum RecurringProcessingModelEnum { @@ -106,7 +106,6 @@ public enum RecurringProcessingModelEnum { RecurringProcessingModelEnum(String value) { this.value = value; } - public String getValue() { return value; } @@ -115,7 +114,6 @@ public String getValue() { public String toString() { return String.valueOf(value); } - public static RecurringProcessingModelEnum fromValue(String text) { for (RecurringProcessingModelEnum b : RecurringProcessingModelEnum.values()) { if (String.valueOf(b.value).equals(text)) { @@ -124,7 +122,6 @@ public static RecurringProcessingModelEnum fromValue(String text) { } return null; } - public static class Adapter extends TypeAdapter { @Override public void write(final JsonWriter jsonWriter, final RecurringProcessingModelEnum enumeration) throws IOException { @@ -145,6 +142,56 @@ public RecurringProcessingModelEnum read(final JsonReader jsonReader) throws IOE @SerializedName("reference") private String reference = null; + /** + * Gets or Sets requiredShopperFields + */ + @JsonAdapter(RequiredShopperFieldsEnum.Adapter.class) + public enum RequiredShopperFieldsEnum { + BILLINGADDRESS("billingAddress"), + DELIVERYADDRESS("deliveryAddress"), + SHOPPEREMAIL("shopperEmail"), + SHOPPERNAME("shopperName"), + TELEPHONENUMBER("telephoneNumber"); + + @JsonValue + private String value; + + RequiredShopperFieldsEnum(String value) { + this.value = value; + } + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + public static RequiredShopperFieldsEnum fromValue(String text) { + for (RequiredShopperFieldsEnum b : RequiredShopperFieldsEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + public static class Adapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final RequiredShopperFieldsEnum enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public RequiredShopperFieldsEnum read(final JsonReader jsonReader) throws IOException { + Object value = jsonReader.nextString(); + return RequiredShopperFieldsEnum.fromValue(String.valueOf(value)); + } + } + } + + @SerializedName("requiredShopperFields") + private List requiredShopperFields = null; + @SerializedName("returnUrl") private String returnUrl = null; @@ -189,8 +236,7 @@ public CreatePaymentLinkRequest addAllowedPaymentMethodsItem(String allowedPayme } /** - * List of payments methods to be presented to the shopper. To refer to payment methods, use their `paymentMethod.type` from [Payment methods overview](https://docs.adyen.com/payment-methods). Example: `\"allowedPaymentMethods\":[\"ideal\",\"giropay\"]` - * + * List of payment methods to be presented to the shopper. To refer to payment methods, use their `paymentMethod.type` from [Payment methods overview](https://docs.adyen.com/payment-methods). Example: `\"allowedPaymentMethods\":[\"ideal\",\"giropay\"]` * @return allowedPaymentMethods **/ public List getAllowedPaymentMethods() { @@ -208,7 +254,6 @@ public CreatePaymentLinkRequest amount(Amount amount) { /** * Get amount - * * @return amount **/ public Amount getAmount() { @@ -226,7 +271,6 @@ public CreatePaymentLinkRequest applicationInfo(ApplicationInfo applicationInfo) /** * Get applicationInfo - * * @return applicationInfo **/ public ApplicationInfo getApplicationInfo() { @@ -244,7 +288,6 @@ public CreatePaymentLinkRequest billingAddress(Address billingAddress) { /** * Get billingAddress - * * @return billingAddress **/ public Address getBillingAddress() { @@ -269,8 +312,7 @@ public CreatePaymentLinkRequest addBlockedPaymentMethodsItem(String blockedPayme } /** - * List of payments methods to be hidden from the shopper. To refer to payment methods, use their `paymentMethod.type` from [Payment methods overview](https://docs.adyen.com/payment-methods). Example: `\"blockedPaymentMethods\":[\"ideal\",\"giropay\"]` - * + * List of payment methods to be hidden from the shopper. To refer to payment methods, use their `paymentMethod.type` from [Payment methods overview](https://docs.adyen.com/payment-methods). Example: `\"blockedPaymentMethods\":[\"ideal\",\"giropay\"]` * @return blockedPaymentMethods **/ public List getBlockedPaymentMethods() { @@ -288,7 +330,6 @@ public CreatePaymentLinkRequest countryCode(String countryCode) { /** * The shopper's two-letter country code. - * * @return countryCode **/ public String getCountryCode() { @@ -306,7 +347,6 @@ public CreatePaymentLinkRequest deliverAt(Date deliverAt) { /** * The date and time the purchased goods should be delivered. - * * @return deliverAt **/ public Date getDeliverAt() { @@ -324,7 +364,6 @@ public CreatePaymentLinkRequest deliveryAddress(Address deliveryAddress) { /** * Get deliveryAddress - * * @return deliveryAddress **/ public Address getDeliveryAddress() { @@ -342,7 +381,6 @@ public CreatePaymentLinkRequest description(String description) { /** * A short description visible on the payment page. Maximum length: 280 characters. - * * @return description **/ public String getDescription() { @@ -359,8 +397,7 @@ public CreatePaymentLinkRequest expiresAt(String expiresAt) { } /** - * The date that the payment link expires, in ISO 8601 format. For example `2019-11-23T12:25:28Z`, or `2020-05-27T20:25:28+08:00`. Maximum expiry date should be 30 days from when the payment link is created. If not provided, the default expiry is set to 24 hours after the payment link is created. - * + * The date that the payment link expires, in ISO 8601 format. For example `2019-11-23T12:25:28Z`, or `2020-05-27T20:25:28+08:00`. Maximum expiry date should be 70 days from when the payment link is created. If not provided, the default expiry is set to 24 hours after the payment link is created. * @return expiresAt **/ public String getExpiresAt() { @@ -371,6 +408,31 @@ public void setExpiresAt(String expiresAt) { this.expiresAt = expiresAt; } + public CreatePaymentLinkRequest installmentOptions(Map installmentOptions) { + this.installmentOptions = installmentOptions; + return this; + } + + public CreatePaymentLinkRequest putInstallmentOptionsItem(String key, InstallmentOption installmentOptionsItem) { + if (this.installmentOptions == null) { + this.installmentOptions = new HashMap<>(); + } + this.installmentOptions.put(key, installmentOptionsItem); + return this; + } + + /** + * A set of key-value pairs that specifies the installment options available per payment method. The key must be a payment method name in lowercase. For example, **card** to specify installment options for all cards, or **visa** or **mc**. The value must be an object containing the installment options. + * @return installmentOptions + **/ + public Map getInstallmentOptions() { + return installmentOptions; + } + + public void setInstallmentOptions(Map installmentOptions) { + this.installmentOptions = installmentOptions; + } + public CreatePaymentLinkRequest lineItems(List lineItems) { this.lineItems = lineItems; return this; @@ -386,7 +448,6 @@ public CreatePaymentLinkRequest addLineItemsItem(LineItem lineItemsItem) { /** * Price and product information about the purchased items, to be included on the invoice sent to the shopper. This parameter is required for open invoice (_buy now, pay later_) payment methods such AfterPay, Klarna, RatePay, and Zip. - * * @return lineItems **/ public List getLineItems() { @@ -404,7 +465,6 @@ public CreatePaymentLinkRequest merchantAccount(String merchantAccount) { /** * The merchant account identifier for which the payment link is created. - * * @return merchantAccount **/ public String getMerchantAccount() { @@ -422,7 +482,6 @@ public CreatePaymentLinkRequest merchantOrderReference(String merchantOrderRefer /** * This reference allows linking multiple transactions to each other for reporting purposes (for example, order auth-rate). The reference should be unique per billing cycle. - * * @return merchantOrderReference **/ public String getMerchantOrderReference() { @@ -447,8 +506,7 @@ public CreatePaymentLinkRequest putMetadataItem(String key, String metadataItem) } /** - * Metadata consists of entries, each of which includes a key and a value. Limitations: * Maximum 20 key-value pairs per request. When exceeding, the \"177\" error occurs: \"Metadata size exceeds limit\" * Maximum 20 characters per key. When exceeding, the \"178\" error occurs: \"Metadata key size exceeds limit\" * A key cannot have the name `checkout.linkId`. Whatever value is present under that key is going to be replaced by the real link id - * + * Metadata consists of entries, each of which includes a key and a value. Limitations: * Maximum 20 key-value pairs per request. Otherwise, error \"177\" occurs: \"Metadata size exceeds limit\" * Maximum 20 characters per key. Otherwise, error \"178\" occurs: \"Metadata key size exceeds limit\" * A key cannot have the name `checkout.linkId`. Any value that you provide with this key is going to be replaced by the real payment link ID. * @return metadata **/ public Map getMetadata() { @@ -465,8 +523,7 @@ public CreatePaymentLinkRequest recurringProcessingModel(RecurringProcessingMode } /** - * Defines a recurring payment type. Allowed values: * `Subscription` – A transaction for a fixed or variable amount, which follows a fixed schedule. * `CardOnFile` – With a card-on-file (CoF) transaction, card details are stored to enable one-click or omnichannel journeys, or simply to streamline the checkout process. Any subscription not following a fixed schedule is also considered a card-on-file transaction. * `UnscheduledCardOnFile` – An unscheduled card-on-file (UCoF) transaction is a transaction that occurs on a non-fixed schedule and/or has variable amounts. For example, automatic top-ups when a cardholder's balance drops below a certain amount. - * + * Defines a recurring payment type. Possible values: * **Subscription** – A transaction for a fixed or variable amount, which follows a fixed schedule. * **CardOnFile** – With a card-on-file (CoF) transaction, card details are stored to enable one-click or omnichannel journeys, or simply to streamline the checkout process. Any subscription not following a fixed schedule is also considered a card-on-file transaction. * **UnscheduledCardOnFile** – An unscheduled card-on-file (UCoF) transaction is a transaction that occurs on a non-fixed schedule and/or has variable amounts. For example, automatic top-ups when a cardholder's balance drops below a certain amount. * @return recurringProcessingModel **/ public RecurringProcessingModelEnum getRecurringProcessingModel() { @@ -484,7 +541,6 @@ public CreatePaymentLinkRequest reference(String reference) { /** * A reference that is used to uniquely identify the payment in future communications about the payment status. - * * @return reference **/ public String getReference() { @@ -495,6 +551,31 @@ public void setReference(String reference) { this.reference = reference; } + public CreatePaymentLinkRequest requiredShopperFields(List requiredShopperFields) { + this.requiredShopperFields = requiredShopperFields; + return this; + } + + public CreatePaymentLinkRequest addRequiredShopperFieldsItem(RequiredShopperFieldsEnum requiredShopperFieldsItem) { + if (this.requiredShopperFields == null) { + this.requiredShopperFields = new ArrayList<>(); + } + this.requiredShopperFields.add(requiredShopperFieldsItem); + return this; + } + + /** + * List of fields that the shopper has to provide on the payment page before completing the payment. For more information, refer to [Provide shopper information](https://docs.adyen.com/online-payments/pay-by-link/api#shopper-information). Possible values: * **billingAddress** – The address where to send the invoice. * **deliveryAddress** – The address where the purchased goods should be delivered. * **shopperEmail** – The shopper's email address. * **shopperName** – The shopper's full name. * **telephoneNumber** – The shopper's phone number. + * @return requiredShopperFields + **/ + public List getRequiredShopperFields() { + return requiredShopperFields; + } + + public void setRequiredShopperFields(List requiredShopperFields) { + this.requiredShopperFields = requiredShopperFields; + } + public CreatePaymentLinkRequest returnUrl(String returnUrl) { this.returnUrl = returnUrl; return this; @@ -502,7 +583,6 @@ public CreatePaymentLinkRequest returnUrl(String returnUrl) { /** * Website URL used for redirection after payment is completed. If provided, a **Continue** button will be shown on the payment page. If shoppers select the button, they are redirected to the specified URL. - * * @return returnUrl **/ public String getReturnUrl() { @@ -520,7 +600,6 @@ public CreatePaymentLinkRequest reusable(Boolean reusable) { /** * Indicates whether the payment link can be reused for multiple payments. If not provided, this defaults to **false** which means the link can be used for one successful payment only. - * * @return reusable **/ public Boolean isReusable() { @@ -538,7 +617,6 @@ public CreatePaymentLinkRequest riskData(RiskData riskData) { /** * Get riskData - * * @return riskData **/ public RiskData getRiskData() { @@ -556,7 +634,6 @@ public CreatePaymentLinkRequest shopperEmail(String shopperEmail) { /** * The shopper's email address. - * * @return shopperEmail **/ public String getShopperEmail() { @@ -573,8 +650,7 @@ public CreatePaymentLinkRequest shopperLocale(String shopperLocale) { } /** - * The language to be used in the payment page, specified by a combination of a language and country code. For example, `en-US`. For a list of shopper locales that Pay by Link supports, refer to [Language and localization](https://docs.adyen.com/checkout/pay-by-link#language-and-localization). - * + * The language to be used in the payment page, specified by a combination of a language and country code. For example, `en-US`. For a list of shopper locales that Pay by Link supports, refer to [Language and localization](https://docs.adyen.com/online-payments/pay-by-link#language-and-localization). * @return shopperLocale **/ public String getShopperLocale() { @@ -592,7 +668,6 @@ public CreatePaymentLinkRequest shopperName(Name shopperName) { /** * Get shopperName - * * @return shopperName **/ public Name getShopperName() { @@ -609,8 +684,7 @@ public CreatePaymentLinkRequest shopperReference(String shopperReference) { } /** - * A unique identifier for the shopper (for example, user ID or account ID). - * + * Your reference to uniquely identify this shopper (for example, user ID or account ID). Minimum length: 3 characters. * @return shopperReference **/ public String getShopperReference() { @@ -635,8 +709,7 @@ public CreatePaymentLinkRequest addSplitsItem(Split splitsItem) { } /** - * Information on how the payment should be split between accounts when using [Adyen for Platforms](https://docs.adyen.com/platforms/processing-payments#providing-split-information). - * + * An array of objects specifying how the payment should be split between accounts when using Adyen for Platforms. For details, refer to [Providing split information](https://docs.adyen.com/platforms/processing-payments#providing-split-information). * @return splits **/ public List getSplits() { @@ -654,7 +727,6 @@ public CreatePaymentLinkRequest store(String store) { /** * The physical store, for which this payment is processed. - * * @return store **/ public String getStore() { @@ -672,7 +744,6 @@ public CreatePaymentLinkRequest storePaymentMethod(Boolean storePaymentMethod) { /** * When this is set to **true** and the `shopperReference` is provided, the payment details will be stored. - * * @return storePaymentMethod **/ public Boolean isStorePaymentMethod() { @@ -683,6 +754,7 @@ public void setStorePaymentMethod(Boolean storePaymentMethod) { this.storePaymentMethod = storePaymentMethod; } + @Override public boolean equals(java.lang.Object o) { if (this == o) { @@ -702,12 +774,14 @@ public boolean equals(java.lang.Object o) { Objects.equals(this.deliveryAddress, createPaymentLinkRequest.deliveryAddress) && Objects.equals(this.description, createPaymentLinkRequest.description) && Objects.equals(this.expiresAt, createPaymentLinkRequest.expiresAt) && + Objects.equals(this.installmentOptions, createPaymentLinkRequest.installmentOptions) && Objects.equals(this.lineItems, createPaymentLinkRequest.lineItems) && Objects.equals(this.merchantAccount, createPaymentLinkRequest.merchantAccount) && Objects.equals(this.merchantOrderReference, createPaymentLinkRequest.merchantOrderReference) && Objects.equals(this.metadata, createPaymentLinkRequest.metadata) && Objects.equals(this.recurringProcessingModel, createPaymentLinkRequest.recurringProcessingModel) && Objects.equals(this.reference, createPaymentLinkRequest.reference) && + Objects.equals(this.requiredShopperFields, createPaymentLinkRequest.requiredShopperFields) && Objects.equals(this.returnUrl, createPaymentLinkRequest.returnUrl) && Objects.equals(this.reusable, createPaymentLinkRequest.reusable) && Objects.equals(this.riskData, createPaymentLinkRequest.riskData) && @@ -722,42 +796,54 @@ public boolean equals(java.lang.Object o) { @Override public int hashCode() { - return Objects.hash(allowedPaymentMethods, amount, applicationInfo, billingAddress, blockedPaymentMethods, countryCode, deliverAt, deliveryAddress, description, expiresAt, lineItems, merchantAccount, merchantOrderReference, metadata, recurringProcessingModel, reference, returnUrl, reusable, riskData, shopperEmail, shopperLocale, shopperName, shopperReference, splits, store, storePaymentMethod); + return Objects.hash(allowedPaymentMethods, amount, applicationInfo, billingAddress, blockedPaymentMethods, countryCode, deliverAt, deliveryAddress, description, expiresAt, installmentOptions, lineItems, merchantAccount, merchantOrderReference, metadata, recurringProcessingModel, reference, requiredShopperFields, returnUrl, reusable, riskData, shopperEmail, shopperLocale, shopperName, shopperReference, splits, store, storePaymentMethod); } + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class CreatePaymentLinkRequest {\n"); - - sb.append(" allowedPaymentMethods: ").append(toIndentedString(allowedPaymentMethods)).append("\n"); - sb.append(" amount: ").append(toIndentedString(amount)).append("\n"); - sb.append(" applicationInfo: ").append(toIndentedString(applicationInfo)).append("\n"); - sb.append(" billingAddress: ").append(toIndentedString(billingAddress)).append("\n"); - sb.append(" blockedPaymentMethods: ").append(toIndentedString(blockedPaymentMethods)).append("\n"); - sb.append(" countryCode: ").append(toIndentedString(countryCode)).append("\n"); - sb.append(" deliverAt: ").append(toIndentedString(deliverAt)).append("\n"); - sb.append(" deliveryAddress: ").append(toIndentedString(deliveryAddress)).append("\n"); - sb.append(" description: ").append(toIndentedString(description)).append("\n"); - sb.append(" expiresAt: ").append(toIndentedString(expiresAt)).append("\n"); - sb.append(" lineItems: ").append(toIndentedString(lineItems)).append("\n"); - sb.append(" merchantAccount: ").append(toIndentedString(merchantAccount)).append("\n"); - sb.append(" merchantOrderReference: ").append(toIndentedString(merchantOrderReference)).append("\n"); - sb.append(" metadata: ").append(toIndentedString(metadata)).append("\n"); - sb.append(" recurringProcessingModel: ").append(toIndentedString(recurringProcessingModel)).append("\n"); - sb.append(" reference: ").append(toIndentedString(reference)).append("\n"); - sb.append(" returnUrl: ").append(toIndentedString(returnUrl)).append("\n"); - sb.append(" reusable: ").append(toIndentedString(reusable)).append("\n"); - sb.append(" riskData: ").append(toIndentedString(riskData)).append("\n"); - sb.append(" shopperEmail: ").append(toIndentedString(shopperEmail)).append("\n"); - sb.append(" shopperLocale: ").append(toIndentedString(shopperLocale)).append("\n"); - sb.append(" shopperName: ").append(toIndentedString(shopperName)).append("\n"); - sb.append(" shopperReference: ").append(toIndentedString(shopperReference)).append("\n"); - sb.append(" splits: ").append(toIndentedString(splits)).append("\n"); - sb.append(" store: ").append(toIndentedString(store)).append("\n"); - sb.append(" storePaymentMethod: ").append(toIndentedString(storePaymentMethod)).append("\n"); - sb.append("}"); - return sb.toString(); + + return "class CreatePaymentLinkRequest {\n" + + " allowedPaymentMethods: " + toIndentedString(allowedPaymentMethods) + "\n" + + " amount: " + toIndentedString(amount) + "\n" + + " applicationInfo: " + toIndentedString(applicationInfo) + "\n" + + " billingAddress: " + toIndentedString(billingAddress) + "\n" + + " blockedPaymentMethods: " + toIndentedString(blockedPaymentMethods) + "\n" + + " countryCode: " + toIndentedString(countryCode) + "\n" + + " deliverAt: " + toIndentedString(deliverAt) + "\n" + + " deliveryAddress: " + toIndentedString(deliveryAddress) + "\n" + + " description: " + toIndentedString(description) + "\n" + + " expiresAt: " + toIndentedString(expiresAt) + "\n" + + " installmentOptions: " + toIndentedString(installmentOptions) + "\n" + + " lineItems: " + toIndentedString(lineItems) + "\n" + + " merchantAccount: " + toIndentedString(merchantAccount) + "\n" + + " merchantOrderReference: " + toIndentedString(merchantOrderReference) + "\n" + + " metadata: " + toIndentedString(metadata) + "\n" + + " recurringProcessingModel: " + toIndentedString(recurringProcessingModel) + "\n" + + " reference: " + toIndentedString(reference) + "\n" + + " requiredShopperFields: " + toIndentedString(requiredShopperFields) + "\n" + + " returnUrl: " + toIndentedString(returnUrl) + "\n" + + " reusable: " + toIndentedString(reusable) + "\n" + + " riskData: " + toIndentedString(riskData) + "\n" + + " shopperEmail: " + toIndentedString(shopperEmail) + "\n" + + " shopperLocale: " + toIndentedString(shopperLocale) + "\n" + + " shopperName: " + toIndentedString(shopperName) + "\n" + + " shopperReference: " + toIndentedString(shopperReference) + "\n" + + " splits: " + toIndentedString(splits) + "\n" + + " store: " + toIndentedString(store) + "\n" + + " storePaymentMethod: " + toIndentedString(storePaymentMethod) + "\n" + + "}"; + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); } } diff --git a/src/main/java/com/adyen/model/checkout/InstallmentOption.java b/src/main/java/com/adyen/model/checkout/InstallmentOption.java new file mode 100644 index 000000000..01d2bcd39 --- /dev/null +++ b/src/main/java/com/adyen/model/checkout/InstallmentOption.java @@ -0,0 +1,232 @@ +/* + * ###### + * ###### + * ############ ####( ###### #####. ###### ############ ############ + * ############# #####( ###### #####. ###### ############# ############# + * ###### #####( ###### #####. ###### ##### ###### ##### ###### + * ###### ###### #####( ###### #####. ###### ##### ##### ##### ###### + * ###### ###### #####( ###### #####. ###### ##### ##### ###### + * ############# ############# ############# ############# ##### ###### + * ############ ############ ############# ############ ##### ###### + * ###### + * ############# + * ############ + * + * Adyen Java API Library + * + * Copyright (c) 2021 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +package com.adyen.model.checkout; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * InstallmentOption + */ + +public class InstallmentOption { + @SerializedName("maxValue") + private Integer maxValue = null; + + /** + * Gets or Sets plans + */ + @JsonAdapter(PlansEnum.Adapter.class) + public enum PlansEnum { + REGULAR("regular"), + REVOLVING("revolving"); + + @JsonValue + private String value; + + PlansEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static PlansEnum fromValue(String text) { + for (PlansEnum b : PlansEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + + public static class Adapter extends TypeAdapter { + @Override + public void write(final JsonWriter jsonWriter, final PlansEnum enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public PlansEnum read(final JsonReader jsonReader) throws IOException { + Object value = jsonReader.nextString(); + return PlansEnum.fromValue(String.valueOf(value)); + } + } + } + + @SerializedName("plans") + private List plans = null; + + @SerializedName("preselectedValue") + private Integer preselectedValue = null; + + @SerializedName("values") + private List values = null; + + public InstallmentOption maxValue(Integer maxValue) { + this.maxValue = maxValue; + return this; + } + + /** + * The maximum number of installments offered for this payment method. + * + * @return maxValue + **/ + public Integer getMaxValue() { + return maxValue; + } + + public void setMaxValue(Integer maxValue) { + this.maxValue = maxValue; + } + + public InstallmentOption plans(List plans) { + this.plans = plans; + return this; + } + + public InstallmentOption addPlansItem(PlansEnum plansItem) { + if (this.plans == null) { + this.plans = new ArrayList<>(); + } + this.plans.add(plansItem); + return this; + } + + /** + * Defines the type of installment plan. If not set, defaults to **regular**. Possible values: * **regular** * **revolving** + * + * @return plans + **/ + public List getPlans() { + return plans; + } + + public void setPlans(List plans) { + this.plans = plans; + } + + public InstallmentOption preselectedValue(Integer preselectedValue) { + this.preselectedValue = preselectedValue; + return this; + } + + /** + * Preselected number of installments offered for this payment method. + * + * @return preselectedValue + **/ + public Integer getPreselectedValue() { + return preselectedValue; + } + + public void setPreselectedValue(Integer preselectedValue) { + this.preselectedValue = preselectedValue; + } + + public InstallmentOption values(List values) { + this.values = values; + return this; + } + + public InstallmentOption addValuesItem(Integer valuesItem) { + if (this.values == null) { + this.values = new ArrayList<>(); + } + this.values.add(valuesItem); + return this; + } + + /** + * An array of the number of installments that the shopper can choose from. For example, **[2,3,5]**. This cannot be specified simultaneously with `maxValue`. + * + * @return values + **/ + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InstallmentOption installmentOption = (InstallmentOption) o; + return Objects.equals(this.maxValue, installmentOption.maxValue) && + Objects.equals(this.plans, installmentOption.plans) && + Objects.equals(this.preselectedValue, installmentOption.preselectedValue) && + Objects.equals(this.values, installmentOption.values); + } + + @Override + public int hashCode() { + return Objects.hash(maxValue, plans, preselectedValue, values); + } + + + @Override + public String toString() { + + return "class InstallmentOption {\n" + + " maxValue: " + toIndentedString(maxValue) + "\n" + + " plans: " + toIndentedString(plans) + "\n" + + " preselectedValue: " + toIndentedString(preselectedValue) + "\n" + + " values: " + toIndentedString(values) + "\n" + + "}"; + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} diff --git a/src/main/java/com/adyen/util/HMACValidator.java b/src/main/java/com/adyen/util/HMACValidator.java index c775debe9..34d4169a4 100644 --- a/src/main/java/com/adyen/util/HMACValidator.java +++ b/src/main/java/com/adyen/util/HMACValidator.java @@ -41,8 +41,12 @@ public class HMACValidator { public static final String DATA_SEPARATOR = ":"; // To calculate the HMAC SHA-256 - public String calculateHMAC(String data, String key) throws java.security.SignatureException { + public String calculateHMAC(String data, String key) throws IllegalArgumentException, SignatureException { try { + if (data == null || key == null) { + throw new IllegalArgumentException(); + } + byte[] rawKey = Hex.decodeHex(key.toCharArray()); // Create an hmac_sha256 key from the raw key bytes SecretKeySpec signingKey = new SecretKeySpec(rawKey, HMAC_SHA256_ALGORITHM); @@ -58,18 +62,23 @@ public String calculateHMAC(String data, String key) throws java.security.Signat // Base64-encode the hmac return new String(Base64.encodeBase64(rawHmac)); - + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Missing data or key."); } catch (Exception e) { throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); } } // To calculate the HMAC SHA-256 - public String calculateHMAC(NotificationRequestItem notificationRequestItem, String key) throws SignatureException { + public String calculateHMAC(NotificationRequestItem notificationRequestItem, String key) throws IllegalArgumentException, SignatureException { return calculateHMAC(getDataToSign(notificationRequestItem), key); } public boolean validateHMAC(NotificationRequestItem notificationRequestItem, String key) throws IllegalArgumentException, SignatureException { + if (notificationRequestItem == null) { + throw new IllegalArgumentException("Missing NotificationRequestItem."); + } + if (notificationRequestItem.getAdditionalData() == null || notificationRequestItem.getAdditionalData().get(HMAC_SIGNATURE).isEmpty()) { throw new IllegalArgumentException("Missing " + HMAC_SIGNATURE); } @@ -79,7 +88,11 @@ public boolean validateHMAC(NotificationRequestItem notificationRequestItem, Str return MessageDigest.isEqual(merchantSign, expectedSign); } - public String getDataToSign(NotificationRequestItem notificationRequestItem) { + public String getDataToSign(NotificationRequestItem notificationRequestItem) throws IllegalArgumentException { + if (notificationRequestItem == null) { + throw new IllegalArgumentException("Missing NotificationRequestItem."); + } + List signedDataList = new ArrayList<>(8); signedDataList.add(notificationRequestItem.getPspReference()); signedDataList.add(notificationRequestItem.getOriginalReference()); @@ -87,8 +100,23 @@ public String getDataToSign(NotificationRequestItem notificationRequestItem) { signedDataList.add(notificationRequestItem.getMerchantReference()); Amount amount = notificationRequestItem.getAmount(); - signedDataList.add(amount.getValue().toString()); - signedDataList.add(amount.getCurrency()); + + //If the amount and value are not null, append them to the payload. + if (amount != null && amount.getValue() != null) { + signedDataList.add(amount.getValue().toString()); + } else { + //Else append a null. Will appear as a empty string in the final payload. + signedDataList.add(null); + } + + //If the amount and currency are not null, append them to the payload. + if (amount != null && amount.getCurrency() != null) { + signedDataList.add(amount.getCurrency()); + } else { + //Else append a null. Will appear as a empty string in the final payload. + signedDataList.add(null); + } + signedDataList.add(notificationRequestItem.getEventCode()); signedDataList.add(String.valueOf(notificationRequestItem.isSuccess())); diff --git a/src/test/java/com/adyen/service/PaymentLinksTest.java b/src/test/java/com/adyen/service/PaymentLinksTest.java index ee760132c..5655cf545 100644 --- a/src/test/java/com/adyen/service/PaymentLinksTest.java +++ b/src/test/java/com/adyen/service/PaymentLinksTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import java.io.IOException; +import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -40,7 +41,7 @@ public class PaymentLinksTest extends BaseTest { @Test public void TestCreatePaymentLinksSuccess() throws Exception { - Client client = createMockClientFromFile("mocks/paymentlinks/get-payment-link-success.json"); + Client client = createMockClientFromFile("mocks/paymentlinks/create-payment-links-success.json"); PaymentLinks paymentLinks = new PaymentLinks(client); CreatePaymentLinkRequest createPaymentLinkRequest = createPaymentLinkRequest(); PaymentLinkResource paymentLink = paymentLinks.create(createPaymentLinkRequest); @@ -138,6 +139,7 @@ private CreatePaymentLinkRequest createPaymentLinkRequest() { createPaymentLinkRequest.setShopperEmail("test@email.com"); createPaymentLinkRequest.setShopperLocale("pt_BR"); createPaymentLinkRequest.setExpiresAt("2019-12-17T10:05:29Z"); + createPaymentLinkRequest.setRequiredShopperFields(Collections.singletonList(CreatePaymentLinkRequest.RequiredShopperFieldsEnum.DELIVERYADDRESS)); Address address = new Address(); address.setStreet("Street"); address.setPostalCode("59000060"); diff --git a/src/test/java/com/adyen/util/HMACValidatorTest.java b/src/test/java/com/adyen/util/HMACValidatorTest.java index 3c3c99799..50fde0945 100644 --- a/src/test/java/com/adyen/util/HMACValidatorTest.java +++ b/src/test/java/com/adyen/util/HMACValidatorTest.java @@ -27,7 +27,9 @@ import java.security.SignatureException; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class HMACValidatorTest { @@ -56,4 +58,54 @@ public void testValidateHMAC() throws SignatureException { boolean result = new HMACValidator().validateHMAC(notificationRequest, HMAC_KEY); assertTrue(result); } + + @Test + public void testValidateHMACMissingOptionalField() throws SignatureException { + String notificationJson = "{\n" + + " \"additionalData\": {\n" + + " \"hmacSignature\": \"NuEkADFmlCC6VgX+wcoPAegIWxVPNBPCuKlM4Hzo5qc=\"\n" + + " },\n" + + " \"eventCode\": \"REPORT_AVAILABLE\",\n" + + " \"eventDate\": \"2019-11-20T14:35:36+01:00\",\n" + + " \"merchantAccountCode\": \"Magento2Rik\",\n" + + " \"merchantReference\": \"testMerchantRef1\",\n" + + " \"pspReference\": \"test_REPORT_AVAILABLE\",\n" + + " \"reason\": \"will contain the url to the report\",\n" + + " \"success\": \"true\"\n" + + "}"; + NotificationRequestItem notificationRequest = new Gson().fromJson(notificationJson, NotificationRequestItem.class); + String payload = new HMACValidator().getDataToSign(notificationRequest); + assertEquals("test_REPORT_AVAILABLE::Magento2Rik:testMerchantRef1:::REPORT_AVAILABLE:true", payload); + } + + @Test + public void testValidateHMACEmptyNotificationRequest() { + try { + new HMACValidator().getDataToSign((NotificationRequestItem) null); + } catch (IllegalArgumentException e) { + assertEquals("Missing NotificationRequestItem.", e.getMessage()); + } + } + + @Test + public void testCalculateHMACNullPayload() { + try { + new HMACValidator().calculateHMAC((String)null, HMAC_KEY); + } catch (IllegalArgumentException e) { + assertEquals("Missing data or key.", e.getMessage()); + } catch (SignatureException e) { + fail(); + } + } + + @Test + public void testCalculateHMACNullKey() { + try { + new HMACValidator().calculateHMAC("TestPayload", null); + } catch (IllegalArgumentException e) { + assertEquals("Missing data or key.", e.getMessage()); + } catch (SignatureException e) { + fail(); + } + } }