From 6f90d0653bf5297177016580e7005e876587732b Mon Sep 17 00:00:00 2001 From: gcatanese Date: Thu, 20 Aug 2026 16:28:39 +0200 Subject: [PATCH] Setup and add integration tests --- .gitignore | 1 + pom.xml | 87 ++++++- src/integration-test/AGENTS.md | 71 ++++++ src/integration-test/README.md | 240 ++++++++++++++++++ .../java/com/adyen/BaseIntegrationTest.java | 131 ++++++++++ .../java/com/adyen/IntegrationTestTags.java | 17 ++ .../balanceplatform/PlatformApiIT.java | 41 +++ .../service/checkout/DonationsApiIT.java | 41 +++ .../service/checkout/ModificationsApiIT.java | 93 +++++++ .../adyen/service/checkout/OrdersApiIT.java | 52 ++++ .../service/checkout/PaymentLinksApiIT.java | 72 ++++++ .../adyen/service/checkout/PaymentsApiIT.java | 160 ++++++++++++ .../LegalEntitiesApiIT.java | 72 ++++++ .../resources/config.properties.example | 13 + ...va => BaseCloudDeviceIntegrationTest.java} | 8 +- .../clouddevice/CloudDeviceApiTerminalIT.java | 4 +- 16 files changed, 1096 insertions(+), 7 deletions(-) create mode 100644 src/integration-test/AGENTS.md create mode 100644 src/integration-test/README.md create mode 100644 src/integration-test/java/com/adyen/BaseIntegrationTest.java create mode 100644 src/integration-test/java/com/adyen/IntegrationTestTags.java create mode 100644 src/integration-test/java/com/adyen/service/balanceplatform/PlatformApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/checkout/DonationsApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/checkout/ModificationsApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/checkout/OrdersApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/checkout/PaymentLinksApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/checkout/PaymentsApiIT.java create mode 100644 src/integration-test/java/com/adyen/service/legalentitymanagement/LegalEntitiesApiIT.java create mode 100644 src/integration-test/resources/config.properties.example rename src/test/java/com/adyen/{BaseIntegrationTest.java => BaseCloudDeviceIntegrationTest.java} (89%) diff --git a/.gitignore b/.gitignore index 188d4f6fe..394c086f1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .DS_Store .vscode src/test/resources/config.properties +src/integration-test/resources/config.properties diff --git a/pom.xml b/pom.xml index 0296ef4e6..26eab4d6f 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,8 @@ 3.6.0 5.1.9 3.9.0 - 3.5.6 + 3.5.6 + 3.6.1 1.34.1 3.6.3 @@ -72,6 +73,48 @@ git@github.com:Adyen/adyen-java-api-library.git + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin-version} + + + add-integration-test-sources + generate-test-sources + + add-test-source + add-test-resource + + + + src/integration-test/java + + + + src/integration-test/resources + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-test-plugins-version} + + + + integration-test + verify + + + + + + org.apache.maven.plugins @@ -222,6 +265,7 @@ src/main/java/**/*.java src/test/java/**/*.java + src/integration-test/java/**/*.java ${google-java-format-version} @@ -238,7 +282,7 @@ org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin-version} + ${maven-test-plugins-version} org.apache.maven.plugins @@ -290,6 +334,45 @@ + + + integration-tests + + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + external + manual + + + + + + + manual-integration-tests + + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + manual + + + + + + diff --git a/src/integration-test/AGENTS.md b/src/integration-test/AGENTS.md new file mode 100644 index 000000000..3c3261629 --- /dev/null +++ b/src/integration-test/AGENTS.md @@ -0,0 +1,71 @@ + +## Scope + +These instructions apply to files under `src/integration-test`. + +## Before Writing a Test + +- Read `src/integration-test/README.md`. +- Verify the public service method and model types in `src/main/java`. +- Do not edit generated production models or services to make an integration test pass. +- Determine whether the test is automated external coverage or requires manual infrastructure. + +## Structure + +- Mirror the production package under `src/integration-test/java`. +- Name integration-test classes `*IT` so Maven Failsafe discovers them. +- Extend `BaseIntegrationTest` and use its typed configuration accessors. +- Use the service-specific client accessor when an API requires dedicated credentials, such as + `getLegalEntityManagementClient()` or `getBalancePlatformClient()`. +- Do not instantiate or close `Client` directly. `BaseIntegrationTest` caches clients by credential + and closes them after each test. +- Annotate external tests with `@Tag(IntegrationTestTags.EXTERNAL)`. +- Also annotate tests requiring a person, terminal, or other dedicated infrastructure with + `@Tag(IntegrationTestTags.MANUAL)`. +- Use behavior-focused names such as `shouldReturnValidationErrorWhenReferenceIsMissing`. +- Keep one observable behavior per test and use Arrange, Act, Assert sections. +- Prefer explicit imports, response types, and checked exceptions. Do not use wildcard imports, + `var`, or `throws Exception`. + +## Reliability and Safety + +- Generate unique references and idempotency keys for requests that create remote state. +- Do not share mutable state or depend on test execution order. +- Clean up remotely created resources when the API supports cleanup. +- For eventually consistent APIs, use bounded polling rather than fixed sleeps. +- Add a suitable timeout when an operation can otherwise wait indefinitely. +- Integration-test clients are configured for the TEST environment. Do not add LIVE-only behavior + or credentials without updating `BaseIntegrationTest` and this documentation. +- Do not use `@Disabled` as the normal opt-in mechanism. Use the Maven profiles and JUnit tags. +- Do not run an external integration test unless the user explicitly asks for that API call. + +## Configuration + +- Add typed accessors to `BaseIntegrationTest` instead of reading configuration directly in tests. +- Configuration lookup precedence is Java system property, environment variable, then + `src/integration-test/resources/config.properties`. +- Use environment variables or the ignored properties file for secrets. Command-line system + properties can be exposed through shell history or process listings. + +## Assertions and Comments + +- Assert stable contract fields, identifiers, statuses, and documented error codes. +- Assert exact error messages only when the wording is part of the documented contract. +- Include assertion messages that explain the violated contract. +- Extract repeated request construction and response assertions into focused helpers. +- Keep comments limited to prerequisites, non-obvious API constraints, and Arrange, Act, Assert + markers. Do not narrate straightforward Java. + +## Validation + +Validate generated tests without contacting Adyen: + +```bash +mvn spotless:apply +mvn -Pintegration-tests -DskipTests test-compile +mvn spotless:check checkstyle:check -DskipTests +``` + +Use the opt-in execution commands from `src/integration-test/README.md` only when external execution +is explicitly requested. + diff --git a/src/integration-test/README.md b/src/integration-test/README.md new file mode 100644 index 000000000..10ffae8d2 --- /dev/null +++ b/src/integration-test/README.md @@ -0,0 +1,240 @@ +# Integration tests + +Integration tests exercise the public Java API library against an Adyen endpoint. Maven Failsafe +discovers classes ending in `IT`, and opt-in profiles keep external calls out of the default build. + +## Quick start + +From the repository root: + +```bash +cp src/integration-test/resources/config.properties.example \ + src/integration-test/resources/config.properties + +mvn -Pintegration-tests test-compile \ + failsafe:integration-test failsafe:verify +``` + +The first command creates the ignored local configuration. Complete the values needed by the tests +you plan to run. The Maven command compiles all test sources but executes only integration tests. + +## Test profiles + +| Profile | Included tests | Purpose | +|---|---|---| +| none | No integration tests | Normal build and unit tests | +| `integration-tests` | Tagged `external`, excluding `manual` | Automated tests that call Adyen | +| `manual-integration-tests` | Tagged `manual` | Tests requiring a terminal, person, or dedicated infrastructure | + +The default lifecycle is safe from integration-test execution: + +```bash +mvn verify +``` + +## Running automated integration tests + +Run all automated integration tests: + +```bash +mvn -Pintegration-tests test-compile \ + failsafe:integration-test failsafe:verify +``` + +Run one class: + +```bash +mvn -Pintegration-tests test-compile \ + failsafe:integration-test failsafe:verify \ + -Dit.test=LegalEntitiesApiIT +``` + +Run one method: + +```bash +mvn -Pintegration-tests test-compile \ + failsafe:integration-test failsafe:verify \ + -Dit.test=LegalEntitiesApiIT#shouldCreateLegalEntityForIndividualResidingInTheNetherlands +``` + +Run multiple classes: + +```bash +mvn -Pintegration-tests test-compile \ + failsafe:integration-test failsafe:verify \ + -Dit.test=LegalEntitiesApiIT,PlatformApiIT +``` + +`-Dit.test` is the Failsafe selector. Do not use Surefire's `-Dtest` selector for these tests. + +## Running manual integration tests + +Always select one manual test at a time unless its service-specific documentation explicitly +permits concurrency: + +```bash +mvn -Pmanual-integration-tests test-compile \ + failsafe:integration-test failsafe:verify \ + -Dit.test=ExampleManualIT#shouldPerformManualOperation +``` + +There are currently no manual integration tests. This profile is reserved for future tests that +require a person, terminal, or dedicated infrastructure. + +## Local configuration + +The ignored local file is: + +```text +src/integration-test/resources/config.properties +``` + +Start from +[`config.properties.example`](resources/config.properties.example). Environment variables take +precedence over values in the properties file. Java system properties take precedence over both. + +Configuration lookup order: + +1. Java system property, for example `-DAPI_LIBRARIES_ADYEN_LEM_API_KEY=...` +2. Environment variable +3. `src/integration-test/resources/config.properties` + +Use environment variables or the ignored properties file for API keys. Command-line values may be +visible in shell history or process listings. + +The current typed configuration properties are: + +| Property | Used by | +|---|---| +| `API_LIBRARIES_ADYEN_API_KEY` | Checkout tests | +| `API_LIBRARIES_ADYEN_MERCHANT_ACCOUNT` | Checkout tests | +| `API_LIBRARIES_ADYEN_LEM_API_KEY` | Legal Entity Management tests | +| `API_LIBRARIES_ADYEN_BCL_API_KEY` | Balance Platform tests | +| `API_LIBRARIES_ADYEN_BALANCE_PLATFORM_ID` | Balance Platform tests | + +All integration-test clients currently use the Adyen TEST environment. `BaseIntegrationTest` +caches one client per credential during a test and closes all clients after each test. + +## Current coverage + +### Automated tests + +| Test | Behavior | +|---|---| +| `PaymentsApiIT` | Card payments, sessions, card brands, and available payment methods | +| `OrdersApiIT` | Creates a Checkout order | +| `PaymentLinksApiIT` | Creates a Checkout payment link | +| `DonationsApiIT` | Retrieves donation campaigns | +| `ModificationsApiIT` | Authorises and captures a payment | +| `LegalEntitiesApiIT` | Creates an individual legal entity | +| `PlatformApiIT` | Retrieves a balance platform | + +## Validation without external calls + +Format and compile integration-test sources without executing them: + +```bash +mvn spotless:apply +mvn -Pintegration-tests -DskipTests test-compile +mvn spotless:check checkstyle:check -DskipTests +``` + +`-DskipTests` is required for offline validation. Do not run an integration-test profile without it +unless the external API calls are intentional. + +Failsafe writes execution reports to: + +```text +target/failsafe-reports/ +``` + +## Project layout + +```text +src/integration-test/ +├── AGENTS.md +├── README.md +├── java/com/adyen/ +│ ├── BaseIntegrationTest.java +│ ├── IntegrationTestTags.java +│ └── service//*IT.java +└── resources/ + ├── config.properties.example + └── config.properties +``` + +Packages mirror production packages under `src/main/java`. + +## Conventions for new tests + +1. Name classes `*IT` and methods with behavior-focused `should...When...` names. +2. Extend `BaseIntegrationTest`. +3. Tag external classes with `external`; add `manual` when dedicated infrastructure is required. +4. Keep one observable behavior per test and use Arrange, Act, Assert sections. +5. Generate unique references and idempotency keys for requests that create remote state. +6. Use explicit imports, response types, and checked exceptions. +7. Assert stable response fields and documented error codes. +8. Extract repeated request construction and contract assertions into focused private helpers. +9. Keep tests independent and clean up remote resources where supported. +10. Use bounded polling and suitable timeouts instead of fixed sleeps or unbounded waits. +11. Use the typed client accessor matching the API credential. +12. Never execute external tests during routine agent validation. + +More specific agent instructions are in [`AGENTS.md`](AGENTS.md). + +## Minimal template + +```java +package com.adyen.service.example; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(EXTERNAL) +public class ExampleOperationIT extends BaseIntegrationTest { + + @Test + public void shouldReturnExpectedResultWhenRequestIsValid() throws ApiException, IOException { + // Arrange + ExampleRequest request = createRequest(); + ExampleApi exampleApi = new ExampleApi(getClient()); + + // Act + ExampleResponse response = exampleApi.exampleOperation(request); + + // Assert + assertNotNull(response, "The API response must not be null"); + } +} +``` + +Add the concrete model, service, and exception imports required by the API under test. + +## Troubleshooting + +### No integration tests were discovered + +- Activate the correct profile. +- Confirm the class name ends in `IT`. +- Confirm automated tests use the `external` tag and manual tests use the `manual` tag. +- Use `-Dit.test`, not `-Dtest`. + +### A required property is not defined + +Confirm `src/integration-test/resources/config.properties` exists and contains the local values +required by the selected test. Java system properties override environment variables, which +override the file. + +### Checkout returns HTTP 403 with error code `010` + +The API credential or merchant account is not allowed to perform the operation. This is an account +permission or merchant-access issue rather than a test compilation problem. + +### The wrong API credential is used + +Confirm the test uses the service-specific client accessor. Legal Entity Management tests use +`getLegalEntityManagementClient()`, and Balance Platform tests use `getBalancePlatformClient()`. diff --git a/src/integration-test/java/com/adyen/BaseIntegrationTest.java b/src/integration-test/java/com/adyen/BaseIntegrationTest.java new file mode 100644 index 000000000..434e01981 --- /dev/null +++ b/src/integration-test/java/com/adyen/BaseIntegrationTest.java @@ -0,0 +1,131 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2025 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen; + +import com.adyen.enums.Environment; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; + +/** Shared client and local configuration access for integration tests. */ +public abstract class BaseIntegrationTest { + + private static final String CONFIGURATION_RESOURCE = "/config.properties"; + private static final String API_KEY_PROPERTY = "API_LIBRARIES_ADYEN_API_KEY"; + private static final String LEM_API_KEY_PROPERTY = "API_LIBRARIES_ADYEN_LEM_API_KEY"; + private static final String BCL_API_KEY_PROPERTY = "API_LIBRARIES_ADYEN_BCL_API_KEY"; + private static final Properties PROPERTIES = loadProperties(); + private final Map clients = new HashMap<>(); + + protected final Client getClient() { + return getClient(API_KEY_PROPERTY); + } + + protected final Client getLegalEntityManagementClient() { + return getClient(LEM_API_KEY_PROPERTY); + } + + protected final Client getBalancePlatformClient() { + return getClient(BCL_API_KEY_PROPERTY); + } + + private Client getClient(String apiKeyProperty) { + return clients.computeIfAbsent( + apiKeyProperty, + property -> + new Client(new Config().apiKey(getProperty(property)).environment(getEnvironment()))); + } + + @AfterEach + public final void closeClients() throws IOException { + IOException failure = null; + for (Client client : clients.values()) { + try { + client.close(); + } catch (IOException exception) { + if (failure == null) { + failure = exception; + } else { + failure.addSuppressed(exception); + } + } + } + clients.clear(); + + if (failure != null) { + throw failure; + } + } + + protected final Environment getEnvironment() { + return Environment.TEST; + } + + protected final String getApiKey() { + return getProperty(API_KEY_PROPERTY); + } + + protected final String getMerchantAccount() { + return getProperty("API_LIBRARIES_ADYEN_MERCHANT_ACCOUNT"); + } + + protected final String getBalancePlatformId() { + return getProperty("API_LIBRARIES_ADYEN_BALANCE_PLATFORM_ID"); + } + + protected final String getTerminalDeviceId() { + return getProperty("ADYEN_TERMINAL_DEVICE_ID"); + } + + protected final String getTerminalDeviceKeyIdentifier() { + return getProperty("ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER"); + } + + protected final String getTerminalDevicePassphrase() { + return getProperty("ADYEN_TERMINAL_DEVICE_PASSPHRASE"); + } + + private static Properties loadProperties() { + Properties properties = new Properties(); + try (InputStream inputStream = + BaseIntegrationTest.class.getResourceAsStream(CONFIGURATION_RESOURCE)) { + if (inputStream != null) { + properties.load(inputStream); + } + } catch (IOException exception) { + throw new IllegalStateException( + "Unable to load integration-test configuration from " + CONFIGURATION_RESOURCE, + exception); + } + return properties; + } + + private static String getProperty(String name) { + String property = getOptionalProperty(name); + + if (property == null) { + throw new IllegalStateException("Integration-test property " + name + " is not defined"); + } + + return property; + } + + private static String getOptionalProperty(String name) { + String property = System.getProperty(name); + if (property == null || property.isBlank()) { + property = System.getenv(name); + } + if (property == null || property.isBlank()) { + property = PROPERTIES.getProperty(name); + } + return property == null || property.isBlank() ? null : property; + } +} diff --git a/src/integration-test/java/com/adyen/IntegrationTestTags.java b/src/integration-test/java/com/adyen/IntegrationTestTags.java new file mode 100644 index 000000000..73fd6c419 --- /dev/null +++ b/src/integration-test/java/com/adyen/IntegrationTestTags.java @@ -0,0 +1,17 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen; + +/** JUnit tags shared by integration tests and Maven profile filters. */ +public final class IntegrationTestTags { + + public static final String EXTERNAL = "external"; + public static final String MANUAL = "manual"; + + private IntegrationTestTags() {} +} diff --git a/src/integration-test/java/com/adyen/service/balanceplatform/PlatformApiIT.java b/src/integration-test/java/com/adyen/service/balanceplatform/PlatformApiIT.java new file mode 100644 index 000000000..d50abcb52 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/balanceplatform/PlatformApiIT.java @@ -0,0 +1,41 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.balanceplatform; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.balanceplatform.BalancePlatform; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class PlatformApiIT extends BaseIntegrationTest { + + // Scenario: Retrieve a balance platform + @Test + public void shouldRetrieveBalancePlatform() throws ApiException, IOException { + // Arrange + String balancePlatformId = getBalancePlatformId(); + PlatformApi platformApi = new PlatformApi(getBalancePlatformClient()); + + // Act + BalancePlatform response = platformApi.getBalancePlatform(balancePlatformId); + + // Assert + assertEquals( + balancePlatformId, + response.getId(), + "The response must contain the requested balance platform ID"); + } +} diff --git a/src/integration-test/java/com/adyen/service/checkout/DonationsApiIT.java b/src/integration-test/java/com/adyen/service/checkout/DonationsApiIT.java new file mode 100644 index 000000000..a8ccc8780 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/checkout/DonationsApiIT.java @@ -0,0 +1,41 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.checkout; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.checkout.DonationCampaignsRequest; +import com.adyen.model.checkout.DonationCampaignsResponse; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class DonationsApiIT extends BaseIntegrationTest { + + // Scenario: Get donation campaigns + @Test + public void shouldGetDonationCampaigns() throws ApiException, IOException { + // Arrange + DonationCampaignsRequest request = + new DonationCampaignsRequest().merchantAccount(getMerchantAccount()).currency("EUR"); + DonationsApi donationsApi = new DonationsApi(getClient()); + + // Act + DonationCampaignsResponse response = donationsApi.donationCampaigns(request); + + // Assert + assertNotNull( + response.getDonationCampaigns(), "The donation campaigns response must contain an array"); + } +} diff --git a/src/integration-test/java/com/adyen/service/checkout/ModificationsApiIT.java b/src/integration-test/java/com/adyen/service/checkout/ModificationsApiIT.java new file mode 100644 index 000000000..32bd43b18 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/checkout/ModificationsApiIT.java @@ -0,0 +1,93 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.checkout; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.checkout.Amount; +import com.adyen.model.checkout.CardDetails; +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.PaymentCaptureRequest; +import com.adyen.model.checkout.PaymentCaptureResponse; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.model.checkout.PaymentResponse; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import java.util.UUID; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class ModificationsApiIT extends BaseIntegrationTest { + + // Scenario: Capture an authorised payment + @Test + public void shouldCaptureAuthorisedPayment() throws ApiException, IOException { + // Arrange + String paymentPspReference = createAuthorisedUncapturedPayment(); + String reference = "capture-it-" + UUID.randomUUID(); + PaymentCaptureRequest request = + new PaymentCaptureRequest() + .reference(reference) + .merchantAccount(getMerchantAccount()) + .amount(new Amount().value(2000L).currency("EUR")); + ModificationsApi modificationsApi = new ModificationsApi(getClient()); + + // Act + PaymentCaptureResponse response = + modificationsApi.captureAuthorisedPayment(paymentPspReference, request); + + // Assert + assertNotNull(response.getPspReference(), "The capture must have a PSP reference"); + assertFalse( + response.getPspReference().isBlank(), "The capture PSP reference must not be blank"); + assertEquals( + paymentPspReference, + response.getPaymentPspReference(), + "The capture must reference the authorised payment"); + assertEquals( + PaymentCaptureResponse.StatusEnum.RECEIVED, + response.getStatus(), + "The capture request must be received"); + } + + private String createAuthorisedUncapturedPayment() throws ApiException, IOException { + CardDetails cardDetails = + new CardDetails() + .type(CardDetails.TypeEnum.SCHEME) + .encryptedCardNumber("test_4111111111111111") + .encryptedExpiryMonth("test_03") + .encryptedExpiryYear("test_2030") + .encryptedSecurityCode("test_737"); + PaymentRequest request = + new PaymentRequest() + .amount(new Amount().value(2000L).currency("EUR")) + .reference("capture-source-it-" + UUID.randomUUID()) + .paymentMethod(new CheckoutPaymentMethod(cardDetails)) + .returnUrl("https://example.com/checkout/return") + .merchantAccount(getMerchantAccount()) + // Defer auto-capture so the payment stays in the authorised state for manual capture + .captureDelayHours(72); + PaymentResponse response = new PaymentsApi(getClient()).payments(request); + + assertEquals( + PaymentResponse.ResultCodeEnum.AUTHORISED, + response.getResultCode(), + "The source payment must be authorised"); + assertNotNull(response.getPspReference(), "The source payment must have a PSP reference"); + assertFalse( + response.getPspReference().isBlank(), "The source payment PSP reference must not be blank"); + return response.getPspReference(); + } +} diff --git a/src/integration-test/java/com/adyen/service/checkout/OrdersApiIT.java b/src/integration-test/java/com/adyen/service/checkout/OrdersApiIT.java new file mode 100644 index 000000000..97d5dba06 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/checkout/OrdersApiIT.java @@ -0,0 +1,52 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.checkout; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.RequestOptions; +import com.adyen.model.checkout.Amount; +import com.adyen.model.checkout.CreateOrderRequest; +import com.adyen.model.checkout.CreateOrderResponse; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import java.util.UUID; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class OrdersApiIT extends BaseIntegrationTest { + + // Scenario: Create an order + @Test + public void shouldCreateOrder() throws ApiException, IOException { + // Arrange + String reference = "order-it-" + UUID.randomUUID(); + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + CreateOrderRequest request = + new CreateOrderRequest() + .reference(reference) + .amount(new Amount().value(2500L).currency("EUR")) + .merchantAccount(getMerchantAccount()); + OrdersApi ordersApi = new OrdersApi(getClient()); + + // Act + CreateOrderResponse response = ordersApi.orders(request, requestOptions); + + // Assert + assertEquals( + CreateOrderResponse.ResultCodeEnum.SUCCESS, + response.getResultCode(), + "The order must be created successfully"); + } +} diff --git a/src/integration-test/java/com/adyen/service/checkout/PaymentLinksApiIT.java b/src/integration-test/java/com/adyen/service/checkout/PaymentLinksApiIT.java new file mode 100644 index 000000000..52f0fdd21 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/checkout/PaymentLinksApiIT.java @@ -0,0 +1,72 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.checkout; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.RequestOptions; +import com.adyen.model.checkout.Address; +import com.adyen.model.checkout.Amount; +import com.adyen.model.checkout.PaymentLinkRequest; +import com.adyen.model.checkout.PaymentLinkResponse; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import java.util.UUID; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class PaymentLinksApiIT extends BaseIntegrationTest { + + // Scenario: Create a payment link + @Test + public void shouldCreatePaymentLink() throws ApiException, IOException { + // Arrange + String reference = "payment-link-it-" + UUID.randomUUID(); + String shopperReference = "shopper-it-" + UUID.randomUUID(); + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + Address address = + new Address() + .street("Roque Petroni Jr") + .postalCode("59000060") + .city("São Paulo") + .houseNumberOrName("999") + .country("BR") + .stateOrProvince("SP"); + PaymentLinkRequest request = + new PaymentLinkRequest() + .reference(reference) + .amount(new Amount().value(1250L).currency("BRL")) + .countryCode("BR") + .merchantAccount(getMerchantAccount()) + .shopperReference(shopperReference) + .shopperEmail("test@email.com") + .shopperLocale("pt-BR") + .billingAddress(address) + .deliveryAddress(address); + PaymentLinksApi paymentLinksApi = new PaymentLinksApi(getClient()); + + // Act + PaymentLinkResponse response = paymentLinksApi.paymentLinks(request, requestOptions); + + // Assert + assertNotNull(response.getId(), "The payment link must have an ID"); + assertFalse(response.getId().isBlank(), "The payment link ID must not be blank"); + assertEquals( + PaymentLinkResponse.StatusEnum.ACTIVE, + response.getStatus(), + "The payment link must be active"); + } +} diff --git a/src/integration-test/java/com/adyen/service/checkout/PaymentsApiIT.java b/src/integration-test/java/com/adyen/service/checkout/PaymentsApiIT.java new file mode 100644 index 000000000..40bb4f7a1 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/checkout/PaymentsApiIT.java @@ -0,0 +1,160 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.checkout; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.RequestOptions; +import com.adyen.model.checkout.Amount; +import com.adyen.model.checkout.CardBrandDetails; +import com.adyen.model.checkout.CardDetails; +import com.adyen.model.checkout.CardDetailsRequest; +import com.adyen.model.checkout.CardDetailsResponse; +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.CreateCheckoutSessionRequest; +import com.adyen.model.checkout.CreateCheckoutSessionResponse; +import com.adyen.model.checkout.PaymentMethodsRequest; +import com.adyen.model.checkout.PaymentMethodsResponse; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.model.checkout.PaymentResponse; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class PaymentsApiIT extends BaseIntegrationTest { + + private static final String RETURN_URL = "https://example.com/checkout/return"; + + // Scenario: Make a successful card payment + @Test + public void shouldMakeSuccessfulCardPayment() throws ApiException, IOException { + // Arrange + String reference = "payment-it-" + UUID.randomUUID(); + CardDetails cardDetails = + new CardDetails() + .type(CardDetails.TypeEnum.SCHEME) + .encryptedCardNumber("test_4111111111111111") + .encryptedExpiryMonth("test_03") + .encryptedExpiryYear("test_2030") + .encryptedSecurityCode("test_737"); + PaymentRequest request = + new PaymentRequest() + .amount(new Amount().currency("USD").value(1000L)) + .reference(reference) + .paymentMethod(new CheckoutPaymentMethod(cardDetails)) + .returnUrl(RETURN_URL) + .merchantAccount(getMerchantAccount()); + PaymentsApi paymentsApi = new PaymentsApi(getClient()); + + // Act + PaymentResponse response = paymentsApi.payments(request); + + // Assert + assertNotNull(response.getPspReference(), "The payment must have a PSP reference"); + assertFalse(response.getPspReference().isBlank(), "The PSP reference must not be blank"); + assertEquals( + PaymentResponse.ResultCodeEnum.AUTHORISED, + response.getResultCode(), + "The payment must be authorised"); + } + + // Scenario: Create a payment session + @Test + public void shouldCreatePaymentSession() throws ApiException, IOException { + // Arrange + String reference = "session-it-" + UUID.randomUUID(); + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + CreateCheckoutSessionRequest request = + new CreateCheckoutSessionRequest() + .merchantAccount(getMerchantAccount()) + .amount(new Amount().value(100L).currency("EUR")) + .returnUrl(RETURN_URL) + .reference(reference) + .countryCode("NL"); + PaymentsApi paymentsApi = new PaymentsApi(getClient()); + + // Act + CreateCheckoutSessionResponse response = paymentsApi.sessions(request, requestOptions); + + // Assert + assertNotNull(response.getId(), "The payment session must have an ID"); + assertFalse(response.getId().isBlank(), "The payment session ID must not be blank"); + } + + // Scenario: List brands for a card + @Test + public void shouldListBrandsForCard() throws ApiException, IOException { + // Arrange + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + CardDetailsRequest request = + new CardDetailsRequest().merchantAccount(getMerchantAccount()).cardNumber("411111"); + PaymentsApi paymentsApi = new PaymentsApi(getClient()); + + // Act + CardDetailsResponse response = paymentsApi.cardDetails(request, requestOptions); + + // Assert + assertNotNull(response.getBrands(), "The response must contain card brands"); + assertFalse(response.getBrands().isEmpty(), "The card brands must not be empty"); + assertEquals("visa", response.getBrands().get(0).getType(), "The first brand must be Visa"); + } + + // Scenario: List supported brands for a card + @Test + public void shouldListSupportedBrandsForCard() throws ApiException, IOException { + // Arrange + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + CardDetailsRequest request = + new CardDetailsRequest() + .merchantAccount(getMerchantAccount()) + .cardNumber("411111") + .supportedBrands(List.of("visa", "mc", "amex")); + PaymentsApi paymentsApi = new PaymentsApi(getClient()); + + // Act + CardDetailsResponse response = paymentsApi.cardDetails(request, requestOptions); + + // Assert + assertNotNull(response.getBrands(), "The response must contain card brands"); + assertFalse(response.getBrands().isEmpty(), "The card brands must not be empty"); + CardBrandDetails firstBrand = response.getBrands().get(0); + assertEquals("visa", firstBrand.getType(), "The first brand must be Visa"); + assertEquals(Boolean.TRUE, firstBrand.getSupported(), "Visa must be supported"); + } + + // Scenario: List available payment methods + @Test + public void shouldListAvailablePaymentMethods() throws ApiException, IOException { + // Arrange + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + PaymentMethodsRequest request = + new PaymentMethodsRequest().merchantAccount(getMerchantAccount()); + PaymentsApi paymentsApi = new PaymentsApi(getClient()); + + // Act + PaymentMethodsResponse response = paymentsApi.paymentMethods(request, requestOptions); + + // Assert + assertNotNull(response.getPaymentMethods(), "The response must contain payment methods"); + assertFalse(response.getPaymentMethods().isEmpty(), "The payment methods must not be empty"); + } +} diff --git a/src/integration-test/java/com/adyen/service/legalentitymanagement/LegalEntitiesApiIT.java b/src/integration-test/java/com/adyen/service/legalentitymanagement/LegalEntitiesApiIT.java new file mode 100644 index 000000000..327592ec2 --- /dev/null +++ b/src/integration-test/java/com/adyen/service/legalentitymanagement/LegalEntitiesApiIT.java @@ -0,0 +1,72 @@ +/* + * Adyen Java API Library + * + * Copyright (c) 2026 Adyen B.V. + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ +package com.adyen.service.legalentitymanagement; + +import static com.adyen.IntegrationTestTags.EXTERNAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.adyen.BaseIntegrationTest; +import com.adyen.model.RequestOptions; +import com.adyen.model.legalentitymanagement.Address; +import com.adyen.model.legalentitymanagement.BirthData; +import com.adyen.model.legalentitymanagement.Individual; +import com.adyen.model.legalentitymanagement.LegalEntity; +import com.adyen.model.legalentitymanagement.LegalEntityInfoRequiredType; +import com.adyen.model.legalentitymanagement.Name; +import com.adyen.model.legalentitymanagement.PhoneNumber; +import com.adyen.service.exception.ApiException; +import java.io.IOException; +import java.util.UUID; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Tag(EXTERNAL) +@Timeout(60) +public class LegalEntitiesApiIT extends BaseIntegrationTest { + + // Scenario: Create a legal entity for an individual residing in the Netherlands + @Test + public void shouldCreateLegalEntityForIndividualResidingInTheNetherlands() + throws ApiException, IOException { + // Arrange + Address residentialAddress = + new Address() + .city("Amsterdam") + .country("NL") + .postalCode("1011DJ") + .street("Simon Carmiggeltstraat 6 - 50"); + Individual individual = + new Individual() + .residentialAddress(residentialAddress) + .name(new Name().firstName("Shelly").lastName("Eller")) + .phone(new PhoneNumber().number("+31858888138").type("mobile")) + .birthData(new BirthData().dateOfBirth("1990-06-21")) + .email("s.eller@example.com"); + LegalEntityInfoRequiredType request = + new LegalEntityInfoRequiredType() + .type(LegalEntityInfoRequiredType.TypeEnum.INDIVIDUAL) + .individual(individual); + RequestOptions requestOptions = + new RequestOptions().idempotencyKey(UUID.randomUUID().toString()); + LegalEntitiesApi legalEntitiesApi = new LegalEntitiesApi(getLegalEntityManagementClient()); + + // Act + LegalEntity response = legalEntitiesApi.createLegalEntity(request, requestOptions); + + // Assert + assertNotNull(response.getId(), "The legal entity must have an ID"); + assertFalse(response.getId().isBlank(), "The legal entity ID must not be blank"); + assertEquals( + LegalEntity.TypeEnum.INDIVIDUAL, + response.getType(), + "The legal entity type must be individual"); + } +} diff --git a/src/integration-test/resources/config.properties.example b/src/integration-test/resources/config.properties.example new file mode 100644 index 000000000..6b76b40bb --- /dev/null +++ b/src/integration-test/resources/config.properties.example @@ -0,0 +1,13 @@ +## Settings for integration testing + +API_LIBRARIES_ADYEN_API_KEY= +API_LIBRARIES_ADYEN_MERCHANT_ACCOUNT= + +API_LIBRARIES_ADYEN_BALANCE_PLATFORM_ID= +API_LIBRARIES_ADYEN_BCL_API_KEY= + +API_LIBRARIES_ADYEN_LEM_API_KEY= + +ADYEN_TERMINAL_DEVICE_ID= +ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER= +ADYEN_TERMINAL_DEVICE_PASSPHRASE= diff --git a/src/test/java/com/adyen/BaseIntegrationTest.java b/src/test/java/com/adyen/BaseCloudDeviceIntegrationTest.java similarity index 89% rename from src/test/java/com/adyen/BaseIntegrationTest.java rename to src/test/java/com/adyen/BaseCloudDeviceIntegrationTest.java index 1ef1d706f..6591bd759 100644 --- a/src/test/java/com/adyen/BaseIntegrationTest.java +++ b/src/test/java/com/adyen/BaseCloudDeviceIntegrationTest.java @@ -13,14 +13,14 @@ import java.util.Properties; /** - * Base class for Integration tests + * Base class for Cloud Device Integration tests * *

