> 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/c-.net.md).

# C# / .NET

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

Use this guide if you have completed the [C# quickstart](/integration/api-client-sdk/quickstarts/c-.net.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)**. A [complete example](#complete-example) puts the steps together.

## Step 1 — Create the client

Everything starts with `DefaultEgwClientFactory`, a fluent factory that assembles an `EgwClient`. Unlike the Java and PHP factories, the .NET factory derives nothing from the base URL — you configure the base URL, token endpoint and (for card sessions) JWK endpoint explicitly, in any order:

```csharp
using Egw.Sdk.Application;
using Egw.Sdk.Application.Bootstrap;

using EgwClient client = new DefaultEgwClientFactory()
    .ConnectingTo("https://sandbox.api.ecommerce.sebgroup.com")
    .TokenEndpoint("https://api.ecommerce.sebgroup.com/auth/realms/participants-seb/protocol/openid-connect/token")
    .AuthorizingAs(
        Environment.GetEnvironmentVariable("EGW_CLIENT_ID")!,
        Environment.GetEnvironmentVariable("EGW_CLIENT_SECRET")!)
    .EncryptingWith("https://checkout.ecommerce.sebgroup.com/api/keys/card-encryption.jwk")
    .CreateEgwClient();
```

`ConnectingTo(...)`, `TokenEndpoint(...)` and `AuthorizingAs(...)` are required; missing any of them fails at `CreateEgwClient()` with `EgwBootstrapException`. `EncryptingWith(...)` is only needed for card checkout sessions, and `ListeningOn(uri)` enables the Webhook listener used in [Step 5](#step-5--listen-for-webhooks).

The factory pattern keeps the client's infrastructure replaceable without changing your application code: adapters (HTTP client, serializer, encrypter, logger) are resolved through provider interfaces (`IApiClientProvider`, `IEgwLoggerProvider`, …) discovered by assembly scan, so you replace one by shipping your own provider — the usual route for custom proxies, TLS material, or timeouts.

Create the client once at startup and reuse it — it is safe to share, and its OAuth token is cached and refreshed automatically. `EgwClient` is `IDisposable`; `Dispose()` stops the webhook listener, so if you enabled one, keep the client alive for as long as you want to receive notifications.

## Step 2 — Compose request DTOs

Requests and responses are data transfer objects (DTOs) in `Egw.Sdk.Application.Dto`. They are immutable — get-only properties, no public constructor — and are composed through a nested `Builder` class reached via the static `NewBuilder()` method. One naming quirk to know up front: the `amount` field's property and setter are named `VarAmount` (the JSON field on the wire is still `amount`).

**Classic builder.** Setters are named after the property and return the builder; `Build()` produces the DTO:

```csharp
using Egw.Sdk.Application.Dto;

Amount amount = Amount.NewBuilder()
    .VarAmount("100.00")
    .Currency("EUR")
    .Build();
```

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

```csharp
InitiateCitPaymentRequest request = InitiateCitPaymentRequest.NewBuilder()
    .Channel(CitPaymentChannel.BROWSER)
    .InstructedAmount(a => a.VarAmount("100.00").Currency("EUR"))
    .OrderNumber("ORD-1001")
    .ParticipantId(Environment.GetEnvironmentVariable("EGW_PARTICIPANT_ID")!)
    .ShopAgreementId(Environment.GetEnvironmentVariable("EGW_SHOP_AGREEMENT_ID")!)
    .ShopRedirectUrl("https://shop.example.com/return")
    .Build();
```

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

**Singular item adders.** Collection properties get an additional singular method that appends one element, so you can add items in a loop without managing the list yourself:

```csharp
Basket basket = Basket.NewBuilder()
    .Item(BasketItemProduct.NewBuilder()
        .Name("Fishing rod")
        .Description("Carbon fiber rod, 2.7 m")
        .Price(a => a.VarAmount("100.00").Currency("EUR"))
        .Quantity(1)
        .Build())
    .SubtotalAmount(a => a.VarAmount("100.00").Currency("EUR"))
    .Build();
```

**Validation.** The .NET SDK does not check required fields at build time — unlike Java, a missing `OrderNumber` surfaces as a server-side HTTP 400, not as a local exception.

## Step 3 — Call the API

`EgwClient` groups operations into domain accessors: `Payments()`, `Refunds()`, `Tokens()`, `Shops()`, the checkout-session groups `CardSessions()`, `BankSessions()`, `TokenSessions()`, `ClickToPaySessions()`, and `Notifications()` for webhooks. All operations are async and carry the `Async` suffix — there are no synchronous variants.

Every operation takes a `Guid` request ID as its first argument. Generate one per call and keep it with your order record — it is your correlation handle for support.

Operations with a request body accept either a built DTO or a builder lambda directly:

```csharp
// With a prepared DTO:
InitiatePaymentResponse created =
    await client.Payments().InitiateCitPaymentAsync(Guid.NewGuid(), request);

// Or with the builder lambda inline:
CancelPaymentResponse cancelled = await client.Payments().CancelPaymentAsync(
    Guid.NewGuid(),
    created.PaymentId!,
    b => b.Reason(CancelReason.SUSPECTEDFRAUD));
```

Reading state and capturing after checkout completes:

```csharp
PaymentInformationResponse info = await client.Payments()
    .GetPaymentInformationAsync(Guid.NewGuid(), created.PaymentId!, false);
Console.WriteLine($"Status: {info.PaymentStatus}");

CapturePaymentResponse captured = await client.Payments().CapturePaymentAsync(
    Guid.NewGuid(),
    created.PaymentId!,
    b => b.InstructedAmount(a => a.VarAmount("100.00").Currency("EUR")));
```

Behind every call the SDK fetches and caches an OAuth 2.0 access token, sets the `X-Request-ID` header, serializes the request, and deserializes the response into the typed response DTO.

## Step 4 — Handle exceptions

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

```csharp
using Egw.Sdk.Core;
using Egw.Sdk.Application.Ports.ApiClient;

try
{
    InitiatePaymentResponse response =
        await client.Payments().InitiateCitPaymentAsync(Guid.NewGuid(), request);
}
catch (AuthenticationException e)
{
    // Token retrieval or authorization failed — check credentials and the token endpoint.
}
catch (ConnectionException e)
{
    // Transport failure. The SDK never retries: query the payment state with
    // GetPaymentInformationAsync(...) before re-sending a state-changing call.
}
catch (ECommException e)
{
    // The API rejected the call (status code is in the message, e.g.
    // "Client error during InitiateCitPayment: 400") or the response
    // could not be processed.
}
```

HTTP error statuses surface as the base `ECommException` with the status code in the message; the API's error details are written to the SDK logger, not attached to the exception (the `ApiException.StatusCode` and `ResponseBody` accessors exist but are not populated in this build). Enable a logger and correlate on `X-Request-ID` — see [Reading API errors](/integration/api-client-sdk/guides/error-handling.md#reading-api-errors). The full hierarchy (`SerializationException`, `EncryptionException`, `ApiServerException`, `EgwBootstrapException`, and the rest) 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 `ListeningOn(...)`, then register a handler:

```csharp
using EgwClient client = new DefaultEgwClientFactory()
    .ConnectingTo("https://sandbox.api.ecommerce.sebgroup.com")
    .TokenEndpoint("https://api.ecommerce.sebgroup.com/auth/realms/participants-seb/protocol/openid-connect/token")
    .AuthorizingAs(clientId, clientSecret)
    .ListeningOn("http://0.0.0.0:8080/egw/notifications")
    .CreateEgwClient();

client.Notifications().ReceiveNotifications(notification =>
{
    // A notification arrived: a payment changed state. In the current build the
    // notification payload exposes no fields yet, so treat delivery as a wake-up
    // signal: re-query your open payments with GetPaymentInformationAsync(...)
    // and update orders from the API's answer.
});
```

`ReceiveNotifications(...)` takes an `Action<NotificationRequest>` and returns immediately; the listener keeps delivering notifications on background threads until the client is disposed. The payload accessors (`type`, `paymentId`, `paymentStatus`, as delivered on the wire) arrive in a later build — until then the wake-up-signal pattern above is the working path.

Always treat a notification as a trigger, not as truth: the current build does not verify the authenticity of inbound webhook calls, so confirm state through the API before updating an order. 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: configure `ListeningOn(...)` before calling `ReceiveNotifications(...)` (without it the call fails with a `NullReferenceException` in the current build), and register exactly one handler per client — there is no unregister. If your handler throws, the SDK answers with HTTP 500 and the notification is not redelivered by the SDK.

## Complete example

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

```csharp
using Egw.Sdk.Application;
using Egw.Sdk.Application.Bootstrap;
using Egw.Sdk.Application.Dto;
using Egw.Sdk.Core;

using EgwClient client = new DefaultEgwClientFactory()
    .ConnectingTo("https://sandbox.api.ecommerce.sebgroup.com")
    .TokenEndpoint("https://api.ecommerce.sebgroup.com/auth/realms/participants-seb/protocol/openid-connect/token")
    .AuthorizingAs(
        Environment.GetEnvironmentVariable("EGW_CLIENT_ID")!,
        Environment.GetEnvironmentVariable("EGW_CLIENT_SECRET")!)
    .ListeningOn("http://0.0.0.0:8080/egw/notifications")
    .CreateEgwClient();

try
{
    client.Notifications().ReceiveNotifications(_ =>
        Console.WriteLine("Notification received — re-checking open payments."));

    InitiatePaymentResponse created = await client.Payments().InitiateCitPaymentAsync(
        Guid.NewGuid(),
        b => b.Channel(CitPaymentChannel.BROWSER)
              .InstructedAmount(a => a.VarAmount("100.00").Currency("EUR"))
              .OrderNumber("ORD-1001")
              .ParticipantId(Environment.GetEnvironmentVariable("EGW_PARTICIPANT_ID")!)
              .ShopAgreementId(Environment.GetEnvironmentVariable("EGW_SHOP_AGREEMENT_ID")!)
              .ShopRedirectUrl("https://shop.example.com/return")
              .Description("Fishing rod, 2.7 m"));

    Console.WriteLine($"Payment created: {created.PaymentId}");

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

    CapturePaymentResponse captured = await client.Payments().CapturePaymentAsync(
        Guid.NewGuid(),
        created.PaymentId!,
        b => b.InstructedAmount(a => a.VarAmount("100.00").Currency("EUR")));

    Console.WriteLine("Captured.");
}
catch (ECommException e)
{
    Console.Error.WriteLine($"Payment flow failed: {e.Message}");
}
```

## 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, webhook security.
* [C# quickstart](/integration/api-client-sdk/quickstarts/c-.net.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/c-.net.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.
