diff --git a/README.md b/README.md index 82a201592..5856ad889 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,10 @@ Alternatively, you can download the [release on GitHub](https://github.com/Adyen ### General use with API key -Every API the library supports is represented by a service object. The name of the service matching the corresponding -API is listed in the [Supported API versions](#supported-api-versions) section of this document. +For every API, one or more corresponding service classes can be found in the folder with the same name. +Check the [Supported API versions](#supported-api-versions). + +**Note**: For requests on `live` environment, you must define the [Live URL Prefix](https://docs.adyen.com/development-resources/live-endpoints#live-url-prefix) in the Client object: ~~~~ java // Import the required classes @@ -92,8 +94,13 @@ import com.adyen.enums.Environment; import com.adyen.service.checkout.PaymentsApi; import com.adyen.model.checkout.*; -// Setup Client and Service -Client client = new Client("Your X-API-KEY", Environment.TEST); +// Setup Client using Config object +Config config = new Config() + .environment(Environment.LIVE) + .liveEndpointUrlPrefix("myCompany") + .apiKey(apiKey); +Client client = new Client(config); + PaymentsApi paymentsApi = new PaymentsApi(client); // Create PaymentRequest @@ -120,20 +127,6 @@ PaymentResponse paymentResponse = paymentsApi.payments(paymentRequest); ~~~~ -### General use with API key for live environment -For requests on live environment, you need to pass the [Live URL Prefix](https://docs.adyen.com/development-resources/live-endpoints#live-url-prefix) to the Client object: -~~~~ java -// Import the required classes -import com.adyen.Client; -import com.adyen.enums.Environment; -import com.adyen.service.checkout.ModificationsApi - -// Setup Client and Service -Client client = new Client("Your X-API-KEY", Environment.LIVE, "Your live URL prefix"); -ModificationsApi modificationsApi = new ModificationsApi(client); - -... -~~~~ ### General use with basic auth ~~~~ java // Import the required classes @@ -141,8 +134,11 @@ import com.adyen.Client; import com.adyen.enums.Environment; import com.adyen.service.checkout.PaymentLinksApi -// Setup Client and Service -Client client = new Client("Your username", "Your password", Environment.LIVE, "Your live URL prefix", "Your application name"); +// Setup Client and Service passing prefix +Client client = new Client("Your username", "Your password", Environment.LIVE, "mycompany123"); +// Or setup Client and Service passing prefix and application name +//Client client = new Client("Your username", "Your password", Environment.LIVE, "mycompany123", "Your application name"); + PaymentLinksApi paymentLinksApi = new PaymentLinksApi(client); ... @@ -156,6 +152,23 @@ import com.adyen.model.checkout.PaymentRequest; // Deserialize using built-in function PaymentRequest paymentRequest = PaymentRequest.fromJson("YOUR_JSON_STRING"); ~~~~ +### Error handling + +Use a try-catch block to handle API errors. Catch the `ApiException` to inspect the response and handle specific cases: +~~~~ +try { + service.getPaymentLink("1234"); +} catch (ApiException e) { + // Obtain response + int statusCode = e.getStatusCode(); + String responseBody = e.getResponseBody(); + // Check ApiError object + ApiError apiError = e.getError(); + String errorCode = apiError.getErrorCode(); + List invalidFields = apiError.getInvalidFields(); + .... +} +~~~~ ### Using notification webhooks parser ~~~~ java // Import the required classes @@ -295,27 +308,9 @@ Client client = new Client(sslContext, apiKey); // Use the client ~~~~ -### Classic Platforms Error Handling -When requests fail, the library throws exceptions. For Classic AFP endpoints like [Create Account Holder](https://docs.adyen.com/api-explorer/Account/6/post/createAccountHolder), you can decode further details from the exception: - -```java -Client client = new Client("Your YOUR_API_KEY", Environment.TEST); -ClassicPlatformAccountApi api = new ClassicPlatformAccountApi(client); -CreateAccountHolderRequest request = new CreateAccountHolderRequest(); - -try { - api.createAccountHolder(request); -} catch (ApiException e) { - CreateAccountHolderResponse error = CreateAccountHolderResponse.fromJson(e.getResponseBody()); - // inspect the error - System.out.println(e.getStatusCode()); - System.out.println(error.getInvalidFields()); -} -``` - -## Using the Cloud Terminal API Integration -In order to submit In-Person requests with [Terminal API over Cloud](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) you need to initialize the client in a similar way as the steps listed above for Ecommerce transactions, but make sure to include `TerminalCloudAPI`: +## Using the Cloud Terminal API +For In-Person Payments integrations with the [Cloud Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/), you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud): ``` java // Step 1: Import the required classes import com.adyen.Client; @@ -325,13 +320,11 @@ import com.adyen.model.nexo.*; import com.adyen.model.terminal.*; // Step 2: Initialize the client object -Client client = new Client("Your YOUR_API_KEY", Environment.TEST); - -// for LIVE environment use -// Config config = new Config(); -// config.setEnvironment(Environment.LIVE); -// config.setTerminalApiRegion(Region.EU); -// Client client = new Client(config); +Config config = new Config() + .environment(Environment.LIVE) + .terminalApiRegion(Region.EU) + .apiKey(apiKey); +Client client = new Client(config); // Step 3: Initialize the API object TerminalCloudAPI terminalCloudApi = new TerminalCloudAPI(client); @@ -567,9 +560,7 @@ TerminalAPIRequest terminalAPIPaymentRequest = new TerminalAPIRequest(); TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); ``` - ### Example integrations - For a closer look at how our Java library works, you can clone one of our example integrations: * [Java Spring Boot example integration](https://github.com/adyen-examples/adyen-java-spring-online-payments). * [Kotlin Spring Boot example integration](https://github.com/adyen-examples/adyen-kotlin-spring-online-payments). @@ -580,20 +571,15 @@ These include commented code, highlighting key features and concepts, and exampl We value your input! Help us enhance our API Libraries and improve the integration experience by providing your feedback. Please take a moment to fill out [our feedback form](https://forms.gle/A4EERrR6CWgKWe5r9) to share your thoughts, suggestions or ideas. ## Contributing - - -We encourage you to contribute to this repository, so everyone can benefit from new features, bug fixes, and any other improvements. - +We encourage you to contribute to this repository, so everyone can benefit from new features, bug fixes, and any other improvements. Have a look at our [contributing guidelines](CONTRIBUTING.md) to find out how to raise a pull request. - - + ## Support If you have a feature request, or spotted a bug or a technical problem, [create an issue here](https://github.com/Adyen/adyen-java-api-library/issues/new/choose). -For other questions, [contact our Support Team](https://www.adyen.help/hc/en-us/requests/new?ticket_form_id=39.1.1705420). - - +For other questions, [contact our Support Team](https://www.adyen.help/hc/en-us/requests/new?ticket_form_id=39.0.0705420). + ## Licence This repository is available under the [MIT license](https://github.com/Adyen/adyen-java-api-library/blob/main/LICENSE). diff --git a/src/main/java/com/adyen/Client.java b/src/main/java/com/adyen/Client.java index 3b26944df..10432d093 100644 --- a/src/main/java/com/adyen/Client.java +++ b/src/main/java/com/adyen/Client.java @@ -18,19 +18,21 @@ public class Client { public static final String TERMINAL_API_ENDPOINT_APSE = "https://terminal-api-live-apse.adyen.com"; + /** Create Client instance (empty config) */ public Client() { this.config = new Config(); } + /** + * Create Client instance with the given configuration + * + * @param config Configuration + */ public Client(Config config) { this.config = config; this.setEnvironment(config.environment, config.liveEndpointUrlPrefix); } - public Client(String username, String password, Environment environment, String applicationName) { - this(username, password, environment, null, applicationName); - } - /** * Use this constructor to create client for client certificate authentication along with API key. * Note: Client certificate authentication is only applicable for PAL and Checkout services in @@ -44,6 +46,28 @@ public Client(SSLContext sslContext, String apiKey) { this.config.setSSLContext(sslContext); } + /** + * Create Client instance + * + * @param username HTTP basic username + * @param password HTTP basic password + * @param environment Environment (Test or Live) + * @param liveEndpointUrlPrefix Prefix required for Live integrations + */ + public Client( + String username, String password, Environment environment, String liveEndpointUrlPrefix) { + this(username, password, environment, liveEndpointUrlPrefix, null); + } + + /** + * Create Client instance + * + * @param username HTTP basic username + * @param password HTTP basic password + * @param environment Environment (Test or Live) + * @param liveEndpointUrlPrefix Prefix required for Live integrations + * @param applicationName Application name (additional name/tag passed in HTTP requests) + */ public Client( String username, String password, @@ -58,46 +82,22 @@ public Client( } /** - * @param username your merchant account Username - * @param password your merchant accont Password - * @param environment This defines the payment environment live or test - * @param connectionTimeoutMillis Provide the time to time out - * @deprecated As of library version 1.6.1, timeouts should be set by {@link #setTimeouts(int - * connectionTimeoutMillis, int readTimeoutMillis)} or directly by {@link - * com.adyen.Config#setConnectionTimeoutMillis(int connectionTimeoutMillis)}. - */ - @Deprecated - public Client( - String username, String password, Environment environment, int connectionTimeoutMillis) { - this(username, password, environment, null); - this.config.setConnectionTimeoutMillis(connectionTimeoutMillis); - } - - /** - * @param username your merchant account Username - * @param password your merchant accont Password - * @param environment This defines the payment environment live or test - * @param connectionTimeoutMillis Provide the time to time out - * @param liveEndpointUrlPrefix provide the merchant specific url - * @deprecated As of library version 1.6.1, timeouts should be set by {@link #setTimeouts(int - * connectionTimeoutMillis, int readTimeoutMillis)} or directly by {@link - * com.adyen.Config#setConnectionTimeoutMillis(int connectionTimeoutMillis)}. + * Create Client instance + * + * @param apiKey API Key + * @param environment Environment (Test or Live) */ - @Deprecated - public Client( - String username, - String password, - Environment environment, - int connectionTimeoutMillis, - String liveEndpointUrlPrefix) { - this(username, password, environment, liveEndpointUrlPrefix, null); - this.config.setConnectionTimeoutMillis(connectionTimeoutMillis); - } - public Client(String apiKey, Environment environment) { this(apiKey, environment, null); } + /** + * Create Client instance + * + * @param apiKey API Key + * @param environment Environment (Test or Live) + * @param liveEndpointUrlPrefix Prefix required for the live integrations + */ public Client(String apiKey, Environment environment, String liveEndpointUrlPrefix) { this.config = new Config(); this.config.setApiKey(apiKey); @@ -105,52 +105,10 @@ public Client(String apiKey, Environment environment, String liveEndpointUrlPref } /** - * @param apiKey Defines the api key that can be retrieved by back office - * @param environment This defines the payment environment live or test - * @param connectionTimeoutMillis Provide the time to time out - * @deprecated As of library version 1.6.1, timeouts should be set by {@link #setTimeouts(int - * connectionTimeoutMillis, int readTimeoutMillis)} or directly by {@link - * com.adyen.Config#setConnectionTimeoutMillis(int connectionTimeoutMillis)}. - */ - @Deprecated - public Client(String apiKey, Environment environment, int connectionTimeoutMillis) { - this(apiKey, environment); - this.config.setConnectionTimeoutMillis(connectionTimeoutMillis); - } - - /** - * @param apiKey Defines the api key that can be retrieved by back office - * @param environment This defines the payment environment live or test - * @param connectionTimeoutMillis Provide the time to time out - * @param liveEndpointUrlPrefix provide the merchant specific url - * @deprecated As of library version 1.6.1, timeouts should be set by {@link #setTimeouts(int - * connectionTimeoutMillis, int readTimeoutMillis)} or directly by {@link - * com.adyen.Config#setConnectionTimeoutMillis(int connectionTimeoutMillis)}. - */ - @Deprecated - public Client( - String apiKey, - Environment environment, - int connectionTimeoutMillis, - String liveEndpointUrlPrefix) { - this(apiKey, environment, liveEndpointUrlPrefix); - this.config.setConnectionTimeoutMillis(connectionTimeoutMillis); - } - - /** - * @param environment This defines the payment environment live or test - * @deprecated As of library version 1.5.4, replaced by {@link #setEnvironment(Environment - * environment, String liveEndpointUrlPrefix)}. - */ - @Deprecated - public void setEnvironment(Environment environment) { - this.setEnvironment(environment, null); - } - - /** - * @param environment This defines the payment environment live or test - * @param liveEndpointUrlPrefix Provide the unique live url prefix from the "API URLs and - * Response" menu in the Adyen Customer Area + * Set Environment, together with the live endpoint url prefix. + * + * @param environment Environment (Test or Live) + * @param liveEndpointUrlPrefix The unique live url prefix (required for live integrations) */ public void setEnvironment(Environment environment, String liveEndpointUrlPrefix) { config.setEnvironment(environment); @@ -161,8 +119,11 @@ public void setEnvironment(Environment environment, String liveEndpointUrlPrefix } /** + * Retrieve the Terminal Cloud endpoint based on Region and Environment + * * @param region The region for which the endpoint is requested. If null or the region is not * found, defaults to default EU endpoint. + * @param environment Environment (Test or Live) */ public String retrieveCloudEndpoint(Region region, Environment environment) { // Check the environment for TEST and get the endpoint diff --git a/src/main/java/com/adyen/Config.java b/src/main/java/com/adyen/Config.java index 989dd3f45..08a8ca2aa 100644 --- a/src/main/java/com/adyen/Config.java +++ b/src/main/java/com/adyen/Config.java @@ -6,21 +6,25 @@ import javax.net.ssl.SSLContext; public class Config { + // API key authentication + protected String apiKey; + // Basic authentication protected String username; protected String password; + // Environment: Test or Live protected Environment environment; - /** Application name: used as HTTP client User-Agent */ + // Application name: used as HTTP client User-Agent protected String applicationName; - protected String apiKey; + // HTTP Client options protected int connectionTimeoutMillis = 60 * 1000; // default 60 sec protected int readTimeoutMillis = 60 * 1000; // default 60 sec protected int connectionRequestTimeoutMillis = 60 * 1000; // default 60 sec protected int defaultKeepAliveMillis = 60 * 1000; // default 60 sec protected Boolean protocolUpgradeEnabled; - // Terminal API Specific + // Terminal API configuration protected String terminalApiCloudEndpoint; protected String terminalApiLocalEndpoint; protected String liveEndpointUrlPrefix; diff --git a/src/main/java/com/adyen/Service.java b/src/main/java/com/adyen/Service.java index 8d1fb877b..6a05ed1c9 100644 --- a/src/main/java/com/adyen/Service.java +++ b/src/main/java/com/adyen/Service.java @@ -22,31 +22,67 @@ import com.adyen.enums.Environment; -/** Parent class for all Service implementations */ +/** + * A generic service that provides shared functionality for all API services. It handles client and + * configuration management. + */ public class Service { private boolean isApiKeyRequired = false; private Client client; + /** + * Constructs a new Service. + * + * @param client The client used to make API calls. + */ protected Service(Client client) { this.client = client; } + /** + * Gets the client used by this service. + * + * @return The client. + */ public Client getClient() { return client; } + /** + * Sets the client to be used by this service. + * + * @param client The client. + */ public void setClient(Client client) { this.client = client; } + /** + * Returns true if the service requires an API key. + * + * @return A boolean indicating if an API key is required. + */ public boolean isApiKeyRequired() { return isApiKeyRequired; } + /** + * Sets if the service requires an API key. + * + * @param apiKeyRequired A boolean indicating if an API key is required. + */ public void setApiKeyRequired(boolean apiKeyRequired) { isApiKeyRequired = apiKeyRequired; } + /** + * Creates the base URL for a specific API endpoint. It dynamically constructs the URL based on + * the client's configured {@link Environment}. + * + * @param url The base URL template. + * @return The fully constructed base URL for the target environment. + * @throws IllegalArgumentException if the live URL prefix is required but not configured. + */ protected String createBaseURL(String url) { Config config = this.getClient().getConfig(); if (config.getEnvironment() != Environment.LIVE) { diff --git a/src/test/java/com/adyen/ServiceTest.java b/src/test/java/com/adyen/ServiceTest.java new file mode 100644 index 000000000..cae243117 --- /dev/null +++ b/src/test/java/com/adyen/ServiceTest.java @@ -0,0 +1,95 @@ +package com.adyen; + +import static org.junit.Assert.*; + +import com.adyen.enums.Environment; +import org.junit.Before; +import org.junit.Test; + +/** Tests for {@link Service#createBaseURL(String)}. */ +public class ServiceTest extends BaseTest { + + private Config config; + private Service service; + + @Before + public void setUp() { + config = new Config().environment(Environment.LIVE); + Client client = new Client(config); + service = new Service(client); + } + + @Test + public void testCreateBaseURLForTestEnvironment() { + config.setEnvironment(Environment.TEST); + String liveUrl = "https://balanceplatform-api-live.adyen.com/bcl/v2/balanceAccounts"; + String expectedUrl = "https://balanceplatform-api-test.adyen.com/bcl/v2/balanceAccounts"; + + String actualUrl = service.createBaseURL(liveUrl); + // verify Live url is converted to Test url + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testCreateBaseURLForLiveEnvironment() { + + String testUrl = "https://balanceplatform-api-test.adyen.com/bcl/v2/balanceAccounts"; + String expectedUrl = "https://balanceplatform-api-live.adyen.com/bcl/v2/balanceAccounts"; + + String actualUrl = service.createBaseURL(testUrl); + // verify Test url is converted to Live url + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testLivePalUrlWithPrefix() { + + config.setLiveEndpointUrlPrefix("123456789-company"); + String testUrl = "https://pal-test.adyen.com/pal/servlet/v52/initiate"; + String expectedUrl = + "https://123456789-company-pal-live.adyenpayments.com/pal/servlet/v52/initiate"; + + String actualUrl = service.createBaseURL(testUrl); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testLivePalUrlWithoutPrefix() { + String testUrl = "https://pal-test.adyen.com/pal/servlet/v52/initiate"; + + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> service.createBaseURL(testUrl)); + assertEquals("please provide a live url prefix in the client", e.getMessage()); + } + + @Test + public void testLiveCheckoutUrlWithPrefix() { + config.setLiveEndpointUrlPrefix("123456789-company"); + String testUrl = "https://checkout-test.adyen.com/v68/payments"; + String expectedUrl = + "https://123456789-company-checkout-live.adyenpayments.com/checkout/v68/payments"; + + String actualUrl = service.createBaseURL(testUrl); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testLiveCheckoutUrlWithoutPrefix() { + String testUrl = "https://checkout-test.adyen.com/v68/payments"; + + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> service.createBaseURL(testUrl)); + assertEquals("please provide a live url prefix in the client", e.getMessage()); + } + + @Test + public void testLiveCheckoutPosSdkUrlWithPrefix() { + config.setLiveEndpointUrlPrefix("123456789-company"); + String testUrl = "https://checkout-test.adyen.com/possdk/v68/sessions"; + String expectedUrl = + "https://123456789-company-checkout-live.adyenpayments.com/possdk/v68/sessions"; + + String actualUrl = service.createBaseURL(testUrl); + assertEquals(expectedUrl, actualUrl); + } +} diff --git a/src/test/java/com/adyen/httpclient/ClientTest.java b/src/test/java/com/adyen/httpclient/ClientTest.java index f2c1668af..f7b176a74 100644 --- a/src/test/java/com/adyen/httpclient/ClientTest.java +++ b/src/test/java/com/adyen/httpclient/ClientTest.java @@ -26,26 +26,50 @@ public class ClientTest extends BaseTest { @Mock private SSLContext clientCertificateAuthSSLContext; - @Mock private String apiKey; - @Test public void testConfigTestClient() { Config config = new Config(); config.setEnvironment(Environment.TEST); - config.setApiKey(apiKey); + config.setApiKey("Your-X-API-KEY"); Client client = new Client(config); assertEquals(Environment.TEST, client.getConfig().getEnvironment()); } @Test public void testConfigLiveClient() { - Config config = new Config(); - config.setEnvironment(Environment.LIVE); - config.setLiveEndpointUrlPrefix("prefix"); - config.setApiKey(apiKey); + Config config = + new Config() + .environment(Environment.LIVE) + .liveEndpointUrlPrefix("myCompany") + .apiKey("Your-X-API-KEY"); Client client = new Client(config); assertEquals(Environment.LIVE, client.getConfig().getEnvironment()); - assertEquals("prefix", client.getConfig().getLiveEndpointUrlPrefix()); + assertEquals("myCompany", client.getConfig().getLiveEndpointUrlPrefix()); + } + + @Test + public void testConstructorConfigLiveClient() { + Client client = new Client("Your-X-API-KEY", Environment.LIVE, "myCompany"); + assertEquals(Environment.LIVE, client.getConfig().getEnvironment()); + assertEquals("myCompany", client.getConfig().getLiveEndpointUrlPrefix()); + } + + @Test + public void testConfigLiveClientWithBasicAuth() { + Client client = new Client("", "", Environment.LIVE, "myCompany"); + assertEquals(Environment.LIVE, client.getConfig().getEnvironment()); + assertEquals("myCompany", client.getConfig().getLiveEndpointUrlPrefix()); + assertNull(client.getConfig().getApiKey()); + assertNull(client.getConfig().getApplicationName()); + } + + @Test + public void testConfigLiveClientWithBasicAuthAndApplication() { + Client client = new Client("", "", Environment.LIVE, "myCompany", "myApplication"); + assertEquals(Environment.LIVE, client.getConfig().getEnvironment()); + assertEquals("myCompany", client.getConfig().getLiveEndpointUrlPrefix()); + assertEquals("myApplication", client.getConfig().getApplicationName()); + assertNull(client.getConfig().getApiKey()); } private static Stream provideCloudTestEndpointTestCases() { @@ -61,11 +85,10 @@ private static Stream provideCloudTestEndpointTestCases() { @MethodSource("provideCloudTestEndpointTestCases") public void testGetCloudEndpointForTestEnvironment( Region region, Environment environment, String expectedEndpoint) { - Config testConfig = new Config(); - testConfig.setEnvironment(Environment.TEST); - testConfig.setTerminalApiRegion(region); - Client testClient = new Client(testConfig); - assertEquals(expectedEndpoint, testConfig.getTerminalApiCloudEndpoint()); + Config config = new Config().environment(environment).terminalApiRegion(region).apiKey("Your-X-API-KEY"); + Client client = new Client(config); + + assertEquals(expectedEndpoint, config.getTerminalApiCloudEndpoint()); } private static Stream provideCloudLiveEndpointTestCases() { @@ -81,11 +104,10 @@ private static Stream provideCloudLiveEndpointTestCases() { @MethodSource("provideCloudLiveEndpointTestCases") public void testGetCloudEndpointForLiveEnvironment( Region region, Environment environment, String expectedEndpoint) { - Config liveConfig = new Config(); - liveConfig.setEnvironment(Environment.LIVE); - liveConfig.setTerminalApiRegion(region); - Client liveClient = new Client(liveConfig); - assertEquals(expectedEndpoint, liveConfig.getTerminalApiCloudEndpoint()); + Config config = new Config().environment(environment).terminalApiRegion(region).apiKey("Your-X-API-KEY"); + Client client = new Client(config); + + assertEquals(expectedEndpoint, config.getTerminalApiCloudEndpoint()); } @Test @@ -99,7 +121,7 @@ public void testUnmappedIndiaRegionThrowsException() { @Test public void testClientCertificateAuth() { - Client client = new Client(clientCertificateAuthSSLContext, apiKey); + Client client = new Client(clientCertificateAuthSSLContext, "Your-X-API-KEY"); assertEquals(Environment.LIVE, client.getConfig().getEnvironment()); }