> 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/architecture-and-extensibility.md).

# Architecture & extensibility

Understand the SDK's ports-and-adapters architecture and integrate custom infrastructure adapters.

The API Client SDK is built as a **hexagonal (ports & adapters) architecture**. Every piece of infrastructure the SDK depends on — the HTTP client, the webhook listener, the JWE encrypter, the JSON serializer, the logger — sits behind a small interface (a *port*) owned by the SDK's application core. The bundled implementations (*adapters*) are defaults, not requirements: you can replace any of them with your own.

This page explains how the SDK is shaped, why it is shaped that way, and what that shape lets you do. For the syntax of actually replacing an adapter in each language, see [Overriding SDK adapters](/integration/api-client-sdk/guides/overriding-sdk-adapters.md).

## The shape of the SDK

<figure><img src="https://1157570179-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuBDtfypkI85zidcVwaDj%2Fuploads%2Fo0GePge2xT9Uws9IktvT%2Fegw-sdk-architecture-diagram.svg?alt=media&#x26;token=eb2cfd82-863c-4f86-922a-a0cb1e8aa5d7" alt="EGW SDK - ports &#x26; adapters architecture"><figcaption><p>EGW SDK - ports &#x26; adapters architecture</p></figcaption></figure>

Three layers, one direction of dependency:

* **Application core** — the `EgwClient` facade, one API class per domain, and the request and response DTOs. Depends only on the ports.
* **Ports** — five small interfaces the core calls out through. They are the SDK's only contract with infrastructure.
* **Adapters** — concrete implementations of the ports using mainstream libraries. The **bootstrap** layer (a client factory plus a service-provider mechanism) decides at construction time which adapter fills each port.

## The five ports

| Port           | Purpose                                      |
| -------------- | -------------------------------------------- |
| API Client     | Outbound HTTP calls, OAuth token handling    |
| API Server     | Inbound webhook listener                     |
| Data Encrypter | JWE encryption of card data                  |
| Serializer     | JSON (de)serialization of DTOs               |
| Logger         | Diagnostic logging (optional; no-op default) |

The ports are deliberately narrow — the API Client port is two methods (`perform`, `performWithoutAuth`), the encrypter is one (`encrypt`). Implementing one is an afternoon's work, not a project.

Each port ships with a default adapter built on a mainstream library for that platform. Open your language for the interface names and the adapter that fills each port:

<details>

<summary>Java</summary>

| Port           | Interface       | Default adapter                         |
| -------------- | --------------- | --------------------------------------- |
| API Client     | `ApiClient`     | `JavaNetHttpClient` (`java.net.http`)   |
| API Server     | `ApiServer`     | `ComSunNetHttpServer`                   |
| Data Encrypter | `DataEncrypter` | `NimbusDataEncrypter` (Nimbus JOSE+JWT) |
| Serializer     | `Serializer`    | `JacksonSerializer` (Jackson)           |
| Logger         | `EgwLogger`     | `NoOpEgwLogger`                         |

</details>

<details>

<summary>PHP</summary>

| Port           | Interface                | Default adapter                                   |
| -------------- | ------------------------ | ------------------------------------------------- |
| API Client     | `ApiClientInterface`     | `GuzzleHttpClient` (Guzzle 7)                     |
| API Server     | `ApiServerInterface`     | `ReactPhpHttpServer` (ReactPHP)                   |
| Data Encrypter | `DataEncrypterInterface` | `WebTokenDataEncrypter` (`web-token/jwt-library`) |
| Serializer     | `SerializerInterface`    | `NativeJsonSerializer` (native `json_*`)          |
| Logger         | `EgwLoggerInterface`     | `NoOpEgwLogger`                                   |

</details>

<details>

<summary>C#</summary>

| Port           | Interface        | Default adapter                       |
| -------------- | ---------------- | ------------------------------------- |
| API Client     | `IApiClient`     | `HttpApiClient` (`HttpClient`)        |
| API Server     | `IApiServer`     | `HttpListenerApiServer`               |
| Data Encrypter | `IDataEncrypter` | `JoseDataEncrypter` (`jose-jwt`)      |
| Serializer     | `ISerializer`    | `JsonSerializer` (`System.Text.Json`) |
| Logger         | `IEgwLogger`     | `NoOpEgwLogger`                       |

</details>

<details>

<summary>Node.js / TypeScript</summary>

| Port           | Interface       | Default adapter                         |
| -------------- | --------------- | --------------------------------------- |
| API Client     | `ApiClient`     | `AxiosApiClient` (axios)                |
| API Server     | `ApiServer`     | `HttpServerApiServer` (`node:http`)     |
| Data Encrypter | `DataEncrypter` | `JoseDataEncrypter` (`jose`)            |
| Serializer     | `Serializer`    | `JsonSerializer` (`JSON` + DTO mapping) |
| Logger         | `EgwLogger`     | `NoopEgwLogger`                         |

</details>

## Why this design

A payment SDK lives inside *your* stack, not the other way around. Your backend already has an HTTP client with tuned timeouts, proxies, and interceptors; a logging framework with correlation IDs; a test harness that must not open real sockets. A monolithic SDK that hard-wires its own choices forces you to run two of everything.

The hexagonal design inverts that. The application core — the `EgwClient` facade, the domain API classes (`payments()`, `refunds()`, `tokens()`, the session APIs, `notifications()`), and the generated DTOs — contains the payment logic and knows nothing about Guzzle, Jackson, or axios. All third-party dependencies are confined to the adapter layer. What this gives you in practice:

* **Native stack integration.** Route the SDK's HTTP traffic through your own client, so your existing timeout, proxy, TLS, and instrumentation policy applies to payment calls too.
* **Observability on your terms.** Plug your logging framework (SLF4J, PSR-3, Serilog, pino) into the SDK's logger port. The default is a no-op logger — the SDK is silent until you give it a destination.
* **Testability.** Every port can be stubbed. You can drive the full facade and generated DTOs in a unit test with a fake API client and a pass-through encrypter, without a network or a mock server.
* **No library lock-in.** If a bundled dependency conflicts with your platform (a Jackson version clash, a Guzzle major upgrade), you implement the port with your preferred library and drop the bundled adapter — the core is unaffected.

## Cross-language consistency

All four SDKs cover the same API operations and follow this same architecture, with the same port set, the same factory concepts, and mirrored test suites. Only the naming convention and the SPI idiom change per language. If you integrate the SDK in two stacks — or migrate from one to another — the mental model transfers wholesale; only the override syntax differs.

## Replacing an adapter

Every language lets you supply your own implementation of any port, through three levers: hand the factory a ready-made instance, register a provider with the SDK's service provider interface (SPI) mechanism, or extend the client factory. For the precedence rules and per-language examples, see [Overriding SDK adapters](/integration/api-client-sdk/guides/overriding-sdk-adapters.md).

## Related pages

* [Overriding SDK adapters](/integration/api-client-sdk/guides/overriding-sdk-adapters.md) — how to replace a built-in adapter, per language
* [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/architecture-and-extensibility.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.
