From 78cdb568a176b3391e23baf52e93c5704372c97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:48:55 +0200 Subject: [PATCH 1/7] feat: require environmentSubdomain or an explicit useLegacyDomain opt-out The merchant-specific subdomain is how merchants should reach the API, but it was optional and an unset value silently fell back to api.checkout.com, so a forgotten subdomain looked exactly like a deliberate opt-out and the SDK could not warn about either. Callers must now choose: set environmentSubdomain, or call the already-deprecated useLegacyDomain(). Both, or neither, throws. An invalid subdomain now throws instead of being quietly ignored, which is a second breaking change: callers passing a malformed value are currently served by the shared host and never find out. environmentSubdomain no longer needs environment() to be set first, since the EnvironmentSubdomain is now built when the configuration is assembled. The Previous (ABC) platform predates merchant-specific subdomains and stays exempt via requiresEnvironmentSubdomain(). Mirrors checkout-sdk-net#590. Refs INT-1688. --- README.md | 29 ++++++- gradle.properties | 2 +- .../checkout/AbstractCheckoutSdkBuilder.java | 42 +++++++++- .../checkout/CheckoutPreviousSdkBuilder.java | 7 ++ .../com/checkout/EnvironmentSubdomain.java | 35 ++++---- .../CheckoutSdkBuilderSynchronousTest.java | 5 ++ .../com/checkout/CheckoutSdkBuilderTest.java | 79 +++++++++++++++++++ .../CheckoutSdkTelemetryIntegrationTest.java | 1 + .../DefaultCheckoutConfigurationTest.java | 12 ++- .../java/com/checkout/SandboxTestFixture.java | 30 +++++-- .../SynchronousAsyncClientComparisonTest.java | 6 ++ .../checkout/_external/CheckoutSdkTest.java | 2 + 12 files changed, 207 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 1c58463ef..b3b17d46f 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ If you don't have your own API keys, you can sign up for a test account [here](h **PLEASE NEVER SHARE OR PUBLISH YOUR CHECKOUT CREDENTIALS.** +### Subdomain value + +Requests must be made through your merchant-specific subdomain (MSSD): the first 8 characters of your client ID (excluding `cli_`). For example, if your client ID is `cli_vkuhvk4vjn2edkps7dfsq6emqm`, your subdomain is `vkuhvk4v`. When `environmentSubdomain` is set the SDK sends requests to `https://vkuhvk4v.api.checkout.com`. See [Base URLs](https://api-reference.checkout.com/#section/Base-URLs) and [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints) for further details, and for where to find your unique client ID. + ### Default Default keys client instantiation can be done as follows: @@ -99,7 +103,7 @@ public static void main(String[] args) { .publicKey("public_key") // optional, only required for operations related with tokens .secretKey("secret_key") .environment(Environment.PRODUCTION) // required - .environmentSubdomain("subdomain") // optional, Merchant-specific DNS name + .environmentSubdomain("subdomain") // required, Merchant-specific DNS name, the first 8 characters of your client ID .executor() // optional for a custom Executor Service .build(); @@ -125,7 +129,7 @@ final CheckoutApi checkoutApi = CheckoutSdk.builder() //.clientCredentials(new URI("https://access.sandbox.checkout.com/connect/token"), "client_id", "client_secret") .scopes(OAuthScope.GATEWAY, OAuthScope.VAULT, OAuthScope.FX) .environment(Environment.PRODUCTION) // required - .environmentSubdomain("subdomain") // optional, Merchant-specific DNS name + .environmentSubdomain("subdomain") // required, Merchant-specific DNS name, the first 8 characters of your client ID .executor() // optional for a custom Executor Service .build(); @@ -149,7 +153,7 @@ public static void main(String[] args) { .publicKey("public_key") // optional, only required for operations related with tokens .secretKey("secret_key") .environment(Environment.PRODUCTION) // required - .environmentSubdomain("subdomain") // optional, Merchant-specific DNS name + .environmentSubdomain("subdomain") // optional for the Previous platform, Merchant-specific DNS name .executor() // optional for a custom Executor Service .build(); @@ -399,7 +403,7 @@ final CheckoutApi checkoutApi = CheckoutSdk.builder() .staticKeys() .secretKey("secret_key") .environment(Environment.PRODUCTION) // required - .environmentSubdomain("subdomain") // optional, Merchant-specific DNS name + .environmentSubdomain("subdomain") // required, Merchant-specific DNS name, the first 8 characters of your client ID .httpClientBuilder(customHttpClient) // optional for a custom HttpClient .build(); ``` @@ -656,6 +660,23 @@ final CheckoutApi checkoutApi = CheckoutSdk.builder() - All resilience patterns are optional - configure only what you need - Rate limiter helps respect API rate limits and prevent overwhelming the service +## Legacy domain (emergency use only) + +> :warning: **Only use if merchant specific sub domains are causing issues.** Connecting through your merchant-specific subdomain (see [Subdomain value](#subdomain-value)) is the supported way of using the Checkout.com API, and non-subdomain usage will be deprecated. + +If, in exceptional circumstances, you cannot use your merchant-specific subdomain, you can explicitly opt out by calling `useLegacyDomain()` instead of `environmentSubdomain(...)`: + +```java +final CheckoutApi checkoutApi = CheckoutSdk.builder() + .staticKeys() + .secretKey("secret_key") + .environment(Environment.SANDBOX) + .useLegacyDomain() // deprecated, emergency fallback only + .build(); +``` + +This routes requests to `api.checkout.com` (or `api.sandbox.checkout.com`) and `access.checkout.com` (or `access.sandbox.checkout.com`). The method is annotated `@Deprecated` and produces a compile-time warning. Exactly one of `environmentSubdomain(...)` or `useLegacyDomain()` must be set: the SDK throws a `CheckoutArgumentException` if both, or neither, are set. The Previous (ABC) platform predates merchant-specific subdomains and is exempt from this requirement. + ## Code of Conduct Please refer to [Code of Conduct](CODE_OF_CONDUCT.md) diff --git a/gradle.properties b/gradle.properties index cfdebb9dd..2802eb50e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.checkout -version=7.15.0 +version=8.0.0 project_name=Checkout SDK Java project_description=Checkout SDK for Java https://checkout.com diff --git a/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java b/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java index 0a193487f..6f2b946b5 100644 --- a/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java +++ b/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java @@ -9,7 +9,8 @@ public abstract class AbstractCheckoutSdkBuilder { protected HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); private IEnvironment environment; - private EnvironmentSubdomain environmentSubdomain; + private String subdomain; + private boolean useLegacyDomain; private Executor executor = ForkJoinPool.commonPool(); private TransportConfiguration transportConfiguration; private Boolean recordTelemetry = true; @@ -25,7 +26,23 @@ public AbstractCheckoutSdkBuilder environmentSubdomain(final String subdomain if (subdomain == null) { throw new CheckoutArgumentException("subdomain must be specified"); } - this.environmentSubdomain = new EnvironmentSubdomain(this.environment, subdomain); + this.subdomain = subdomain; + return this; + } + + /** + * Opts out of the merchant-specific subdomain, sending every request to the shared + * hosts instead ({@code api.checkout.com} and {@code access.checkout.com}, or their + * sandbox equivalents). + * + * @deprecated this is an emergency fallback for the rare case where the + * merchant-specific subdomain cannot be used, and will be removed in a future release. + * Call {@link #environmentSubdomain(String)} instead. + * See Base URLs. + */ + @Deprecated + public AbstractCheckoutSdkBuilder useLegacyDomain() { + this.useLegacyDomain = true; return this; } @@ -49,7 +66,16 @@ protected IEnvironment getEnvironment() { } protected EnvironmentSubdomain getEnvironmentSubdomain() { - return environmentSubdomain; + return subdomain != null ? new EnvironmentSubdomain(environment, subdomain) : null; + } + + /** + * Whether this builder requires the merchant-specific subdomain to be configured. + * The Previous (ABC) platform predates merchant-specific subdomains, so it overrides + * this to {@code false}. + */ + protected boolean requiresEnvironmentSubdomain() { + return true; } public AbstractCheckoutSdkBuilder recordTelemetry(final Boolean recordTelemetry) { @@ -73,6 +99,7 @@ protected CheckoutConfiguration getCheckoutConfiguration() { if (environment == null) { throw new CheckoutArgumentException("environment must be specified"); } + validateEnvironmentSettings(); final SdkCredentials sdkCredentials = getSdkCredentials(); if (transportConfiguration == null) { transportConfiguration = new DefaultTransportConfiguration(); @@ -80,6 +107,15 @@ protected CheckoutConfiguration getCheckoutConfiguration() { return buildCheckoutConfiguration(sdkCredentials); } + private void validateEnvironmentSettings() { + if (subdomain != null && useLegacyDomain) { + throw new CheckoutArgumentException("environmentSubdomain and useLegacyDomain cannot both be set - provide only your merchant-specific subdomain"); + } + if (subdomain == null && !useLegacyDomain && requiresEnvironmentSubdomain()) { + throw new CheckoutArgumentException("environmentSubdomain is required - provide your merchant-specific subdomain (the first 8 characters of your client ID, see https://api-reference.checkout.com/#section/Base-URLs), or call useLegacyDomain() to opt out only if merchant specific sub domains are causing issues"); + } + } + private CheckoutConfiguration buildCheckoutConfiguration(final SdkCredentials sdkCredentials) { return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration, recordTelemetry, synchronous, resilience4jConfiguration); } diff --git a/src/main/java/com/checkout/CheckoutPreviousSdkBuilder.java b/src/main/java/com/checkout/CheckoutPreviousSdkBuilder.java index 196f4ba2a..f4462a18b 100644 --- a/src/main/java/com/checkout/CheckoutPreviousSdkBuilder.java +++ b/src/main/java/com/checkout/CheckoutPreviousSdkBuilder.java @@ -13,6 +13,13 @@ public static class CheckoutStaticKeysSdkBuilder extends AbstractCheckoutSdkBuil private String publicKey; private String secretKey; + // The Previous (ABC) platform predates merchant-specific subdomains, so it is exempt + // from the mandatory environmentSubdomain/useLegacyDomain configuration. + @Override + protected boolean requiresEnvironmentSubdomain() { + return false; + } + public CheckoutStaticKeysSdkBuilder publicKey(final String publicKey) { this.publicKey = publicKey; return this; diff --git a/src/main/java/com/checkout/EnvironmentSubdomain.java b/src/main/java/com/checkout/EnvironmentSubdomain.java index 54841a6ed..671d39482 100644 --- a/src/main/java/com/checkout/EnvironmentSubdomain.java +++ b/src/main/java/com/checkout/EnvironmentSubdomain.java @@ -24,36 +24,29 @@ public URI getOAuthAuthorizationApi() { } /** - * Applies subdomain transformation to any given URI. - * If the subdomain is valid (alphanumeric pattern), prepends it to the host. - * Otherwise, returns the original URI unchanged. + * Applies subdomain transformation to any given URI, prepending the subdomain to the host. * * @param originalUrl the original URI to transform * @param subdomain the subdomain to prepend - * @return the transformed URI with subdomain, or original URI if subdomain is invalid + * @return the transformed URI with subdomain + * @throws CheckoutArgumentException if the subdomain is not a valid merchant-specific subdomain */ private static URI createUrlWithSubdomain(URI originalUrl, String subdomain) { - URI newEnvironment = null; + Pattern pattern = Pattern.compile("^(?:pl-)?[a-z0-9]+$"); + Matcher matcher = subdomain == null ? null : pattern.matcher(subdomain); + if (matcher == null || !matcher.matches()) { + throw new CheckoutArgumentException("invalid environment subdomain - provide your merchant-specific subdomain, the first 8 characters of your client ID (see https://api-reference.checkout.com/#section/Base-URLs)"); + } + + String host = originalUrl.getHost(); + String scheme = originalUrl.getScheme(); + int port = originalUrl.getPort(); + String newHost = subdomain + "." + host; try { - newEnvironment = new URI(originalUrl.toString()); + return new URI(scheme, null, newHost, port, originalUrl.getPath(), originalUrl.getQuery(), originalUrl.getFragment()); } catch (final URISyntaxException e) { throw new CheckoutException(e); } - - Pattern pattern = Pattern.compile("^(?:pl-)?[a-z0-9]+$"); - Matcher matcher = pattern.matcher(subdomain); - if (matcher.matches()) { - String host = originalUrl.getHost(); - String scheme = originalUrl.getScheme(); - int port = originalUrl.getPort(); - String newHost = subdomain + "." + host; - try { - newEnvironment = new URI(scheme, null, newHost, port, originalUrl.getPath(), originalUrl.getQuery(), originalUrl.getFragment()); - } catch (final URISyntaxException e) { - throw new CheckoutException(e); - } - } - return newEnvironment; } } diff --git a/src/test/java/com/checkout/CheckoutSdkBuilderSynchronousTest.java b/src/test/java/com/checkout/CheckoutSdkBuilderSynchronousTest.java index 859bc8fc4..77efd4744 100644 --- a/src/test/java/com/checkout/CheckoutSdkBuilderSynchronousTest.java +++ b/src/test/java/com/checkout/CheckoutSdkBuilderSynchronousTest.java @@ -18,6 +18,7 @@ void shouldCreateCheckoutApiWithSynchronousMode() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) .build(); @@ -35,6 +36,7 @@ void shouldCreateCheckoutApiWithResilience4jConfiguration() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .resilience4jConfiguration(resilience4jConfig) .build(); @@ -49,6 +51,7 @@ void shouldCreateCheckoutApiWithSynchronousAndResilience4j() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) .resilience4jConfiguration(resilience4jConfig) .build(); @@ -63,6 +66,7 @@ void shouldCreateCheckoutApiWithoutNewParameters() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); assertNotNull(checkoutApi); @@ -90,6 +94,7 @@ void shouldCreateCheckoutApiWithCustomResilience4jConfiguration() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) .resilience4jConfiguration(resilience4jConfig) .build(); diff --git a/src/test/java/com/checkout/CheckoutSdkBuilderTest.java b/src/test/java/com/checkout/CheckoutSdkBuilderTest.java index 77bdee7df..7ac520330 100644 --- a/src/test/java/com/checkout/CheckoutSdkBuilderTest.java +++ b/src/test/java/com/checkout/CheckoutSdkBuilderTest.java @@ -11,6 +11,7 @@ import static com.checkout.TestHelper.VALID_DEFAULT_SK; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -23,6 +24,7 @@ void shouldCreateStaticKeysCheckoutSdks() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); assertNotNull(checkoutApi1); @@ -30,6 +32,7 @@ void shouldCreateStaticKeysCheckoutSdks() { final CheckoutApi checkoutApi2 = new CheckoutSdkBuilder().staticKeys() .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); assertNotNull(checkoutApi2); @@ -66,6 +69,7 @@ void shouldCreateCheckoutAndInitOAuthSdk() throws URISyntaxException { .clientCredentials(new URI("test"), "client_id", "client_secret") .scopes(OAuthScope.GATEWAY) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); fail(); } catch (final CheckoutException e) { @@ -94,6 +98,78 @@ void shouldCreateOAuthSdkWithSubdomain() throws URISyntaxException { } + @SuppressWarnings("deprecation") + @Test + void shouldCreateStaticKeysCheckoutSdkWithLegacyDomain() { + + final CheckoutApi checkoutApi = new CheckoutSdkBuilder().staticKeys() + .publicKey(VALID_DEFAULT_PK) + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .useLegacyDomain() + .build(); + + assertNotNull(checkoutApi); + + } + + @Test + void shouldFailToCreateCheckoutSdkWithoutSubdomainOrLegacyDomain() { + + final CheckoutArgumentException exception = assertThrows(CheckoutArgumentException.class, + () -> new CheckoutSdkBuilder().staticKeys() + .publicKey(VALID_DEFAULT_PK) + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .build()); + + assertTrue(exception.getMessage().contains("environmentSubdomain is required")); + + } + + @SuppressWarnings("deprecation") + @Test + void shouldFailToCreateCheckoutSdkWithBothSubdomainAndLegacyDomain() { + + final CheckoutArgumentException exception = assertThrows(CheckoutArgumentException.class, + () -> new CheckoutSdkBuilder().staticKeys() + .publicKey(VALID_DEFAULT_PK) + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") + .useLegacyDomain() + .build()); + + assertTrue(exception.getMessage().contains("cannot both be set")); + + } + + @Test + void shouldFailToCreateCheckoutSdkWithInvalidSubdomain() { + + final CheckoutArgumentException exception = assertThrows(CheckoutArgumentException.class, + () -> new CheckoutSdkBuilder().staticKeys() + .publicKey(VALID_DEFAULT_PK) + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("not a subdomain") + .build()); + + assertTrue(exception.getMessage().contains("invalid environment subdomain")); + + } + + @Test + void shouldCreatePreviousSdkWithoutSubdomain() { + + assertNotNull(new CheckoutSdkBuilder().previous().staticKeys() + .publicKey(TestHelper.VALID_PREVIOUS_PK) + .secretKey(TestHelper.VALID_PREVIOUS_SK) + .environment(Environment.SANDBOX) + .build()); + + } + @Test void shouldFailToCreateCheckoutSdks() { @@ -102,6 +178,7 @@ void shouldFailToCreateCheckoutSdks() { .publicKey(INVALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); } catch (final Exception e) { assertTrue(e instanceof CheckoutArgumentException); @@ -113,6 +190,7 @@ void shouldFailToCreateCheckoutSdks() { .publicKey(VALID_DEFAULT_PK) .secretKey(INVALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); } catch (final Exception e) { assertTrue(e instanceof CheckoutArgumentException); @@ -123,6 +201,7 @@ void shouldFailToCreateCheckoutSdks() { new CheckoutSdkBuilder().staticKeys() .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) + .environmentSubdomain("1234doma") .build(); } catch (final Exception e) { assertTrue(e instanceof CheckoutArgumentException); diff --git a/src/test/java/com/checkout/CheckoutSdkTelemetryIntegrationTest.java b/src/test/java/com/checkout/CheckoutSdkTelemetryIntegrationTest.java index d6061afe6..6d4376067 100644 --- a/src/test/java/com/checkout/CheckoutSdkTelemetryIntegrationTest.java +++ b/src/test/java/com/checkout/CheckoutSdkTelemetryIntegrationTest.java @@ -67,6 +67,7 @@ private CheckoutApi buildCheckoutApi(CloseableHttpClient httpClientMock, boolean .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) .recordTelemetry(telemetryEnabled) .environment(SANDBOX) + .environmentSubdomain("1234doma") .httpClientBuilder(httpClientBuilderMock) .build(); } diff --git a/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java b/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java index e8f9da6e4..23dd8e852 100644 --- a/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java +++ b/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -66,14 +67,11 @@ void shouldCreateConfigurationWithSubdomain(String subdomain) { @ParameterizedTest @ValueSource(strings = {"", " ", " ", " - ", "a b", "ab c1", "foo-", "-foo", "ABC123", "FOO", "test-123", "foo-bar", "pl-"}) - void shouldCreateConfigurationWithBadSubdomain(String subdomain) { + void shouldFailWithBadSubdomain(String subdomain) { - final StaticKeysSdkCredentials credentials = Mockito.mock(StaticKeysSdkCredentials.class); - final EnvironmentSubdomain environmentSubdomain = new EnvironmentSubdomain(Environment.SANDBOX, subdomain); - - final CheckoutConfiguration configuration = new DefaultCheckoutConfiguration(credentials, Environment.SANDBOX, environmentSubdomain, DEFAULT_CLIENT_BUILDER, DEFAULT_EXECUTOR, DEFAULT_TRANSPORT_CONFIGURATION, false); - assertEquals("https://api.sandbox.checkout.com/", configuration.getEnvironmentSubdomain().getCheckoutApi().toString()); - assertEquals("https://access.sandbox.checkout.com/connect/token", configuration.getEnvironmentSubdomain().getOAuthAuthorizationApi().toString()); + final CheckoutArgumentException exception = assertThrows(CheckoutArgumentException.class, + () -> new EnvironmentSubdomain(Environment.SANDBOX, subdomain)); + assertTrue(exception.getMessage().contains("invalid environment subdomain")); } @Test diff --git a/src/test/java/com/checkout/SandboxTestFixture.java b/src/test/java/com/checkout/SandboxTestFixture.java index a88588b4d..411f76628 100644 --- a/src/test/java/com/checkout/SandboxTestFixture.java +++ b/src/test/java/com/checkout/SandboxTestFixture.java @@ -61,26 +61,26 @@ public SandboxTestFixture(final PlatformType platformType) { .setConnectionTimeToLive(60, TimeUnit.SECONDS) .evictIdleConnections(30, TimeUnit.SECONDS); - this.checkoutApi = CheckoutSdk.builder() + this.checkoutApi = configureDomain(CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) .environment(Environment.SANDBOX) .executor(Executors.newFixedThreadPool(100)) - .httpClientBuilder(httpClientBuilder) + .httpClientBuilder(httpClientBuilder)) .build(); break; case DEFAULT: - this.checkoutApi = CheckoutSdk.builder() + this.checkoutApi = configureDomain(CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) .environment(Environment.SANDBOX) - .executor(CUSTOM_EXECUTOR) + .executor(CUSTOM_EXECUTOR)) .build(); break; case DEFAULT_OAUTH: - this.checkoutApi = CheckoutSdk.builder() + this.checkoutApi = configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID")), @@ -92,13 +92,29 @@ public SandboxTestFixture(final PlatformType platformType) { OAuthScope.VAULT_CARD_METADATA, OAuthScope.FINANCIAL_ACTIONS, OAuthScope.FORWARD, OAuthScope.FORWARD_SECRETS, OAuthScope.PAYMENTS_SEARCH) .environment(Environment.SANDBOX) - .executor(CUSTOM_EXECUTOR) + .executor(CUSTOM_EXECUTOR)) .build(); - case CUSTOM: + case CUSTOM: break; } } + /** + * The merchant-specific subdomain is mandatory, so the fixtures read it from + * {@code CHECKOUT_MERCHANT_SUBDOMAIN}. Where that variable is not configured the suite + * falls back to the shared hosts, which is the only reason this touches the deprecated + * opt-out. + */ + @SuppressWarnings("deprecation") + protected static AbstractCheckoutSdkBuilder configureDomain( + final AbstractCheckoutSdkBuilder builder) { + final String subdomain = System.getenv("CHECKOUT_MERCHANT_SUBDOMAIN"); + if (subdomain != null && !subdomain.trim().isEmpty()) { + return builder.environmentSubdomain(subdomain); + } + return builder.useLegacyDomain(); + } + protected T blocking(final Supplier> supplier) { int attempts = 1; while (attempts <= TRY_MAX_ATTEMPTS) { diff --git a/src/test/java/com/checkout/SynchronousAsyncClientComparisonTest.java b/src/test/java/com/checkout/SynchronousAsyncClientComparisonTest.java index ef7c15467..0950d6e2a 100644 --- a/src/test/java/com/checkout/SynchronousAsyncClientComparisonTest.java +++ b/src/test/java/com/checkout/SynchronousAsyncClientComparisonTest.java @@ -28,6 +28,7 @@ void shouldUseSameMethodsForSyncAndAsyncClients() throws ExecutionException, Int .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) // Synchronous mode .build(); @@ -36,6 +37,7 @@ void shouldUseSameMethodsForSyncAndAsyncClients() throws ExecutionException, Int .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(false) // Asynchronous mode (default) .build(); @@ -111,6 +113,7 @@ void shouldHaveSameInterfaceForSyncAndAsyncClients() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) .build(); @@ -118,6 +121,7 @@ void shouldHaveSameInterfaceForSyncAndAsyncClients() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(false) .build(); @@ -142,6 +146,7 @@ void shouldGetSameResponseTypeFromBothClients() throws ExecutionException, Inter .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(true) .build(); @@ -149,6 +154,7 @@ void shouldGetSameResponseTypeFromBothClients() throws ExecutionException, Inter .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .synchronous(false) .build(); diff --git a/src/test/java/com/checkout/_external/CheckoutSdkTest.java b/src/test/java/com/checkout/_external/CheckoutSdkTest.java index 84a4b3a3e..18c480d69 100644 --- a/src/test/java/com/checkout/_external/CheckoutSdkTest.java +++ b/src/test/java/com/checkout/_external/CheckoutSdkTest.java @@ -41,6 +41,7 @@ void shouldCreatePreviousSdk() { .publicKey(VALID_PREVIOUS_PK) .secretKey(VALID_PREVIOUS_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); assertNotNull(defaultCheckoutApi); @@ -69,6 +70,7 @@ void shouldCreateSdk() { .publicKey(VALID_DEFAULT_PK) .secretKey(VALID_DEFAULT_SK) .environment(Environment.SANDBOX) + .environmentSubdomain("1234doma") .build(); assertNotNull(checkoutApi); From 03d0d6661e4398f42695a508bcf99434beec4454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:40:56 +0200 Subject: [PATCH 2/7] test: route every client the suite builds through TestDomainConfiguration Seven integration fixtures build their own clients outside SandboxTestFixture (OAuth, Issuing, Accounts, Accounts payout schedules, APM previews, card metadata), so the mandatory subdomain would have failed them at construction. They now share TestDomainConfiguration.configureDomain, which uses the shared hosts. Applying the merchant-specific subdomain instead looked better, since it is the path merchants are being moved to, but the sandbox OAuth clients are not provisioned for it: .NET CI failed 224 integration tests with invalid_client when the token request went to {subdomain}.access.sandbox.checkout.com. The reason is recorded on the class so nobody repeats the experiment. --- src/test/java/com/checkout/OAuthTestIT.java | 2 ++ .../java/com/checkout/SandboxTestFixture.java | 22 +++-------------- .../com/checkout/TestDomainConfiguration.java | 24 +++++++++++++++++++ .../accounts/AccountsPayoutSchedulesIT.java | 5 ++-- .../com/checkout/accounts/AccountsTestIT.java | 5 ++-- .../checkout/issuing/BaseIssuingTestIT.java | 5 ++-- .../com/checkout/metadata/CardMetadataIT.java | 5 ++-- .../payments/RequestApmPaymentsIT.java | 5 ++-- 8 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 src/test/java/com/checkout/TestDomainConfiguration.java diff --git a/src/test/java/com/checkout/OAuthTestIT.java b/src/test/java/com/checkout/OAuthTestIT.java index 2fe1f0bf8..9a07b5775 100644 --- a/src/test/java/com/checkout/OAuthTestIT.java +++ b/src/test/java/com/checkout/OAuthTestIT.java @@ -70,6 +70,7 @@ void shouldMakeOAuthCall() { } + @SuppressWarnings("deprecation") @Test void shouldInitAuthorization() { @@ -82,6 +83,7 @@ void shouldInitAuthorization() { "fake") .scopes(OAuthScope.GATEWAY) .environment(Environment.SANDBOX) + .useLegacyDomain() .build(); fail(); } catch (final Exception e) { diff --git a/src/test/java/com/checkout/SandboxTestFixture.java b/src/test/java/com/checkout/SandboxTestFixture.java index 411f76628..0703ae702 100644 --- a/src/test/java/com/checkout/SandboxTestFixture.java +++ b/src/test/java/com/checkout/SandboxTestFixture.java @@ -61,7 +61,7 @@ public SandboxTestFixture(final PlatformType platformType) { .setConnectionTimeToLive(60, TimeUnit.SECONDS) .evictIdleConnections(30, TimeUnit.SECONDS); - this.checkoutApi = configureDomain(CheckoutSdk.builder() + this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) @@ -71,7 +71,7 @@ public SandboxTestFixture(final PlatformType platformType) { .build(); break; case DEFAULT: - this.checkoutApi = configureDomain(CheckoutSdk.builder() + this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) @@ -80,7 +80,7 @@ public SandboxTestFixture(final PlatformType platformType) { .build(); break; case DEFAULT_OAUTH: - this.checkoutApi = configureDomain(CheckoutSdk.builder() + this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID")), @@ -99,22 +99,6 @@ public SandboxTestFixture(final PlatformType platformType) { } } - /** - * The merchant-specific subdomain is mandatory, so the fixtures read it from - * {@code CHECKOUT_MERCHANT_SUBDOMAIN}. Where that variable is not configured the suite - * falls back to the shared hosts, which is the only reason this touches the deprecated - * opt-out. - */ - @SuppressWarnings("deprecation") - protected static AbstractCheckoutSdkBuilder configureDomain( - final AbstractCheckoutSdkBuilder builder) { - final String subdomain = System.getenv("CHECKOUT_MERCHANT_SUBDOMAIN"); - if (subdomain != null && !subdomain.trim().isEmpty()) { - return builder.environmentSubdomain(subdomain); - } - return builder.useLegacyDomain(); - } - protected T blocking(final Supplier> supplier) { int attempts = 1; while (attempts <= TRY_MAX_ATTEMPTS) { diff --git a/src/test/java/com/checkout/TestDomainConfiguration.java b/src/test/java/com/checkout/TestDomainConfiguration.java new file mode 100644 index 000000000..9c2c569ea --- /dev/null +++ b/src/test/java/com/checkout/TestDomainConfiguration.java @@ -0,0 +1,24 @@ +package com.checkout; + +/** + * Every client the suite builds has to choose a domain now that the merchant-specific + * subdomain is mandatory, so they all come through here. + * + *

