Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ea2be6c
Add synchronous API methods and Resilience4j configuration support
armando-rodriguez-cko Dec 10, 2025
9f3a844
Update dependencies for Gson and SLF4J to latest versions
armando-rodriguez-cko Dec 10, 2025
3a904d9
wip
armando-rodriguez-cko Dec 12, 2025
ce9ca0e
updated git ignore, to avoid vscode folders
david-ruiz-cko Dec 19, 2025
1345ac6
PaymenyContextsClient sync methods + tests
david-ruiz-cko Dec 19, 2025
dd5657f
HostedPaymentsClient sync methods + test
david-ruiz-cko Dec 19, 2025
0ee8287
TokensClient sync methods + tests
david-ruiz-cko Dec 22, 2025
3b78208
TokensClient: Typo and test fixes
david-ruiz-cko Dec 22, 2025
65e8f9c
CustomerClient sync methods + tests
david-ruiz-cko Dec 22, 2025
2ada3c1
InstumentsClient sync methods + tests
david-ruiz-cko Dec 29, 2025
f20f446
RiskClient sync methods + tests
david-ruiz-cko Dec 29, 2025
8af34f8
RiskClient sync methods tests
david-ruiz-cko Dec 29, 2025
c41404d
SessionsClient sync methods + tests
david-ruiz-cko Dec 29, 2025
9427272
ForexClient sync methods + tests
david-ruiz-cko Dec 29, 2025
b9d8247
PaymentLinksClient sync methods + tests
david-ruiz-cko Dec 30, 2025
7839db4
ReportsClient sync methods + tests
david-ruiz-cko Dec 30, 2025
8e9f17d
BalancesClient sync methods + tests
david-ruiz-cko Dec 30, 2025
d82f93b
TransfersClient sync methods + tests
david-ruiz-cko Dec 30, 2025
054930c
FlowClient sync methods + tests
david-ruiz-cko Dec 31, 2025
01158cc
MetadataClient sync methods + tests
david-ruiz-cko Dec 31, 2025
98be302
ForwardClient sync methods + tests
david-ruiz-cko Dec 31, 2025
5367f04
FinancialClient sync methods + tests
david-ruiz-cko Dec 31, 2025
68b6a98
IdealClient sync methods + tests
david-ruiz-cko Dec 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.gradle/
.idea/
build/
bin/
lombok.config
.DS_Store
.vscode/
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ java {


dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.slf4j:slf4j-api:2.0.16'
implementation 'com.google.code.gson:gson:2.13.2'
implementation 'org.slf4j:slf4j-api:2.0.17'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'org.apache.commons:commons-lang3:3.19.0'
implementation 'javax.validation:validation-api:2.0.1.Final'
implementation 'org.apache.httpcomponents:httpmime:4.5.14'
implementation 'commons-codec:commons-codec:1.19.0'
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:1.7.1'
implementation 'io.github.resilience4j:resilience4j-retry:1.7.1'
implementation 'io.github.resilience4j:resilience4j-ratelimiter:1.7.1'

testImplementation(platform('org.junit:junit-bom:5.11.0'))
testImplementation('org.junit.jupiter:junit-jupiter')
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pluginManagement {
}

plugins {
id 'com.gradle.develocity' version '3.19.2'
id 'com.gradle.develocity' version '4.2.2'
}

develocity {
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public abstract class AbstractCheckoutSdkBuilder<T extends CheckoutApiClient> {
private Executor executor = ForkJoinPool.commonPool();
private TransportConfiguration transportConfiguration;
private Boolean recordTelemetry = true;
private Boolean synchronous = false;
private Resilience4jConfiguration resilience4jConfiguration;

public AbstractCheckoutSdkBuilder<T> environment(final IEnvironment environment) {
this.environment = environment;
Expand Down Expand Up @@ -55,6 +57,16 @@ public AbstractCheckoutSdkBuilder<T> recordTelemetry(final Boolean recordTelemet
return this;
}

public AbstractCheckoutSdkBuilder<T> synchronous(final Boolean synchronous) {
this.synchronous = synchronous;
return this;
}

public AbstractCheckoutSdkBuilder<T> resilience4jConfiguration(final Resilience4jConfiguration resilience4jConfiguration) {
this.resilience4jConfiguration = resilience4jConfiguration;
return this;
}

protected abstract SdkCredentials getSdkCredentials();

protected CheckoutConfiguration getCheckoutConfiguration() {
Expand All @@ -69,7 +81,7 @@ protected CheckoutConfiguration getCheckoutConfiguration() {
}

private CheckoutConfiguration buildCheckoutConfiguration(final SdkCredentials sdkCredentials) {
return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration, recordTelemetry);
return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration, recordTelemetry, synchronous, resilience4jConfiguration);
}

public abstract T build();
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/com/checkout/ApacheHttpClientTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import java.util.function.Supplier;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.retry.Retry;

import static com.checkout.ClientOperation.POST;
import static com.checkout.common.CheckoutUtils.ACCEPT_JSON;
Expand Down Expand Up @@ -137,6 +142,92 @@ public CompletableFuture<Response> submitFile(final String path, final SdkAuthor
}, executor);
}

@Override
public Response invokeSync(final ClientOperation clientOperation,
final String path,
final SdkAuthorization authorization,
final String requestBody,
final String idempotencyKey,
final Map<String, String> queryParams) {
final HttpUriRequest request;
switch (clientOperation) {
case GET:
case GET_CSV_CONTENT:
request = new HttpGet(getRequestUrl(path));
break;
case PUT:
request = new HttpPut(getRequestUrl(path));
break;
case POST:
request = new HttpPost(getRequestUrl(path));
break;
case DELETE:
request = new HttpDelete(getRequestUrl(path));
break;
case PATCH:
request = new HttpPatch(getRequestUrl(path));
break;
case QUERY:
final List<NameValuePair> params = queryParams.entrySet().stream()
.map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
try {
request = new HttpGet(new URIBuilder(getRequestUrl(path)).addParameters(params).build());
} catch (final URISyntaxException e) {
throw new CheckoutException(e);
}
break;
default:
throw new UnsupportedOperationException("Unsupported HTTP Method: " + clientOperation);
}
if (idempotencyKey != null) {
request.setHeader(CKO_IDEMPOTENCY_KEY, idempotencyKey);
}

final Supplier<Response> callSupplier = () -> performCall(authorization, requestBody, request, clientOperation);
return executeWithResilience4j(callSupplier);
}

@Override
public Response submitFileSync(final String path, final SdkAuthorization authorization, final AbstractFileRequest fileRequest) {
final HttpPost request = new HttpPost(getRequestUrl(path));
request.setEntity(getMultipartFileEntity(fileRequest));

final Supplier<Response> callSupplier = () -> performCall(authorization, null, request, POST);
return executeWithResilience4j(callSupplier);
}

/**
* Executes a supplier function with Resilience4j components (Circuit Breaker, Retry, Rate Limiter)
* if they are configured. Otherwise, executes the supplier directly.
*/
private Response executeWithResilience4j(final Supplier<Response> supplier) {
final Resilience4jConfiguration resilience4jConfig = configuration.getResilience4jConfiguration();

if (resilience4jConfig == null) {
return supplier.get();
}

Supplier<Response> decoratedSupplier = supplier;

// Apply Rate Limiter if configured
if (resilience4jConfig.hasRateLimiter()) {
decoratedSupplier = RateLimiter.decorateSupplier(resilience4jConfig.getRateLimiter(), decoratedSupplier);
}

// Apply Retry if configured
if (resilience4jConfig.hasRetry()) {
decoratedSupplier = Retry.decorateSupplier(resilience4jConfig.getRetry(), decoratedSupplier);
}

// Apply Circuit Breaker if configured (should be last to wrap everything)
if (resilience4jConfig.hasCircuitBreaker()) {
decoratedSupplier = CircuitBreaker.decorateSupplier(resilience4jConfig.getCircuitBreaker(), decoratedSupplier);
}

return decoratedSupplier.get();
}

private HttpEntity getMultipartFileEntity(final AbstractFileRequest abstractFileRequest) {
final MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
if (abstractFileRequest instanceof FileRequest) {
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/checkout/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,31 @@ public interface ApiClient {

<T extends HttpMetadata> CompletableFuture<T> submitFileAsync(String path, SdkAuthorization authorization, AbstractFileRequest request, Class<T> responseType);

// Synchronous methods
<T extends HttpMetadata> T get(String path, SdkAuthorization authorization, Class<T> responseType);

<T extends HttpMetadata> T get(String path, SdkAuthorization authorization, Type responseType);

<T extends HttpMetadata> T put(String path, SdkAuthorization authorization, Class<T> responseType, Object request);

<T extends HttpMetadata> T patch(String path, SdkAuthorization authorization, Class<T> responseType, Object request, String idempotencyKey);

<T extends HttpMetadata> T patch(String path, SdkAuthorization authorization, Type type, Object request, String idempotencyKey);

<T extends HttpMetadata> T post(String path, SdkAuthorization authorization, Class<T> responseType, Object request, String idempotencyKey);

<T extends HttpMetadata> T post(String path, SdkAuthorization authorization, Type responseType, Object request, String idempotencyKey);

EmptyResponse delete(String path, SdkAuthorization authorization);

<T extends HttpMetadata> T delete(String path, SdkAuthorization authorization, Class<T> responseType);

HttpMetadata post(String path, SdkAuthorization authorization, Map<Integer, Class<? extends HttpMetadata>> resultTypeMappings, Object request, String idempotencyKey);

<T extends HttpMetadata> T query(String path, SdkAuthorization authorization, Object filter, Class<T> responseType);

ContentResponse queryCsvContent(String path, SdkAuthorization authorization, Object filter, String targetFile);

<T extends HttpMetadata> T submitFile(String path, SdkAuthorization authorization, AbstractFileRequest request, Class<T> responseType);

}
Loading
Loading