> ## Documentation Index
> Fetch the complete documentation index at: https://andcze.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Java i Kotlin

> pl.itemshop:sdk (Maven) — Storefront i klient API dla JVM 17+, wygodny w pluginach Paper i botach.

`pl.itemshop:sdk` to oficjalne SDK Javy (17+, `java.net.http`; jedyna zależność to **Gson**, który mają już Paper i Spigot). Moduł `pl.itemshop:sdk-kotlin` dodaje funkcje `suspend` i `Flow`.

| Klasa | Do czego | Sekrety |
| - | - | - |
| `Storefront` | Storefront API v1: sklep, produkty, płatności, rabaty, kasa, vouchery, status zamówienia, widżety | **brak** — nigdy nie wysyła klucza API |
| `ItemShopClient` | pełne API z kluczem + Sites API + AI Workspace | klucz API (`X-API-Key`) |

Każda metoda ma wariant blokujący i nieblokujący (`…Async` → `CompletableFuture`).

## Instalacja

```kotlin build.gradle.kts theme={null}
dependencies {
    implementation("pl.itemshop:sdk:0.1.0")
    // Kotlin: implementation("pl.itemshop:sdk-kotlin:0.1.0")
}
```

## Plugin Paper — asynchronicznie

Zapytania HTTP idą na wątkach `HttpClient`; na główny wątek serwera wracasz schedulerem Bukkita. **Nigdy** nie wołaj metod blokujących (`list()`, `create()`…) z głównego wątku — tylko `…Async`.

```java theme={null}
public final class ShopPlugin extends JavaPlugin {
    private Storefront storefront;

    @Override
    public void onEnable() {
        storefront = Storefront.builder()
                .shop(getConfig().getString("shop-id"))   // id, uniqueName or shop domain
                .locale("pl")
                .userAgent(getName() + "/" + getPluginMeta().getVersion())
                .build();
    }

    void showProducts(Player player) {
        storefront.products().listAsync()
                .thenAccept(list -> Bukkit.getScheduler().runTask(this, () -> {
                    for (Product p : list.products()) {
                        player.sendMessage(p.name() + " — " + p.price() + " " + p.currency());
                    }
                }))
                .exceptionally(err -> {
                    Throwable cause = err.getCause() != null ? err.getCause() : err;
                    getLogger().warning("ItemShop: " + cause);
                    return null;
                });
    }

    void redeem(Player player, String code, String turnstileToken) {
        storefront.vouchers().redeemAsync(code, player.getName(), turnstileToken, null)
                .thenAccept(r -> Bukkit.getScheduler().runTask(this,
                        () -> player.sendMessage("Redeemed: " + r.productName())));
    }
}
```

Kasa z gry (np. link w GUI):

```java theme={null}
storefront.checkout().createAsync(CheckoutInput.builder()
                .item(productId, 1)
                .nick(player.getName())
                .acceptTerms(true)            // terms — consent collected from the player
                .immediateDelivery(true)      // instant delivery + loss of the withdrawal right
                .paymentMethod(method.code(), method.channel())
                .build())
        .thenAccept(res -> Bukkit.getScheduler().runTask(this,
                () -> player.sendMessage("Pay here: " + res.redirectUrl())));
```

`checkout().create…` sam uzupełnia `consents.legalVersion` z profilu sklepu i zawsze wysyła `Idempotency-Key`, więc ponowienie po zerwanym połączeniu nie utworzy drugiego zamówienia. Koszyk mieści 1–20 pozycji (powtarzaj `.item(...)`); dwie lub więcej tworzą jedną grupę płatności. Po powrocie z bramki odczytasz zamówienie przez `Storefront.returnedOrderFromReturnUrl(url)` (`?itemshop_order=<numer>&itemshop_key=<klucz>` → `ReturnedOrder(number, key)`), poczekasz na płatność przez `orders().awaitPaymentByNumberAsync(number, key, Duration.ofSeconds(3))`, a treść cyfrową odbierzesz przez `orders().deliveryAsync(number, key)`. W Kotlinie `statusByNumber`, `delivery` i `awaitPaymentByNumber` są `suspend`. Wersje po id (`orderIdFromReturnUrl`, `awaitPaymentAsync`) obsługują wyłącznie zamówienia sprzed 3.0.

### Shadow i relokacja

```kotlin theme={null}
tasks.shadowJar {
    relocate("pl.itemshop.sdk", "com.example.myplugin.libs.itemshop")
    relocate("com.google.gson", "com.example.myplugin.libs.gson")
}
```

