> 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/using-the-sdk/java.md).

# Java

Build Java payment integrations with clients, request DTOs, API calls, error handling, and webhooks.

Use this guide if you have completed the [Java quickstart](/integration/api-client-sdk/quickstarts/java.md) and want to build a full integration: create the client, compose request objects, call the API, handle failures, and receive a **Webhook (also referred to as callback notifications)**. Each step builds on the previous one. A [complete example](#complete-example) brings them together.

## Step 1 — Create the client

Everything starts with `DefaultEgwClientFactory`, a fluent factory that assembles an `EgwClient` from your connection settings. Each method returns the factory, so you chain the configuration and finish with `createEgwClient()`:

```java
import com.tieto.banking.egw.merchantapi.sdk.application.EgwClient;
import com.tieto.banking.egw.merchantapi.sdk.application.bootstrap.DefaultEgwClientFactory;
import java.net.URI;

EgwClient client = new DefaultEgwClientFactory()
        .connectingTo(URI.create("https://sandbox.api.ecommerce.sebgroup.com"))
        .authorizingAs(System.getenv("EGW_CLIENT_ID"), System.getenv("EGW_CLIENT_SECRET"))
        .createEgwClient();
```

The factory pattern exists so that the client's infrastructure stays replaceable without changing your application code: `connectingWith(ApiClient)` swaps the HTTP client (proxy, TLS, timeouts), `loggingWith(EgwLogger)` plugs in diagnostics, and `listeningAt(URI)` enables the Webhook listener used in [Step 5](#step-5--listen-for-webhooks). Configure and build from a single thread — the factory is not thread-safe — but the resulting `EgwClient` is immutable and safe to share across your whole application. Create it once at startup and reuse it; its OAuth token cache is thread-safe and refreshes automatically.

The factory derives the OAuth token endpoint and the card-encryption JWK endpoint from the base URL. If your deployment serves either from a different host, override just that one with `.tokenEndpoint(URI)` or `.jwkEndpoint(URI)` anywhere in the chain — whichever you leave unset is still derived.

`EgwClient` is `AutoCloseable`. `close()` stops the webhook listener; if you enabled one, keep the client open for as long as you want to receive notifications, and prefer try-with-resources or a shutdown hook so the listener is released cleanly.

## Step 2 — Compose request DTOs

Requests and responses are plain data transfer objects (DTOs) in `...sdk.application.dto`: `InitiateCitPaymentRequest`, `Amount`, `Basket`, and so on. Responses expose standard getters (`response.getPaymentId()`). Requests are composed through builders, and the builders support three complementary patterns.

**Classic builder.** Every DTO has a static `builder()` method; setters are named after the field and return the builder, and `build()` produces the DTO:

```java
Amount amount = Amount.builder()
        .amount("100.00")
        .currency("EUR")
        .build();
```

**Nested lambda builder.** For every property that is itself a DTO, the builder also accepts a `UnaryOperator` over the nested builder. Instead of building `Amount` separately, describe it inline — the lambda receives a fresh nested builder and must return it:

```java
InitiateCitPaymentRequest request = InitiateCitPaymentRequest.builder()
        .channel(CitPaymentChannel.BROWSER)
        .instructedAmount(a -> a.amount("100.00").currency("EUR"))
        .orderNumber("ORD-1001")
        .participantId(System.getenv("EGW_PARTICIPANT_ID"))
        .shopAgreementId(System.getenv("EGW_SHOP_AGREEMENT_ID"))
        .shopRedirectUrl("https://shop.example.com/return")
        .build();
```

Both styles produce identical objects; the lambda form reads best for deeply nested requests, the classic form when you reuse a DTO across requests. Scalar and enum properties (`orderNumber`, `channel`) have only the classic form.

**Singular item adders.** List properties get an additional `<property>Item(...)` method that appends one element, so you can add items in a loop without managing the list yourself:

```java
Basket basket = Basket.builder()
        .itemsItem(BasketItemProduct.builder()
                .name("Fishing rod")
                .description("Carbon fiber rod, 2.7 m")
                .price(Amount.builder().amount("100.00").currency("EUR").build())
                .quantity(1)
                .build())
        .subtotalAmount(a -> a.amount("100.00").currency("EUR"))
        .build();
```

Item adders take a fully built element — for polymorphic lists like basket items, pick the concrete type (`BasketItemProduct`, `BasketItemShipping`, `BasketItemDiscount`, `BasketItemTax`).

**Validation.** `build()` checks required fields and throws `InvalidRequestException` immediately — a missing `orderNumber` fails at build time in your code, not as an HTTP 400 round-trip. This is why you should always construct requests through builders: the DTOs also have public no-argument constructors and setters, but that path skips validation entirely.

## Step 3 — Call the API

`EgwClient` groups operations into domain accessors: `payments()`, `refunds()`, `tokens()`, `shops()`, plus the checkout-session groups `cardSessions()`, `bankSessions()`, `tokenSessions()`, `clickToPaySessions()`, and `notifications()` for webhooks. Each accessor returns a lightweight API object — call it inline rather than caching it.

Every operation that takes a request body accepts either a built DTO or a builder lambda directly, so short calls need no separate request variable:

```java
// With a prepared DTO:
InitiatePaymentResponse created = client.payments().initiateCitPayment(request);

// Or with the builder lambda inline:
CancelPaymentResponse cancelled = client.payments().cancelPayment(
        created.getPaymentId(),
        b -> b.reason(CancelReason.SUSPECTED_FRAUD));
```

Operations without a body take plain arguments:

```java
PaymentInformationResponse info =
        client.payments().getPaymentInformation(created.getPaymentId(), null);
System.out.println("Status: " + info.getPaymentStatus());
```

A typical capture flow after the shopper completes checkout:

```java
CapturePaymentResponse captured = client.payments().capturePayment(
        created.getPaymentId(),
        b -> b.instructedAmount(a -> a.amount("100.00").currency("EUR")));
```

And a refund, which lives on its own accessor because it is a separate resource with its own ID:

```java
RefundPaymentResponse refund = client.refunds().refundPayment(b -> b
        .paymentId(created.getPaymentId())
        .instructedAmount(a -> a.amount("100.00").currency("EUR")));
```

Behind every call the SDK fetches and caches an OAuth 2.0 access token, sets a unique `X-Request-ID` header, serializes the request, and deserializes the response into the typed response DTO. Enable a logger via `loggingWith(...)` and record the `X-Request-ID` to correlate failed calls with support — see [Error handling](/integration/api-client-sdk/guides/error-handling.md#reading-api-errors).

## Step 4 — Handle exceptions

All SDK failures are unchecked exceptions extending `ECommException`. Catch the specific types you can act on first, and `ECommException` as the safety net:

```java
try {
    InitiatePaymentResponse response = client.payments().initiateCitPayment(request);
} catch (InvalidRequestException e) {
    // A required field was missing at build time — fix the request in code.
} catch (AuthenticationException e) {
    // Token retrieval or authorization failed — check credentials and the token endpoint.
} catch (ConnectionException e) {
    // Transport failure (DNS, refused connection, network error).
} catch (ECommException e) {
    // The API rejected the call, or the response could not be processed.
}
```

What each catch block means in practice:

* `InvalidRequestException` — thrown by `build()` before any network traffic. A code bug, not a runtime condition; there is nothing to retry.
* `AuthenticationException` — the OAuth token could not be obtained. Verify the client ID and secret in the Merchant Portal.
* `ConnectionException` — the request may or may not have reached the platform. The SDK never retries a request; after a connection failure on a state-changing call, query the payment state with `getPaymentInformation(...)` before re-sending. See [Retries, idempotency, timeouts](/integration/api-client-sdk/guides/error-handling.md#retries-idempotency-timeouts).
* `ECommException` (base type) — an HTTP error status or an unusable response. The status code appears in the exception message (for example `Client error during initiateCitPayment: 404`); the response's error details are written to the SDK logger, not attached to the exception, so keep a logger enabled to diagnose these — see [Reading API errors](/integration/api-client-sdk/guides/error-handling.md#reading-api-errors).

Do not narrow your last catch to `ApiException`: HTTP error statuses surface as the base `ECommException`, and `AuthenticationException`/`ConnectionException` are siblings of `ApiException`, not subclasses. The full hierarchy, including `SerializationException`, `EncryptionException`, `ApiServerException` and `EgwBootstrapException`, is in [Error handling](/integration/api-client-sdk/guides/error-handling.md).

## Step 5 — Listen for Webhooks

The platform notifies you about payment state changes by POSTing to an endpoint you expose. The SDK can run that endpoint for you: enable the listener at client creation with `listeningAt(...)`, then register a handler through `notifications()`:

```java
EgwClient client = new DefaultEgwClientFactory()
        .connectingTo(URI.create("https://sandbox.api.ecommerce.sebgroup.com"))
        .authorizingAs(System.getenv("EGW_CLIENT_ID"), System.getenv("EGW_CLIENT_SECRET"))
        .listeningAt(URI.create("http://0.0.0.0:8080/egw/notifications"))
        .createEgwClient();

client.notifications().receiveNotifications(notification -> {
    if ("PAYMENT".equals(notification.getType())) {
        PaymentInformationResponse info =
                client.payments().getPaymentInformation(notification.getPaymentId(), null);
        // Update the order from info.getPaymentStatus() — not from the notification alone.
    }
});
```

`receiveNotifications(...)` takes a `Consumer<NotificationRequest>` and returns immediately; the listener keeps delivering notifications on a background thread until `client.close()`. The notification carries `type`, `paymentId` and `paymentStatus` as strings.

Always treat a notification as a trigger, not as truth: the current build does not verify the authenticity of inbound webhook calls, so re-query the payment state through the API before updating an order. This and the transport requirements (the built-in listener speaks plain HTTP — terminate TLS in front of it) are covered in [Security mechanics](/integration/api-client-sdk/guides/security-mechanics.md#webhook-notifications).

Two usage rules keep the listener healthy: configure `listeningAt(...)` before calling `receiveNotifications(...)` (without it the call fails with a `NullPointerException` in the current build), and register exactly one handler per client — there is no unregister, and calling `receiveNotifications(...)` twice on the same client is not supported. If your handler throws, the SDK answers the platform with HTTP 500 and the notification is not redelivered by the SDK, so keep handlers small and quick to acknowledge.

## Complete example

A minimal end-to-end flow — create the client, listen for notifications, initiate a payment, and capture it once checkout completes:

```java
import com.tieto.banking.egw.merchantapi.sdk.application.EgwClient;
import com.tieto.banking.egw.merchantapi.sdk.application.bootstrap.DefaultEgwClientFactory;
import com.tieto.banking.egw.merchantapi.sdk.application.dto.*;
import com.tieto.banking.egw.merchantapi.sdk.core.ECommException;
import java.net.URI;

public class EgwIntegration {
    public static void main(String[] args) {
        try (EgwClient client = new DefaultEgwClientFactory()
                .connectingTo(URI.create("https://sandbox.api.ecommerce.sebgroup.com"))
                .authorizingAs(System.getenv("EGW_CLIENT_ID"), System.getenv("EGW_CLIENT_SECRET"))
                .listeningAt(URI.create("http://0.0.0.0:8080/egw/notifications"))
                .createEgwClient()) {

            client.notifications().receiveNotifications(n ->
                    System.out.println("Payment " + n.getPaymentId() + " → " + n.getPaymentStatus()));

            InitiatePaymentResponse created = client.payments().initiateCitPayment(b -> b
                    .channel(CitPaymentChannel.BROWSER)
                    .instructedAmount(a -> a.amount("100.00").currency("EUR"))
                    .orderNumber("ORD-1001")
                    .participantId(System.getenv("EGW_PARTICIPANT_ID"))
                    .shopAgreementId(System.getenv("EGW_SHOP_AGREEMENT_ID"))
                    .shopRedirectUrl("https://shop.example.com/return")
                    .description("Fishing rod, 2.7 m"));

            System.out.println("Payment created: " + created.getPaymentId());

            // ... the shopper completes checkout; you learn the outcome via the
            // notification handler above or by polling getPaymentInformation(...).

            CapturePaymentResponse captured = client.payments().capturePayment(
                    created.getPaymentId(),
                    b -> b.instructedAmount(a -> a.amount("100.00").currency("EUR")));

            System.out.println("Captured, status: " + captured.getPaymentStatus());

        } catch (ECommException e) {
            System.err.println("Payment flow failed: " + e.getMessage());
        }
    }
}
```

## Next steps

* [Error handling](/integration/api-client-sdk/guides/error-handling.md) — the exception hierarchy in full, and retry guidance.
* [Security mechanics](/integration/api-client-sdk/guides/security-mechanics.md) — card encryption for card checkout sessions, webhook security.
* [Java quickstart](/integration/api-client-sdk/quickstarts/java.md) — install, credentials, known limitations.


---

# 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/using-the-sdk/java.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.
