From d2543c018f0ecd1036aada81012e20ca04652194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:30:03 +0100 Subject: [PATCH] Merchant-specific DNS names Support (#390) - Adds subdomain for merchants - Adds new field challenge_notification_url to the completion object - Fix payment context details response --- .github/workflows/build-master.yml | 1 + .github/workflows/build-pull-request.yml | 1 + .github/workflows/build-release.yml | 1 + README.md | 2 + .../com/checkout/AbstractCheckoutApmApi.java | 8 +++- .../checkout/AbstractCheckoutSdkBuilder.java | 15 ++++++- .../com/checkout/CheckoutConfiguration.java | 2 + .../DefaultCheckoutConfiguration.java | 22 ++++++++++ .../com/checkout/EnvironmentSubdomain.java | 44 +++++++++++++++++++ .../PaymentContextDetailsResponse.java | 3 -- .../completion/NonHostedCompletionInfo.java | 6 ++- .../com/checkout/CheckoutApiImplTest.java | 1 + .../CheckoutPreviousSdkBuilderTest.java | 23 ++++++++++ .../com/checkout/CheckoutSdkBuilderTest.java | 22 ++++++++++ .../DefaultCheckoutConfigurationTest.java | 21 ++++++++- .../payments/RequestApmPaymentsIT.java | 9 +--- .../previous/RequestApmPaymentsIT.java | 2 + 17 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/checkout/EnvironmentSubdomain.java diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index 91905cdc..ad78d731 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -30,4 +30,5 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET }} 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 }} run: ./gradlew build test --fail-fast diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index a77330a5..78501e6a 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -38,6 +38,7 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET }} 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 }} 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 5505b7f1..8bd2b5e8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -35,6 +35,7 @@ jobs: CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET: ${{ secrets.IT_CHECKOUT_DEFAULT_OAUTH_ACCOUNTS_CLIENT_SECRET }} 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 }} run: ./gradlew build test --fail-fast jar - id: publish env: diff --git a/README.md b/README.md index 33e5f294..13482fba 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,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 .executor() // optional for a custom Executor Service .build(); @@ -105,6 +106,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 .executor() // optional for a custom Executor Service .build(); diff --git a/src/main/java/com/checkout/AbstractCheckoutApmApi.java b/src/main/java/com/checkout/AbstractCheckoutApmApi.java index 1023bfb1..39091e64 100644 --- a/src/main/java/com/checkout/AbstractCheckoutApmApi.java +++ b/src/main/java/com/checkout/AbstractCheckoutApmApi.java @@ -37,6 +37,7 @@ public SepaClient sepaClient() { } private ApiClient getBaseApiClient(final CheckoutConfiguration configuration) { + return new ApiClientImpl(configuration, new BaseUriStrategy(configuration)); } @@ -50,7 +51,12 @@ private BaseUriStrategy(final CheckoutConfiguration configuration) { @Override public URI getUri() { - return configuration.getEnvironment().getCheckoutApi(); + + if (configuration.getEnvironmentSubdomain() != null) { + return configuration.getEnvironmentSubdomain().getCheckoutApi(); + }else { + return configuration.getEnvironment().getCheckoutApi(); + } } } diff --git a/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java b/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java index f5b4a66a..71399f40 100644 --- a/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java +++ b/src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java @@ -8,6 +8,7 @@ public abstract class AbstractCheckoutSdkBuilder { protected HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); private IEnvironment environment; + private EnvironmentSubdomain environmentSubdomain; private Executor executor = ForkJoinPool.commonPool(); private TransportConfiguration transportConfiguration; @@ -16,6 +17,14 @@ public AbstractCheckoutSdkBuilder environment(final IEnvironment environment) return this; } + public AbstractCheckoutSdkBuilder environmentSubdomain(final String subdomain) { + if (subdomain == null) { + throw new CheckoutArgumentException("subdomain must be specified"); + } + this.environmentSubdomain = new EnvironmentSubdomain(this.environment, subdomain); + return this; + } + public AbstractCheckoutSdkBuilder httpClientBuilder(final HttpClientBuilder httpClientBuilder) { this.httpClientBuilder = httpClientBuilder; return this; @@ -35,6 +44,10 @@ protected IEnvironment getEnvironment() { return environment; } + protected EnvironmentSubdomain getEnvironmentSubdomain() { + return environmentSubdomain; + } + protected abstract SdkCredentials getSdkCredentials(); protected CheckoutConfiguration getCheckoutConfiguration() { @@ -49,7 +62,7 @@ protected CheckoutConfiguration getCheckoutConfiguration() { } private CheckoutConfiguration buildCheckoutConfiguration(final SdkCredentials sdkCredentials) { - return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), httpClientBuilder, executor, transportConfiguration); + return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration); } public abstract T build(); diff --git a/src/main/java/com/checkout/CheckoutConfiguration.java b/src/main/java/com/checkout/CheckoutConfiguration.java index 96b7260f..8df00873 100644 --- a/src/main/java/com/checkout/CheckoutConfiguration.java +++ b/src/main/java/com/checkout/CheckoutConfiguration.java @@ -8,6 +8,8 @@ public interface CheckoutConfiguration { IEnvironment getEnvironment(); + EnvironmentSubdomain getEnvironmentSubdomain(); + SdkCredentials getSdkCredentials(); HttpClientBuilder getHttpClientBuilder(); diff --git a/src/main/java/com/checkout/DefaultCheckoutConfiguration.java b/src/main/java/com/checkout/DefaultCheckoutConfiguration.java index 86224743..ffe2b2cf 100644 --- a/src/main/java/com/checkout/DefaultCheckoutConfiguration.java +++ b/src/main/java/com/checkout/DefaultCheckoutConfiguration.java @@ -12,6 +12,7 @@ class DefaultCheckoutConfiguration implements CheckoutConfiguration { private final HttpClientBuilder httpClientBuilder; private final Executor executor; private final IEnvironment environment; + private final EnvironmentSubdomain environmentSubdomain; private final TransportConfiguration transportConfiguration; DefaultCheckoutConfiguration(final SdkCredentials sdkCredentials, @@ -24,6 +25,22 @@ class DefaultCheckoutConfiguration implements CheckoutConfiguration { this.httpClientBuilder = httpClientBuilder; this.executor = executor; this.environment = environment; + this.environmentSubdomain = null; + this.transportConfiguration = transportConfiguration; + } + + DefaultCheckoutConfiguration(final SdkCredentials sdkCredentials, + final IEnvironment environment, + final EnvironmentSubdomain environmentSubdomain, + final HttpClientBuilder httpClientBuilder, + final Executor executor, + final TransportConfiguration transportConfiguration) { + validateParams("sdkCredentials", sdkCredentials, "environment", environment, "httpClientBuilder", httpClientBuilder, "executor", executor, "transportConfiguration", transportConfiguration); + this.sdkCredentials = sdkCredentials; + this.httpClientBuilder = httpClientBuilder; + this.executor = executor; + this.environment = environment; + this.environmentSubdomain = environmentSubdomain; this.transportConfiguration = transportConfiguration; } @@ -47,6 +64,11 @@ public IEnvironment getEnvironment() { return environment; } + @Override + public EnvironmentSubdomain getEnvironmentSubdomain() { + return environmentSubdomain; + } + @Override public TransportConfiguration getTransportConfiguration() { return transportConfiguration; diff --git a/src/main/java/com/checkout/EnvironmentSubdomain.java b/src/main/java/com/checkout/EnvironmentSubdomain.java new file mode 100644 index 00000000..49b552b0 --- /dev/null +++ b/src/main/java/com/checkout/EnvironmentSubdomain.java @@ -0,0 +1,44 @@ +package com.checkout; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +public class EnvironmentSubdomain { + + private URI checkoutApi; + + public EnvironmentSubdomain(IEnvironment environment, String subdomain) { + checkoutApi = addSubdomainToApiUrlEnvironment(environment, subdomain); + } + + public URI getCheckoutApi() { + return checkoutApi; + } + + private static URI addSubdomainToApiUrlEnvironment(IEnvironment environment, String subdomain) { + URI apiUrl = environment.getCheckoutApi(); + URI newEnvironment = null; + try { + newEnvironment = new URI(apiUrl.toString()); + } catch (final URISyntaxException e) { + throw new CheckoutException(e); + } + Pattern pattern = Pattern.compile("^[0-9a-z]{8}$"); + Matcher matcher = pattern.matcher(subdomain); + if (matcher.matches()) { + String host = apiUrl.getHost(); + String scheme = apiUrl.getScheme(); + int port = apiUrl.getPort(); + String newHost = subdomain + "." + host; + try { + newEnvironment = new URI(scheme, null, newHost, port, apiUrl.getPath(), apiUrl.getQuery(), apiUrl.getFragment()); + } catch (final URISyntaxException e) { + throw new CheckoutException(e); + } + } + return newEnvironment; + } + +} diff --git a/src/main/java/com/checkout/payments/contexts/PaymentContextDetailsResponse.java b/src/main/java/com/checkout/payments/contexts/PaymentContextDetailsResponse.java index bb034221..ea2cb25e 100644 --- a/src/main/java/com/checkout/payments/contexts/PaymentContextDetailsResponse.java +++ b/src/main/java/com/checkout/payments/contexts/PaymentContextDetailsResponse.java @@ -18,7 +18,4 @@ public final class PaymentContextDetailsResponse extends HttpMetadata { @SerializedName("partner_metadata") private PaymentContextsPartnerMetadata partnerMetadata; - - private Object customer; - } diff --git a/src/main/java/com/checkout/sessions/completion/NonHostedCompletionInfo.java b/src/main/java/com/checkout/sessions/completion/NonHostedCompletionInfo.java index aafb51e7..8d635759 100644 --- a/src/main/java/com/checkout/sessions/completion/NonHostedCompletionInfo.java +++ b/src/main/java/com/checkout/sessions/completion/NonHostedCompletionInfo.java @@ -16,10 +16,14 @@ public final class NonHostedCompletionInfo extends CompletionInfo { @SerializedName("callback_url") private final String callbackUrl; + @SerializedName("challenge_notification_url") + private final String challengeNotificationUrl; + @Builder - private NonHostedCompletionInfo(final String callbackUrl) { + private NonHostedCompletionInfo(final String callbackUrl, final String challengeNotificationUrl) { super(CompletionInfoType.NON_HOSTED); this.callbackUrl = callbackUrl; + this.challengeNotificationUrl = challengeNotificationUrl; } } diff --git a/src/test/java/com/checkout/CheckoutApiImplTest.java b/src/test/java/com/checkout/CheckoutApiImplTest.java index 6029756e..fdedc0ad 100644 --- a/src/test/java/com/checkout/CheckoutApiImplTest.java +++ b/src/test/java/com/checkout/CheckoutApiImplTest.java @@ -18,6 +18,7 @@ class CheckoutApiImplTest { void shouldInstantiateAndRetrieveClients() { final CheckoutConfiguration configuration = mock(CheckoutConfiguration.class); when(configuration.getEnvironment()).thenReturn(Environment.SANDBOX); + when(configuration.getEnvironmentSubdomain()).thenReturn(new EnvironmentSubdomain(Environment.SANDBOX, "subdomain")); when(configuration.getHttpClientBuilder()).thenReturn(HttpClientBuilder.create()); when(configuration.getExecutor()).thenReturn(mock(Executor.class)); final CheckoutApi checkoutApi = new CheckoutApiImpl(configuration); diff --git a/src/test/java/com/checkout/CheckoutPreviousSdkBuilderTest.java b/src/test/java/com/checkout/CheckoutPreviousSdkBuilderTest.java index cac3da7c..14fa5e6f 100644 --- a/src/test/java/com/checkout/CheckoutPreviousSdkBuilderTest.java +++ b/src/test/java/com/checkout/CheckoutPreviousSdkBuilderTest.java @@ -34,6 +34,29 @@ void shouldCreateCheckoutSdks() { } + @Test + void shouldCreateCheckoutWithSubdomainSdks() { + + final CheckoutApi checkoutApi1 = new CheckoutPreviousSdkBuilder() + .staticKeys() + .publicKey(VALID_PREVIOUS_PK) + .secretKey(VALID_PREVIOUS_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("123dmain") + .build(); + + assertNotNull(checkoutApi1.tokensClient()); + + final CheckoutApi checkoutApi2 = new CheckoutPreviousSdkBuilder().staticKeys() + .secretKey(VALID_PREVIOUS_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("123dmain") + .build(); + + assertNotNull(checkoutApi2.tokensClient()); + + } + @Test void shouldFailToCreateCheckoutSdks() { diff --git a/src/test/java/com/checkout/CheckoutSdkBuilderTest.java b/src/test/java/com/checkout/CheckoutSdkBuilderTest.java index 528b655e..fa3b1251 100644 --- a/src/test/java/com/checkout/CheckoutSdkBuilderTest.java +++ b/src/test/java/com/checkout/CheckoutSdkBuilderTest.java @@ -36,6 +36,28 @@ void shouldCreateStaticKeysCheckoutSdks() { } + @Test + void shouldCreateStaticKeysCheckoutWithSubdomainSdks() { + + final CheckoutApi checkoutApi1 = new CheckoutSdkBuilder().staticKeys() + .publicKey(VALID_DEFAULT_PK) + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("123dmain") + .build(); + + assertNotNull(checkoutApi1); + + final CheckoutApi checkoutApi2 = new CheckoutSdkBuilder().staticKeys() + .secretKey(VALID_DEFAULT_SK) + .environment(Environment.SANDBOX) + .environmentSubdomain("123dmain") + .build(); + + assertNotNull(checkoutApi2); + + } + @Test void shouldCreateCheckoutAndInitOAuthSdk() throws URISyntaxException { diff --git a/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java b/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java index cb1bdffb..83b0fd0d 100644 --- a/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java +++ b/src/test/java/com/checkout/DefaultCheckoutConfigurationTest.java @@ -44,6 +44,26 @@ void shouldCreateConfiguration() { } + @Test + void shouldCreateConfigurationWithSubdomain() { + + final StaticKeysSdkCredentials credentials = Mockito.mock(StaticKeysSdkCredentials.class); + final EnvironmentSubdomain environmentSubdomain = new EnvironmentSubdomain(Environment.SANDBOX, "123dmain"); + + final CheckoutConfiguration configuration = new DefaultCheckoutConfiguration(credentials, Environment.SANDBOX, environmentSubdomain, DEFAULT_CLIENT_BUILDER, DEFAULT_EXECUTOR, DEFAULT_TRANSPORT_CONFIGURATION); + assertEquals("https://123dmain.api.sandbox.checkout.com/", configuration.getEnvironmentSubdomain().getCheckoutApi().toString()); + } + + @Test + void shouldCreateConfigurationWithBadSubdomain() { + + final StaticKeysSdkCredentials credentials = Mockito.mock(StaticKeysSdkCredentials.class); + final EnvironmentSubdomain environmentSubdomain = new EnvironmentSubdomain(Environment.SANDBOX, "123bad"); + + final CheckoutConfiguration configuration = new DefaultCheckoutConfiguration(credentials, Environment.SANDBOX, environmentSubdomain, DEFAULT_CLIENT_BUILDER, DEFAULT_EXECUTOR, DEFAULT_TRANSPORT_CONFIGURATION); + assertEquals("https://api.sandbox.checkout.com/", configuration.getEnvironmentSubdomain().getCheckoutApi().toString()); + } + @Test void shouldCreateConfiguration_defaultHttpClientBuilderAndExecutor() { @@ -81,7 +101,6 @@ void shouldCreateConfigurationForProd() { final CheckoutConfiguration configuration = new DefaultCheckoutConfiguration(credentials, Environment.PRODUCTION, DEFAULT_CLIENT_BUILDER, DEFAULT_EXECUTOR, DEFAULT_TRANSPORT_CONFIGURATION); assertEquals(Environment.PRODUCTION, configuration.getEnvironment()); - } @Test diff --git a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java index e8298364..1cbf9f39 100644 --- a/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java +++ b/src/test/java/com/checkout/payments/RequestApmPaymentsIT.java @@ -388,14 +388,7 @@ void shouldMakeBancontactPayment() { .failureUrl("https://testing.checkout.com/failure") .build(); - //checkErrorItem(() -> paymentsClient.requestPayment(paymentRequest), PAYEE_NOT_ONBOARDED); - final com.checkout.payments.response.PaymentResponse paymentResponse = blocking(() -> paymentsClient.requestPayment(paymentRequest)); - assertNotNull(paymentResponse); - - final com.checkout.payments.response.GetPaymentResponse paymentDetails = blocking(() -> paymentsClient.getPayment(paymentResponse.getId())); - assertNotNull(paymentDetails); - assertTrue(paymentDetails.getSource() instanceof com.checkout.payments.response.source.AlternativePaymentSourceResponse); - assertEquals(PaymentSourceType.BANCONTACT, paymentDetails.getSource().getType()); + checkErrorItem(() -> paymentsClient.requestPayment(paymentRequest), APM_SERVICE_UNAVAILABLE); } @Test diff --git a/src/test/java/com/checkout/payments/previous/RequestApmPaymentsIT.java b/src/test/java/com/checkout/payments/previous/RequestApmPaymentsIT.java index 15d40580..adf9c5f8 100644 --- a/src/test/java/com/checkout/payments/previous/RequestApmPaymentsIT.java +++ b/src/test/java/com/checkout/payments/previous/RequestApmPaymentsIT.java @@ -297,6 +297,7 @@ void shouldMakeKnetPayment() { assertEquals(PaymentSourceType.KNET, paymentDetails.getSource().getType()); } + @Disabled("not available") @Test void shouldMakePrzelewy24Payment() { final PaymentRequest paymentRequest = PaymentRequest.builder() @@ -358,6 +359,7 @@ void shouldMakePoliPayment() { assertEquals(PaymentSourceType.POLI, paymentDetails.getSource().getType()); } + @Disabled("not available") @Test void shouldMakeBancontactPayment() { final PaymentRequest paymentRequest = PaymentRequest.builder()