Define in src/test/resources the configuration for the tests * *

``` ADYEN_API_KEY= ADYEN_MERCHANT_ACCOUNT= ADYEN_TERMINAL_DEVICE_ID= * ADYEN_TERMINAL_DEVICE_KEY_IDENTIFIER= ADYEN_TERMINAL_DEVICE_PASSPHRASE= ``` */ -public class BaseIntegrationTest { +public class BaseCloudDeviceIntegrationTest { private static Properties properties = null; @@ -52,7 +52,9 @@ private Properties getProperties() { if (properties == null) { properties = new Properties(); try (InputStream inputStream = - BaseIntegrationTest.class.getClassLoader().getResourceAsStream("config.properties")) { + BaseCloudDeviceIntegrationTest.class + .getClassLoader() + .getResourceAsStream("config.properties")) { if (inputStream != null) { properties.load(inputStream); } diff --git a/src/test/java/com/adyen/service/clouddevice/CloudDeviceApiTerminalIT.java b/src/test/java/com/adyen/service/clouddevice/CloudDeviceApiTerminalIT.java index 979e5cdb7..8034cb891 100644 --- a/src/test/java/com/adyen/service/clouddevice/CloudDeviceApiTerminalIT.java +++ b/src/test/java/com/adyen/service/clouddevice/CloudDeviceApiTerminalIT.java @@ -1,6 +1,6 @@ package com.adyen.service.clouddevice; -import com.adyen.BaseIntegrationTest; +import com.adyen.BaseCloudDeviceIntegrationTest; import com.adyen.model.clouddevice.*; import com.adyen.model.tapi.*; import com.adyen.security.clouddevice.EncryptionCredentialDetails; @@ -38,7 +38,7 @@ *

  • Disable the test again * */ -public class CloudDeviceApiTerminalIT extends BaseIntegrationTest { +public class CloudDeviceApiTerminalIT extends BaseCloudDeviceIntegrationTest { @Disabled("Enable when you want to test with the Terminal") @Test