If you need visibility into SDK calls, like request/response logging, latency metrics, trace spans, you can wire up a wrapper around RatepayClient that adds that behavior without modifying the SDK itself.
A logging/observability wrapper is the same pattern applied to production code instead of tests: wrap a real RatepayClient instance, delegate every call to it, and add your cross-cutting behavior around the delegated call.
RatepayClient declares many operations (authorize, capture, refund, getters, OTP, ...), and a Java class that implements RatepayClient must override all of them. The simplest Java approach is a java.lang.reflect.Proxy, which logs every call without listing each method. Kotlin can use by delegate and override only what it needs.
import com.ratepay.sdk.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
public final class LoggingRatepayClient {
private LoggingRatepayClient() {}
public static RatepayClient wrap(RatepayClient delegate) {
return (RatepayClient) Proxy.newProxyInstance(
RatepayClient.class.getClassLoader(),
new Class<?>[] {RatepayClient.class},
(proxy, method, args) -> {
long start = System.nanoTime();
try {
Object result = method.invoke(delegate, args);
log(method.getName(), "OK", start, null);
return result;
} catch (InvocationTargetException e) {
log(method.getName(), "EXCEPTION", start, e.getCause());
throw e.getCause();
}
});
}
private static void log(String operation, String result, long startNanos, Throwable error) {
long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000;
if (error != null) {
System.err.printf("ratepay.%s failed after %dms: %s%n", operation, elapsedMs, error);
} else {
System.out.printf("ratepay.%s -> %s (%dms)%n", operation, result, elapsedMs);
}
}
}Swap in a real logging framework (SLF4J) or a metrics client (Micrometer) in place of the System.out/System.err calls shown here.
Build the wrapper once, in the same place you build the underlying client, and use the wrapped instance everywhere downstream code would otherwise use RatepayClient directly:
RatepayClient client = LoggingRatepayClient.wrap(
RatepayClient.builder()
.clientId(System.getenv("RATEPAY_CLIENT_ID"))
.clientSecret(System.getenv("RATEPAY_CLIENT_SECRET"))
.environment(Environment.PRODUCTION)
.build()
);