The suite uses the shared hosts. It would be better to exercise the merchant-specific + * subdomain, since that is the path merchants are being moved to, but the sandbox OAuth clients + * are not provisioned for it: pointing the token request at + * {@code {subdomain}.access.sandbox.checkout.com} returns {@code invalid_client} for every + * integration test. Until those clients are bound to the subdomain, CI has to use the legacy + * hosts. + */ +public final class TestDomainConfiguration { + + private TestDomainConfiguration() { + } + + @SuppressWarnings("deprecation") + public static AbstractCheckoutSdkBuilder configureDomain( + final AbstractCheckoutSdkBuilder builder) { + return builder.useLegacyDomain(); + } +} diff --git a/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java b/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java index 5af2d7004..6a736fb64 100644 --- a/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java +++ b/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java @@ -1,5 +1,6 @@ package com.checkout.accounts; +import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -194,13 +195,13 @@ private void validateScheduleResponseBase(final GetScheduleResponse response) { } private CheckoutApi getPayoutSchedulesCheckoutApi() { - return CheckoutSdk.builder() + return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_PAYOUT_SCHEDULE_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_PAYOUT_SCHEDULE_CLIENT_SECRET"))) .scopes(OAuthScope.MARKETPLACE) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); } diff --git a/src/test/java/com/checkout/accounts/AccountsTestIT.java b/src/test/java/com/checkout/accounts/AccountsTestIT.java index 61ec0fd1f..93ccbc757 100644 --- a/src/test/java/com/checkout/accounts/AccountsTestIT.java +++ b/src/test/java/com/checkout/accounts/AccountsTestIT.java @@ -1,5 +1,6 @@ package com.checkout.accounts; +import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -794,13 +795,13 @@ private IdResponse uploadFile() throws URISyntaxException { } private CheckoutApi getAccountsCheckoutApi() { - return CheckoutSdk.builder() + return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET"))) .scopes(OAuthScope.ACCOUNTS) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); } diff --git a/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java b/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java index 9b4ac598d..c976978e4 100644 --- a/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java +++ b/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java @@ -1,5 +1,6 @@ package com.checkout.issuing; +import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -34,7 +35,7 @@ public BaseIssuingTestIT() { } private CheckoutApi getIssuingCheckoutApi() { - return CheckoutSdk.builder() + return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID")), @@ -42,7 +43,7 @@ private CheckoutApi getIssuingCheckoutApi() { .scopes(OAuthScope.VAULT, OAuthScope.ISSUING_CLIENT, OAuthScope.ISSUING_CARD_MGMT, OAuthScope.ISSUING_CONTROLS_READ, OAuthScope.ISSUING_CONTROLS_WRITE, OAuthScope.ISSUING_TRANSACTIONS_READ, OAuthScope.ISSUING_TRANSACTIONS_WRITE) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); } diff --git a/src/test/java/com/checkout/metadata/CardMetadataIT.java b/src/test/java/com/checkout/metadata/CardMetadataIT.java index a780687bf..50762e1ac 100644 --- a/src/test/java/com/checkout/metadata/CardMetadataIT.java +++ b/src/test/java/com/checkout/metadata/CardMetadataIT.java @@ -1,5 +1,6 @@ package com.checkout.metadata; +import com.checkout.TestDomainConfiguration; import com.checkout.CardSourceHelper; import com.checkout.CheckoutApi; import com.checkout.CheckoutApiImpl; @@ -160,10 +161,10 @@ void shouldRequestCardMetadataForTokenSync() { // ─── Helpers ──────────────────────────────────────────────────────────── private CheckoutApiImpl createStaticKeyApi() { - return CheckoutSdk.builder().staticKeys() + return TestDomainConfiguration.configureDomain(CheckoutSdk.builder().staticKeys() .publicKey(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY")) .secretKey(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY")) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); } diff --git a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java index b7507f148..1df56bed7 100644 --- a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java +++ b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java @@ -1,5 +1,6 @@ package com.checkout.payments; +import com.checkout.TestDomainConfiguration; import static com.checkout.TestHelper.createAddress; import static com.checkout.TestHelper.createPhone; import static com.checkout.TestHelper.getAccountHolder; @@ -934,12 +935,12 @@ private ProductRequest createTamaraProduct() { // API builders private CheckoutApi createPreviewApi() { - return CheckoutSdk.builder() + return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_PREVIEW_OAUTH_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_PREVIEW_OAUTH_CLIENT_SECRET"))) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); } From f8e36fe3f0cb2d182b4bfb6142f0419ea53a9b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:03:01 +0200 Subject: [PATCH 3/7] test: route the remaining OAuth integration tests through TestDomainConfiguration CI runs the full suite, so three OAuthTestIT cases that build their own client were still failing at construction. Local runs excluded integration tests, which is why they were missed. --- src/test/java/com/checkout/OAuthTestIT.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/checkout/OAuthTestIT.java b/src/test/java/com/checkout/OAuthTestIT.java index 9a07b5775..34cf538a9 100644 --- a/src/test/java/com/checkout/OAuthTestIT.java +++ b/src/test/java/com/checkout/OAuthTestIT.java @@ -113,13 +113,13 @@ void shouldFailInitAuthorization() { @Test void shouldInstantiateCheckoutApiWithOAuth_defaultAuthorizeUrl() { - final CheckoutApi checkoutApi = CheckoutSdk.builder() + final CheckoutApi checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET")) .scopes(OAuthScope.GATEWAY) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); assertNotNull(checkoutApi); @@ -130,14 +130,14 @@ void shouldInstantiateCheckoutApiWithOAuth_defaultAuthorizeUrl() { @Test void shouldInstantiateCheckoutApiWithOAuth_customAuthorizeUrl() throws URISyntaxException { - final CheckoutApi checkoutApi = CheckoutSdk.builder() + final CheckoutApi checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( new URI(OAUTH_AUTHORIZE_URL), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET")) .scopes(OAuthScope.GATEWAY) - .environment(Environment.SANDBOX) + .environment(Environment.SANDBOX)) .build(); assertNotNull(checkoutApi); @@ -148,7 +148,7 @@ void shouldInstantiateCheckoutApiWithOAuth_customAuthorizeUrl() throws URISyntax void shouldFailInitAuthorizationWithCustomEnvironment() { try { - CheckoutSdk.builder() + TestDomainConfiguration.configureDomain(CheckoutSdk.builder() .oAuth() .clientCredentials( System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), @@ -156,7 +156,7 @@ void shouldFailInitAuthorizationWithCustomEnvironment() { .scopes(OAuthScope.GATEWAY) .environment(CustomEnvironment.builder() .oAuthAuthorizationApi(create("https://the.oauth.uri/connect/token")) - .build()) + .build())) .build(); fail(); } catch (final Exception e) { From 066f883c3fc7eaa6f2fe29f341bfcee8add2b3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:15:17 +0200 Subject: [PATCH 4/7] test: stop passing a subdomain to the Previous platform builder Flagged in review, and fair: a bulk edit added the subdomain to the Previous (ABC) builder, the one platform that is exempt from needing one. That made the test misleading and, worse, removed the only coverage of the exemption actually working. It builds without a subdomain again. --- src/test/java/com/checkout/_external/CheckoutSdkTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/checkout/_external/CheckoutSdkTest.java b/src/test/java/com/checkout/_external/CheckoutSdkTest.java index 18c480d69..9f5bc18f8 100644 --- a/src/test/java/com/checkout/_external/CheckoutSdkTest.java +++ b/src/test/java/com/checkout/_external/CheckoutSdkTest.java @@ -37,11 +37,12 @@ class CheckoutSdkTest { @Test void shouldCreatePreviousSdk() { + // No subdomain here on purpose: the Previous (ABC) platform predates merchant-specific + // subdomains and is exempt, so this also covers that exemption. final CheckoutApi defaultCheckoutApi = CheckoutSdk.builder().previous().staticKeys() .publicKey(VALID_PREVIOUS_PK) .secretKey(VALID_PREVIOUS_SK) .environment(Environment.SANDBOX) - .environmentSubdomain("1234doma") .build(); assertNotNull(defaultCheckoutApi); From ea69ccaa3a621a6ee57cdd0db1cee178aaef00ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:27:40 +0200 Subject: [PATCH 5/7] test: add a switch to run the suite against the merchant subdomain The suite could only run against the shared hosts, so the subdomain path this PR makes mandatory had no integration coverage. Reviewers flagged that on every SDK, and it is the right thing to flag. The domain helper now has two modes. Default is unchanged, the shared hosts, because the sandbox OAuth clients are not provisioned for the subdomain and the token request returns invalid_client. Set CHECKOUT_TEST_USE_SUBDOMAIN=true and the suite runs against CHECKOUT_MERCHANT_SUBDOMAIN instead, so once sandbox is provisioned like production it is a one-line change in the workflows, already wired and documented, rather than a rewrite of every fixture. The switch is deliberately separate from CHECKOUT_MERCHANT_SUBDOMAIN, which CI already exports: provisioning should drive the behaviour, not the presence of a secret. --- .github/workflows/build-master.yml | 4 +++ .github/workflows/build-pull-request.yml | 4 +++ .github/workflows/build-release.yml | 4 +++ README.md | 12 +++++++++ .../com/checkout/TestDomainConfiguration.java | 27 ++++++++++++++----- 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index 8ef4a3d3b..c313f204e 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -31,4 +31,8 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} + # Flip to 'true' once the sandbox OAuth clients are provisioned for the + # merchant-specific subdomain, and the suite will run against it instead of + # the shared hosts. See TestDomainConfiguration. + CHECKOUT_TEST_USE_SUBDOMAIN: 'false' run: ./gradlew build test --fail-fast diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 097f0160c..6c90be3dd 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -39,6 +39,10 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} + # Flip to 'true' once the sandbox OAuth clients are provisioned for the + # merchant-specific subdomain, and the suite will run against it instead of + # the shared hosts. See TestDomainConfiguration. + CHECKOUT_TEST_USE_SUBDOMAIN: 'false' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: ./gradlew build diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index dbd1f42dd..f5f030987 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -36,6 +36,10 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} + # Flip to 'true' once the sandbox OAuth clients are provisioned for the + # merchant-specific subdomain, and the suite will run against it instead of + # the shared hosts. See TestDomainConfiguration. + CHECKOUT_TEST_USE_SUBDOMAIN: 'false' run: ./gradlew build test --fail-fast jar - id: publish env: diff --git a/README.md b/README.md index b3b17d46f..edfd344a9 100644 --- a/README.md +++ b/README.md @@ -677,6 +677,18 @@ final CheckoutApi checkoutApi = CheckoutSdk.builder() This routes requests to `api.checkout.com` (or `api.sandbox.checkout.com`) and `access.checkout.com` (or `access.sandbox.checkout.com`). The method is annotated `@Deprecated` and produces a compile-time warning. Exactly one of `environmentSubdomain(...)` or `useLegacyDomain()` must be set: the SDK throws a `CheckoutArgumentException` if both, or neither, are set. The Previous (ABC) platform predates merchant-specific subdomains and is exempt from this requirement. +## Running the tests against your subdomain + +The test suite builds every client through `TestDomainConfiguration`, which has two modes. By default it uses the shared hosts, because the sandbox OAuth clients are not provisioned for merchant-specific subdomains and the token request would come back `invalid_client`. To run against a subdomain instead: + +```bash +export CHECKOUT_MERCHANT_SUBDOMAIN="your_subdomain" +export CHECKOUT_TEST_USE_SUBDOMAIN=true +./gradlew test +``` + +The switch is separate from `CHECKOUT_MERCHANT_SUBDOMAIN` on purpose: CI already exports that secret, so provisioning is what should flip the behaviour, not the presence of a value. Once sandbox is provisioned like production, set `CHECKOUT_TEST_USE_SUBDOMAIN: 'true'` in the workflows and CI exercises the subdomain path end to end. + ## Code of Conduct Please refer to [Code of Conduct](CODE_OF_CONDUCT.md) diff --git a/src/test/java/com/checkout/TestDomainConfiguration.java b/src/test/java/com/checkout/TestDomainConfiguration.java index 9c2c569ea..5a1d36eb8 100644 --- a/src/test/java/com/checkout/TestDomainConfiguration.java +++ b/src/test/java/com/checkout/TestDomainConfiguration.java @@ -1,24 +1,37 @@ package com.checkout; /** - * Every client the suite builds has to choose a domain now that the merchant-specific - * subdomain is mandatory, so they all come through here. + * Every client the suite builds has to choose a domain now that the merchant-specific subdomain + * is mandatory, so they all come through here. There are deliberately two modes. * - *

The suite uses the shared hosts. It would be better to exercise the merchant-specific - * subdomain, since that is the path merchants are being moved to, but the sandbox OAuth clients - * are not provisioned for it: pointing the token request at + *

Default: the shared hosts. The sandbox OAuth clients are not provisioned for the + * merchant-specific subdomain, so pointing the token request at * {@code {subdomain}.access.sandbox.checkout.com} returns {@code invalid_client} for every - * integration test. Until those clients are bound to the subdomain, CI has to use the legacy - * hosts. + * integration test. + * + *

Opt-in: set {@code CHECKOUT_TEST_USE_SUBDOMAIN=true} and the suite runs against + * {@code CHECKOUT_MERCHANT_SUBDOMAIN} instead, exercising end to end the path merchants are being + * moved to. Once sandbox is provisioned like production, set that variable in the workflows and + * this becomes the mode CI runs in. The switch is deliberately separate from + * {@code CHECKOUT_MERCHANT_SUBDOMAIN}, which CI already exports, so provisioning drives the change + * rather than the presence of a secret. */ public final class TestDomainConfiguration { private TestDomainConfiguration() { } + public static boolean useSubdomain() { + return "true".equalsIgnoreCase(System.getenv("CHECKOUT_TEST_USE_SUBDOMAIN")); + } + @SuppressWarnings("deprecation") public static AbstractCheckoutSdkBuilder configureDomain( final AbstractCheckoutSdkBuilder builder) { + final String subdomain = System.getenv("CHECKOUT_MERCHANT_SUBDOMAIN"); + if (useSubdomain() && subdomain != null && !subdomain.trim().isEmpty()) { + return builder.environmentSubdomain(subdomain); + } return builder.useLegacyDomain(); } } From 60c9fe3483b2e41427d77aed30b1d9950ab6dc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:19:52 +0200 Subject: [PATCH 6/7] revert: leave the version bump to the release Versions are bumped on master during the release, not in a feature branch, per the release workflow. This branch should carry only the change itself; the major bump is classified and applied when the release is cut. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2802eb50e..cfdebb9dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.checkout -version=8.0.0 +version=7.15.0 project_name=Checkout SDK Java project_description=Checkout SDK for Java https://checkout.com From b136f1e29d544b7c7cefaa5ac703e40ef2408dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:52:04 +0200 Subject: [PATCH 7/7] revert: drop the test domain helpers and the workflow variable Two problems with the previous approach. It needed a new variable in 21 workflow files, which is not viable without access to create secrets. And it wrapped the builder chain in a configureDomain helper that is not part of the public API, so the tests stopped looking like the code a merchant would actually write. Every fixture now calls the real opt-out inline, in the chain, with a comment saying why: the sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so the token request comes back invalid_client. When sandbox is provisioned, those calls become the subdomain setter. The unit tests covering all four combinations are untouched: they already used the public API directly. --- .github/workflows/build-master.yml | 4 -- .github/workflows/build-pull-request.yml | 4 -- .github/workflows/build-release.yml | 4 -- README.md | 12 ------ src/test/java/com/checkout/OAuthTestIT.java | 22 ++++++++--- .../java/com/checkout/SandboxTestFixture.java | 21 ++++++++--- .../com/checkout/TestDomainConfiguration.java | 37 ------------------- .../accounts/AccountsPayoutSchedulesIT.java | 8 ++-- .../com/checkout/accounts/AccountsTestIT.java | 8 ++-- .../checkout/issuing/BaseIssuingTestIT.java | 8 ++-- .../com/checkout/metadata/CardMetadataIT.java | 8 ++-- .../payments/RequestApmPaymentsIT.java | 8 ++-- 12 files changed, 56 insertions(+), 88 deletions(-) delete mode 100644 src/test/java/com/checkout/TestDomainConfiguration.java diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index c313f204e..8ef4a3d3b 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -31,8 +31,4 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} - # Flip to 'true' once the sandbox OAuth clients are provisioned for the - # merchant-specific subdomain, and the suite will run against it instead of - # the shared hosts. See TestDomainConfiguration. - CHECKOUT_TEST_USE_SUBDOMAIN: 'false' run: ./gradlew build test --fail-fast diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 6c90be3dd..097f0160c 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -39,10 +39,6 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} - # Flip to 'true' once the sandbox OAuth clients are provisioned for the - # merchant-specific subdomain, and the suite will run against it instead of - # the shared hosts. See TestDomainConfiguration. - CHECKOUT_TEST_USE_SUBDOMAIN: 'false' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: ./gradlew build diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f5f030987..dbd1f42dd 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -36,10 +36,6 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID }} CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_SECRET }} CHECKOUT_MERCHANT_SUBDOMAIN: ${{ secrets.IT_CHECKOUT_MERCHANT_SUBDOMAIN }} - # Flip to 'true' once the sandbox OAuth clients are provisioned for the - # merchant-specific subdomain, and the suite will run against it instead of - # the shared hosts. See TestDomainConfiguration. - CHECKOUT_TEST_USE_SUBDOMAIN: 'false' run: ./gradlew build test --fail-fast jar - id: publish env: diff --git a/README.md b/README.md index edfd344a9..b3b17d46f 100644 --- a/README.md +++ b/README.md @@ -677,18 +677,6 @@ final CheckoutApi checkoutApi = CheckoutSdk.builder() This routes requests to `api.checkout.com` (or `api.sandbox.checkout.com`) and `access.checkout.com` (or `access.sandbox.checkout.com`). The method is annotated `@Deprecated` and produces a compile-time warning. Exactly one of `environmentSubdomain(...)` or `useLegacyDomain()` must be set: the SDK throws a `CheckoutArgumentException` if both, or neither, are set. The Previous (ABC) platform predates merchant-specific subdomains and is exempt from this requirement. -## Running the tests against your subdomain - -The test suite builds every client through `TestDomainConfiguration`, which has two modes. By default it uses the shared hosts, because the sandbox OAuth clients are not provisioned for merchant-specific subdomains and the token request would come back `invalid_client`. To run against a subdomain instead: - -```bash -export CHECKOUT_MERCHANT_SUBDOMAIN="your_subdomain" -export CHECKOUT_TEST_USE_SUBDOMAIN=true -./gradlew test -``` - -The switch is separate from `CHECKOUT_MERCHANT_SUBDOMAIN` on purpose: CI already exports that secret, so provisioning is what should flip the behaviour, not the presence of a value. Once sandbox is provisioned like production, set `CHECKOUT_TEST_USE_SUBDOMAIN: 'true'` in the workflows and CI exercises the subdomain path end to end. - ## Code of Conduct Please refer to [Code of Conduct](CODE_OF_CONDUCT.md) diff --git a/src/test/java/com/checkout/OAuthTestIT.java b/src/test/java/com/checkout/OAuthTestIT.java index 34cf538a9..b59267f70 100644 --- a/src/test/java/com/checkout/OAuthTestIT.java +++ b/src/test/java/com/checkout/OAuthTestIT.java @@ -113,13 +113,16 @@ void shouldFailInitAuthorization() { @Test void shouldInstantiateCheckoutApiWithOAuth_defaultAuthorizeUrl() { - final CheckoutApi checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + final CheckoutApi checkoutApi = CheckoutSdk.builder() .oAuth() .clientCredentials( System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET")) .scopes(OAuthScope.GATEWAY) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); assertNotNull(checkoutApi); @@ -130,14 +133,17 @@ void shouldInstantiateCheckoutApiWithOAuth_defaultAuthorizeUrl() { @Test void shouldInstantiateCheckoutApiWithOAuth_customAuthorizeUrl() throws URISyntaxException { - final CheckoutApi checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + final CheckoutApi checkoutApi = CheckoutSdk.builder() .oAuth() .clientCredentials( new URI(OAUTH_AUTHORIZE_URL), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET")) .scopes(OAuthScope.GATEWAY) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); assertNotNull(checkoutApi); @@ -148,7 +154,7 @@ void shouldInstantiateCheckoutApiWithOAuth_customAuthorizeUrl() throws URISyntax void shouldFailInitAuthorizationWithCustomEnvironment() { try { - TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + CheckoutSdk.builder() .oAuth() .clientCredentials( System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID"), @@ -156,7 +162,11 @@ void shouldFailInitAuthorizationWithCustomEnvironment() { .scopes(OAuthScope.GATEWAY) .environment(CustomEnvironment.builder() .oAuthAuthorizationApi(create("https://the.oauth.uri/connect/token")) - .build())) + .build()) + // The sandbox OAuth clients are not provisioned for the merchant-specific + // subdomain, so the token request would come back invalid_client. Opting out + // explicitly until they are. + .useLegacyDomain() .build(); fail(); } catch (final Exception e) { diff --git a/src/test/java/com/checkout/SandboxTestFixture.java b/src/test/java/com/checkout/SandboxTestFixture.java index 0703ae702..2a861844c 100644 --- a/src/test/java/com/checkout/SandboxTestFixture.java +++ b/src/test/java/com/checkout/SandboxTestFixture.java @@ -61,26 +61,32 @@ public SandboxTestFixture(final PlatformType platformType) { .setConnectionTimeToLive(60, TimeUnit.SECONDS) .evictIdleConnections(30, TimeUnit.SECONDS); - this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + this.checkoutApi = CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) .environment(Environment.SANDBOX) .executor(Executors.newFixedThreadPool(100)) - .httpClientBuilder(httpClientBuilder)) + .httpClientBuilder(httpClientBuilder) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); break; case DEFAULT: - this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + this.checkoutApi = CheckoutSdk.builder() .staticKeys() .publicKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY"))) .secretKey(requireNonNull(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY"))) .environment(Environment.SANDBOX) - .executor(CUSTOM_EXECUTOR)) + .executor(CUSTOM_EXECUTOR) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); break; case DEFAULT_OAUTH: - this.checkoutApi = TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + this.checkoutApi = CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_CLIENT_ID")), @@ -92,7 +98,10 @@ public SandboxTestFixture(final PlatformType platformType) { OAuthScope.VAULT_CARD_METADATA, OAuthScope.FINANCIAL_ACTIONS, OAuthScope.FORWARD, OAuthScope.FORWARD_SECRETS, OAuthScope.PAYMENTS_SEARCH) .environment(Environment.SANDBOX) - .executor(CUSTOM_EXECUTOR)) + .executor(CUSTOM_EXECUTOR) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); case CUSTOM: break; diff --git a/src/test/java/com/checkout/TestDomainConfiguration.java b/src/test/java/com/checkout/TestDomainConfiguration.java deleted file mode 100644 index 5a1d36eb8..000000000 --- a/src/test/java/com/checkout/TestDomainConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.checkout; - -/** - * Every client the suite builds has to choose a domain now that the merchant-specific subdomain - * is mandatory, so they all come through here. There are deliberately two modes. - * - *

Default: the shared hosts. The sandbox OAuth clients are not provisioned for the - * merchant-specific subdomain, so pointing the token request at - * {@code {subdomain}.access.sandbox.checkout.com} returns {@code invalid_client} for every - * integration test. - * - *

Opt-in: set {@code CHECKOUT_TEST_USE_SUBDOMAIN=true} and the suite runs against - * {@code CHECKOUT_MERCHANT_SUBDOMAIN} instead, exercising end to end the path merchants are being - * moved to. Once sandbox is provisioned like production, set that variable in the workflows and - * this becomes the mode CI runs in. The switch is deliberately separate from - * {@code CHECKOUT_MERCHANT_SUBDOMAIN}, which CI already exports, so provisioning drives the change - * rather than the presence of a secret. - */ -public final class TestDomainConfiguration { - - private TestDomainConfiguration() { - } - - public static boolean useSubdomain() { - return "true".equalsIgnoreCase(System.getenv("CHECKOUT_TEST_USE_SUBDOMAIN")); - } - - @SuppressWarnings("deprecation") - public static AbstractCheckoutSdkBuilder configureDomain( - final AbstractCheckoutSdkBuilder builder) { - final String subdomain = System.getenv("CHECKOUT_MERCHANT_SUBDOMAIN"); - if (useSubdomain() && subdomain != null && !subdomain.trim().isEmpty()) { - return builder.environmentSubdomain(subdomain); - } - return builder.useLegacyDomain(); - } -} diff --git a/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java b/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java index 6a736fb64..1fba7be9e 100644 --- a/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java +++ b/src/test/java/com/checkout/accounts/AccountsPayoutSchedulesIT.java @@ -1,6 +1,5 @@ package com.checkout.accounts; -import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -195,13 +194,16 @@ private void validateScheduleResponseBase(final GetScheduleResponse response) { } private CheckoutApi getPayoutSchedulesCheckoutApi() { - return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + return CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_PAYOUT_SCHEDULE_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_PAYOUT_SCHEDULE_CLIENT_SECRET"))) .scopes(OAuthScope.MARKETPLACE) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); } diff --git a/src/test/java/com/checkout/accounts/AccountsTestIT.java b/src/test/java/com/checkout/accounts/AccountsTestIT.java index 93ccbc757..8ddb8f1d2 100644 --- a/src/test/java/com/checkout/accounts/AccountsTestIT.java +++ b/src/test/java/com/checkout/accounts/AccountsTestIT.java @@ -1,6 +1,5 @@ package com.checkout.accounts; -import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -795,13 +794,16 @@ private IdResponse uploadFile() throws URISyntaxException { } private CheckoutApi getAccountsCheckoutApi() { - return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + return CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET"))) .scopes(OAuthScope.ACCOUNTS) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); } diff --git a/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java b/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java index c976978e4..892c0e04e 100644 --- a/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java +++ b/src/test/java/com/checkout/issuing/BaseIssuingTestIT.java @@ -1,6 +1,5 @@ package com.checkout.issuing; -import com.checkout.TestDomainConfiguration; import com.checkout.CheckoutApi; import com.checkout.CheckoutSdk; import com.checkout.Environment; @@ -35,7 +34,7 @@ public BaseIssuingTestIT() { } private CheckoutApi getIssuingCheckoutApi() { - return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + return CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_DEFAULT_OAUTH_ISSUING_CLIENT_ID")), @@ -43,7 +42,10 @@ private CheckoutApi getIssuingCheckoutApi() { .scopes(OAuthScope.VAULT, OAuthScope.ISSUING_CLIENT, OAuthScope.ISSUING_CARD_MGMT, OAuthScope.ISSUING_CONTROLS_READ, OAuthScope.ISSUING_CONTROLS_WRITE, OAuthScope.ISSUING_TRANSACTIONS_READ, OAuthScope.ISSUING_TRANSACTIONS_WRITE) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); } diff --git a/src/test/java/com/checkout/metadata/CardMetadataIT.java b/src/test/java/com/checkout/metadata/CardMetadataIT.java index 50762e1ac..4ce84a8f9 100644 --- a/src/test/java/com/checkout/metadata/CardMetadataIT.java +++ b/src/test/java/com/checkout/metadata/CardMetadataIT.java @@ -1,6 +1,5 @@ package com.checkout.metadata; -import com.checkout.TestDomainConfiguration; import com.checkout.CardSourceHelper; import com.checkout.CheckoutApi; import com.checkout.CheckoutApiImpl; @@ -161,10 +160,13 @@ void shouldRequestCardMetadataForTokenSync() { // ─── Helpers ──────────────────────────────────────────────────────────── private CheckoutApiImpl createStaticKeyApi() { - return TestDomainConfiguration.configureDomain(CheckoutSdk.builder().staticKeys() + return CheckoutSdk.builder().staticKeys() .publicKey(System.getenv("CHECKOUT_DEFAULT_PUBLIC_KEY")) .secretKey(System.getenv("CHECKOUT_DEFAULT_SECRET_KEY")) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); } diff --git a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java index 1df56bed7..60b2e58d5 100644 --- a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java +++ b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java @@ -1,6 +1,5 @@ package com.checkout.payments; -import com.checkout.TestDomainConfiguration; import static com.checkout.TestHelper.createAddress; import static com.checkout.TestHelper.createPhone; import static com.checkout.TestHelper.getAccountHolder; @@ -935,12 +934,15 @@ private ProductRequest createTamaraProduct() { // API builders private CheckoutApi createPreviewApi() { - return TestDomainConfiguration.configureDomain(CheckoutSdk.builder() + return CheckoutSdk.builder() .oAuth() .clientCredentials( requireNonNull(System.getenv("CHECKOUT_PREVIEW_OAUTH_CLIENT_ID")), requireNonNull(System.getenv("CHECKOUT_PREVIEW_OAUTH_CLIENT_SECRET"))) - .environment(Environment.SANDBOX)) + .environment(Environment.SANDBOX) + // The sandbox OAuth clients are not provisioned for the merchant-specific subdomain, so + // the token request would come back invalid_client. Opting out explicitly until they are. + .useLegacyDomain() .build(); }