> For the complete documentation index, see [llms.txt](https://docs.ecommerce.seb.ee/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ecommerce.seb.ee/integration/api-client-sdk/guides/overriding-sdk-adapters.md).

# Overriding SDK adapters

Replace SDK infrastructure adapters with native HTTP, logging, serialization, encryption, and webhook implementations.

Use this guide if you want to replace one of the SDK's built-in adapters — route HTTP through your own client, send SDK diagnostics to your logging framework, swap the JSON serializer, or stub a port in tests.

The [SDK architecture](/integration/api-client-sdk/guides/architecture-and-extensibility.md) page explains the ports-and-adapters design and lists the five ports and their default adapters. This page is the practical follow-up: which lever to pull, and the exact syntax per language.

## How overriding works

Every language offers the same three levers, in the same order of precedence:

1. **Hand the factory (or the `EgwClient` constructor) a ready-made instance.** An explicitly supplied adapter always wins — the factory only falls back to discovery for ports you left unset. This is the most direct and most deterministic path, and the right one for one-off replacements.
2. **Register a provider through the SDK's service provider interface (SPI) mechanism.** A *provider* is a small factory object that knows how to build your adapter; the SDK's bootstrap discovers it and uses it everywhere a client is created. The term SPI comes from the Java world, but the SDK applies the concept in all four languages — each SDK keeps its provider contracts in a dedicated `spi` namespace, discovered by whatever idiom is native to the platform. If you know the classic .NET provider model, Laravel service providers, or providers in Angular/NestJS dependency injection, this is the same idea. This is the right path when the override is stack-wide policy ("all EGW clients in this service log through Serilog") rather than a per-instance choice.
3. **Extend the client factory.** The factories follow the template-method pattern: one overridable `newXxx()` creation method per port (Java/PHP). Subclassing gives you full control of construction without touching the SPI at all.

The *mechanism* behind lever 2 is where the languages differ, each using the idiom native to its platform. Go straight to yours:

* [**Java**](#java) — `ServiceLoader` discovery, registered through a `META-INF/services` file
* [**PHP**](#php) — an interface-to-class map in a `providers.php` you hand to the factory
* [**C#**](#c) — assembly scan; declaring the class *is* the registration
* [**Node.js / TypeScript**](#nodejs) — an explicit `registerProvider()` call

One asymmetry to know about in Java and PHP: the factory exposes explicit setters for the API client, API server, and logger, but the serializer and data encrypter are always created through the SPI — to replace those two, use lever 2 or lever 3.

## Java

* **Discovery** — `java.util.ServiceLoader`
* **Registration** — a `META-INF/services/<provider FQN>` file, or a JPMS `provides` clause
* **If several providers match** — the first on the classpath wins

The bootstrap is `DefaultEgwClientFactory`, which resolves each port through `java.util.ServiceLoader` against the provider interfaces in `...sdk.application.bootstrap.spi`: `ApiClientProvider`, `ApiServerProvider`, `DataEncrypterProvider`, `SerializerProvider`, and `EgwLoggerProvider`.

**Lever 1 — factory setters.** `connectingWith(ApiClient)`, `listeningWith(ApiServer)`, and `loggingWith(EgwLogger)` accept ready instances:

```java
var client = new DefaultEgwClientFactory()
        .connectingTo(baseApiUri)
        .authorizingAs(System.getenv("EGW_CLIENT_ID"), System.getenv("EGW_CLIENT_SECRET"))
        .loggingWith(new Slf4jEgwLogger(LoggerFactory.getLogger("egw-sdk")))
        .createEgwClient();
```

**Lever 2 — ServiceLoader provider.** Implement the provider interface with a public no-arg constructor, then register it in `META-INF/services`. The logger is the cleanest SPI target — the SDK ships no `EgwLoggerProvider` registration of its own, so yours is picked up with no competition:

```java
public final class Slf4jEgwLoggerProvider implements EgwLoggerProvider {
    @Override
    public EgwLogger create() {
        return new Slf4jEgwLogger(LoggerFactory.getLogger("egw-sdk"));
    }
}
```

```
# src/main/resources/META-INF/services/com.tieto.banking.egw.merchantapi.sdk.application.bootstrap.spi.EgwLoggerProvider
com.example.shop.egw.Slf4jEgwLoggerProvider
```

For the four other ports the SDK registers its own providers in the core JAR, so yours is competing for first place on the classpath rather than replacing anything — an SPI registration is not a guaranteed override there. Prefer lever 1 or lever 3 for those.

**Lever 3 — factory subclass.** `DefaultEgwClientFactory` is open for extension; override the `newXxx()` method for the port you want to replace. This is the reliable way to swap the serializer or encrypter:

```java
public class ShopEgwClientFactory extends DefaultEgwClientFactory {
    @Override
    protected Serializer newSerializer() {
        return new ShopJsonbSerializer();
    }
}
```

## PHP

* **Discovery** — a providers config file
* **Registration** — an interface → class map in a `providers.php` you pass to the factory
* **If several providers match** — can't happen; the map holds one entry per port

The bootstrap reads an interface-to-class map from a plain PHP config file. The provider interfaces live in `...\Application\Bootstrap\Spi`: `ApiClientProviderInterface`, `ApiServerProviderInterface`, `DataEncrypterProviderInterface`, `SerializerProviderInterface`, `EgwLoggerProviderInterface`.

**Lever 1 — factory setters.** Same shape as Java (`connectingWith()`, `listeningWith()`, `loggingWith()`):

```php
$client = (new DefaultEgwClientFactory())
    ->connectingTo($baseApiUri)
    ->authorizingAs(getenv('EGW_CLIENT_ID'), getenv('EGW_CLIENT_SECRET'))
    ->loggingWith(new Psr3EgwLogger($psrLogger))
    ->createEgwClient();
```

**Lever 2 — your own providers file.** Copy the SDK's `config/providers.php`, swap the entries you want to change, and pass the file's path to the factory constructor. Don't edit the file inside `vendor/` — the default path resolves there, and Composer will overwrite it:

```php
<?php // config/egw-providers.php
// use statements for the SPI interfaces, bundled providers, and your providers omitted

return [
    ApiClientProviderInterface::class => GuzzleHttpClientProvider::class,
    ApiServerProviderInterface::class => ReactPhpHttpServerProvider::class,
    DataEncrypterProviderInterface::class => WebTokenDataEncrypterProvider::class,
    SerializerProviderInterface::class => ShopJsonSerializerProvider::class,   // your override
    EgwLoggerProviderInterface::class => Psr3EgwLoggerProvider::class,         // your override
];
```

```php
$factory = new DefaultEgwClientFactory(__DIR__ . '/config/egw-providers.php');
```

A provider is a class with a public no-arg constructor implementing the interface:

```php
final class ShopJsonSerializerProvider implements SerializerProviderInterface
{
    public function create(EgwLoggerInterface $logger): SerializerInterface
    {
        return new ShopJsonSerializer();
    }
}
```

**Lever 3 — factory subclass.** Extend `DefaultEgwClientFactory` (or `AbstractEgwClientFactory`) and override the relevant `newXxx()` method, exactly as in Java.

## C\#

* **Discovery** — an assembly scan (reflection over the loaded assemblies)
* **Registration** — none; just declare a public class implementing the provider interface
* **If several providers match** — the first in assembly-load order wins

The bootstrap scans the loaded assemblies of the current `AppDomain` for concrete classes implementing the provider interfaces in `Egw.Sdk.Application.Bootstrap.Spi`: `IApiClientProvider`, `IApiServerProvider`, `IDataEncrypterProvider`, `ISerializerProvider`, `IEgwLoggerProvider`. There is nothing to register — declaring the class is the registration. Providers are instantiated via reflection, so they need a public parameterless constructor.

The logger is again the cleanest SPI target, because the SDK ships no default `IEgwLoggerProvider`:

```csharp
public sealed class SerilogEgwLoggerProvider : IEgwLoggerProvider
{
    public IEgwLogger Create() => new SerilogEgwLogger(Log.Logger);
}
```

Two practical notes. The scan only sees assemblies already loaded into the AppDomain — if your provider lives in an assembly nothing has touched yet, force-load it (reference a type from it) before creating the client. And for ports where the SDK ships a default provider, yours is competing for first place in assembly-load order rather than replacing anything — not a guaranteed override.

The guaranteed override in C# is direct construction — `EgwClient`'s constructor takes all five ports:

```csharp
var apiClient = new HttpApiClient(myHttpClient, authentication, logger); // your own HttpClient instance
using var client = new EgwClient(
    baseUri, apiClient, apiServer: null,
    new JoseDataEncrypter(apiClient, jwkEndpoint, logger),
    new JsonSerializer(logger),
    logger);
```

Note the first line: `HttpApiClient` accepts an injected `HttpClient`, so you can apply your own handlers, timeouts, and proxy settings without implementing the port at all.

## Node.js

Applies to both JavaScript and TypeScript.

* **Discovery** — none; an explicit registry
* **Registration** — call `registerProvider({...})` before creating the client
* **If several providers match** — the last registration wins

There is no implicit discovery in the JS SDK — registration is an explicit call, matching the platform's module-oriented idiom. The registry holds one provider per port, and `registerProvider()` takes a partial object and merges it, so you override only what you name:

```ts
// Alpha note: deep import until registerProvider is exported from the package root — see below
import { registerProvider } from "@tieto/egw-sdk/dist/application/bootstrap/spi/ProviderRegistry.js";

registerProvider({
  loggerProvider: { create: () => new PinoEgwLogger(pino()) }
});

const client = new DefaultEgwClientFactory().create({
  baseUri: new URL("https://sandbox-api.example.com/merchant"),
  authentication: {
    tokenEndpoint: new URL(process.env.EGW_TOKEN_ENDPOINT!),
    clientId: process.env.EGW_CLIENT_ID!,
    clientSecret: process.env.EGW_CLIENT_SECRET!
  },
  jwkUri: new URL(process.env.EGW_JWK_URI!)
});
```

Providers are plain object literals with a `create()` method — closures, not classes — so passing configuration into your adapter needs no ceremony. Call `registerProvider()` before creating the factory; the SDK registers its defaults when the factory module loads, and your later registration replaces them per key.

In the current alpha, `registerProvider` is not yet re-exported from the package root, so it needs a deep import from the SDK's `ProviderRegistry` module — see [Known limitations in the Node.js quickstart](/integration/api-client-sdk/quickstarts/node.js-typescript.md).

Direct construction also works — `EgwClient`'s constructor takes all five ports, and `AxiosApiClient` accepts an injected `AxiosInstance` (third constructor parameter) for your interceptors, timeouts, and proxy configuration.

## Related pages

* [SDK architecture](/integration/api-client-sdk/guides/architecture-and-extensibility.md) — the ports-and-adapters design, the five ports, and their default adapters
* [Overview](/integration/api-client-sdk/overview.md) — language matrix, packages, alpha status
* [Error handling](/integration/api-client-sdk/guides/error-handling.md) — the `ECommException` hierarchy your adapters participate in
* [Security mechanics](/integration/api-client-sdk/guides/security-mechanics.md) — what an adapter must uphold (token handling, JWE, logging redaction)
* Per-language quickstarts: [Java](/integration/api-client-sdk/quickstarts/java.md) · [PHP](/integration/api-client-sdk/quickstarts/php.md) · [C#](/integration/api-client-sdk/quickstarts/c-.net.md) · [Node.js](/integration/api-client-sdk/quickstarts/node.js-typescript.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ecommerce.seb.ee/integration/api-client-sdk/guides/overriding-sdk-adapters.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