Wątki `HttpClient` są demonami — nie blokują wyłączenia serwera. Własny `Executor` (`.executor(...)`) dostaje też callbacki async i opóźnienia retry.

### Kotlin (coroutines)

```kotlin theme={null}
val sf = storefront(shopId) { locale("pl") }.suspending()
val api = itemShopClient { apiKey(System.getenv("ITEMSHOP_API_KEY")) }.suspending()

scope.launch(Dispatchers.IO) {
    val list = sf.products.list()
    api.orders.listAll(shopId, OrdersService.ListParams().status("paid"))   // Flow<Order>, pages lazily
        .collect { order -> println(order.orderNumber()) }
    val res = sf.checkout.create {
        item(list.products().first().id(), 1); nick("Steve"); acceptTerms(true); immediateDelivery(true)
    }
}
```

Anulowanie korutyny anuluje żądanie HTTP. Na Paperze wróć na główny wątek przez `Bukkit.getScheduler().runTask(plugin) { … }`.

## Klient API (z kluczem)

```java theme={null}
ItemShopClient client = ItemShopClient.builder()
        .apiKey(System.getenv("ITEMSHOP_API_KEY"))     // null → $ITEMSHOP_API_KEY
        .build();

Product created = client.products().create(shopId, CreateProductDto.builder()
        .name("VIP").price(19.99).serverId(serverId)
        .commands(List.of("lp user {PLAYER} parent set vip"))
        .build());

for (Order o : client.orders().listAll(shopId, new OrdersService.ListParams().status("paid"))) { /* lazy */ }
client.orders().list(shopId).autoPagingStream().limit(50).forEach(o -> { });

client.site().deploy(shopId, Files.readAllBytes(zip), SiteDeployment.PRODUCTION);   // = `itemshop deploy`
AgentSetup setup = client.workspace().agentSetup(shopId, "claude-code");             // MCP config
```

Serwisy generowane: `shops, servers, products, paymentMethods, paymentErrors, blacklist, discounts, vouchers, stats, uploads, orders`. Ręczne: `site()` (pliki, draft, rewizje, upload, buildy, wdrożenia, diff), `templates()`, `workspace()`, `agents()`, `skills()`. Tokeny Workspace wymagają sesji panelu — kluczem API dostaniesz 401/403. Endpoint bez typowanej metody: `client.request("GET", "/api/v2/…", null)`. Modele (`pl.itemshop.sdk.model.*`) to rekordy z builderami; `null` nie jest wysyłany.

## Błędy

Każdy błąd to niekontrolowany `ItemShopException` z `code()` (kod backendu, np. `LEGAL_CONSENT_REQUIRED`, albo SDK: `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `VALIDATION_FAILED`, `RATE_LIMITED`, `SERVER_ERROR`, `NETWORK_ERROR`, `TIMEOUT`, `ABORTED`…), `status()`, `details()`, `retryAfter()` i `body()`. Podklasy do łapania: `BadRequestException` (400/413/422), `AuthenticationException` (401), `PermissionDeniedException` (403), `NotFoundException` (404), `ConflictException` (409), `RateLimitException` (429), `ServerException` (5xx), `ApiConnectionException` (sieć, timeout, anulowanie). W `CompletableFuture` błąd przychodzi jako przyczyna `CompletionException`.

## Retry, timeouty, idempotencja

2 ponowienia, backoff 300 ms·2ⁿ (maks. 5 s) z jitterem, `Retry-After` zastępuje opóźnienie. GET/HEAD i żądania z `Idempotency-Key` (`orders().create`, `checkout().create`) są ponawiane po błędach sieci, timeoutach i 408/425/429/500/502/503/504; pozostałe tylko po 429. `timeout(...)` ogranicza jedną próbę (domyślnie 30 s). Przerwanie wątku czekającego na wynik anuluje żądanie (`ABORTED`).

## Webhooki

Backend nie wysyła podpisanych webhooków do właścicieli sklepów (powiadomienia idą na Discord — zob. [Webhooki](https://andcze.mintlify.site/pl/developers/webhooks.md)), więc SDK nie ma weryfikatora podpisów. Do realizacji zamówień w grze służy [protokół pluginu](https://andcze.mintlify.site/pl/developers/plugin-protocol.md).

> **[Storefront API](https://andcze.mintlify.site/pl/developers/storefront-api.md)**
>
> Endpointy pod spodem SDK.

> **[Protokół pluginu](https://andcze.mintlify.site/pl/developers/plugin-protocol.md)**
>
> Realizacja zamówień w czasie rzeczywistym.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.