Skip to content

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.

Wrapping the client for logging/metrics

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.

Using the wrapper

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()
);