> ## 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 PHP

> itemshop/sdk (Composer) — storefront, klient API z kluczem, błędy i weryfikacja webhooków dla PHP 8.1+.

Oficjalne SDK PHP (≥ 8.1): dowolny klient PSR-18 (domyślnie Guzzle), PSR-4, niemutowalne DTO, PHPStan poziom 8. Odpowiada `@itemshop/sdk` z TypeScript.

| | Uwierzytelnienie | Gdzie używać |
| - | - | - |
| `ItemShop\Storefront` | brak (publiczne Storefront API v1) | motywy WordPress, widoki Laravel, zadania cron |
| `ItemShop\Client` | klucz API (`X-API-Key`) | tylko Twój backend — **nigdy** w przeglądarce |

## Instalacja

```bash theme={null}
composer require itemshop/sdk
```

## Storefront (publiczne, bez sekretów)

```php theme={null}
use ItemShop\Storefront;

$sf = new Storefront(shopId: '664f1a2b3c4d5e6f7a8b9c0d', locale: 'pl');   // id, uniqueName or domain; or $ITEMSHOP_SHOP_ID
// $sf = Storefront::fromDomain('sklep.mojserwer.pl');

$shop = $sf->shop->get();
echo $shop->displayName, ' — ', $shop->currency, PHP_EOL;
foreach ($shop->servers as $server) {
    printf("%s: %s (%d/%d)\n", $server->name, $server->isOnline ? 'online' : 'offline', $server->playersOnline, $server->maxPlayers);
}

$catalog = $sf->products->list();                    // ->categories (= the shop's servers) + ->products
foreach ($sf->products->list(category: $catalog->categories[0]->id)->products as $product) {
    echo $product->name, ': ', $product->price, ' ', $product->currency, PHP_EOL;
}

$vip = $sf->products->get('vip');                    // slug or id
$methods = $sf->paymentMethods->list();              // one entry per channel: code, channel, limits
$quote = $sf->discounts->validate('LATO10', [['productId' => $vip->id]]);   // $quote->valid / ->reason
```

### Kasa

Strona nigdy nie dotyka danych płatniczych: `checkout->create()` zwraca `redirectUrl` (bramka albo strona statusu dla darmowego zamówienia). SDK dodaje losowy `Idempotency-Key` i ponawia błędy sieci **z tym samym kluczem**, więc zerwane połączenie nie utworzy dwóch zamówień. Koszyk ma 1–20 pozycji; dwie lub więcej tworzą jedną grupę płatności (`->groupId`, `->orderIds`).

```php theme={null}
$result = $sf->checkout->create([
    'items' => [['productId' => $vip->id, 'quantity' => 1, 'variant' => '30 dni']],
    'nick' => 'Steve',
    'email' => 'steve@example.com',
    'consents' => [
        'terms' => true,
        'immediateDelivery' => true,
        'legalVersion' => $shop->legal->version,      // the legal documents the buyer was shown
    ],
    'paymentMethodCode' => $methods[0]->code,
    'paymentChannel' => $methods[0]->channel,
    'returnUrl' => 'https://sklep.mojserwer.pl/dziekujemy',
], captchaToken: $_POST['cf-turnstile-response'] ?? null);

header('Location: ' . $result->redirectUrl);

// On the return page:
$returned = Storefront::returnedOrderFromReturnUrl($currentUrl);   // ?itemshop_order=<numer>&itemshop_key=<klucz> → ['number' => …, 'key' => …] albo null
if ($returned !== null) {
    $status = $sf->orders->statusByNumber($returned['number'], $returned['key']);   // pending → paid → completed
    if ($status->delivery?->ready) {
        $delivery = $sf->orders->delivery($returned['number'], $returned['key']);   // klucze, tekst, link do pliku
    }
}
// $result->orderNumber and $result->statusKey działają tak samo zaraz po kasie
```

Możesz też użyć typowanych modeli (`new StorefrontCheckoutDto(items: [new StorefrontCartItemDto(productId: …)], …)`). Własny klucz (`idempotencyKey:`) trzymaj z koszykiem w sesji, by zachować idempotencję między żądaniami; `Storefront::newIdempotencyKey()` generuje poprawny.

### Vouchery i widżety

```php theme={null}
$sf->vouchers->redeem('ABCD-1234', 'Steve', turnstileToken: $token);   // ->orderNumber, ->statusKey, ->productName

$sf->widgets->recentPurchases(limit: 5);   // [nick, productName, serverName, amount, createdAt]
$sf->widgets->topSpenders(limit: 10);      // [nick, totalSpent, orderCount]
$sf->widgets->monthlyGoal();               // enabled, percent
$sf->widgets->onlinePlayers();             // count, players: ['Steve', …]
```

Widżet wyłączony przez właściciela sklepu zwraca puste dane.

## Klient (klucz API, po stronie serwera)

Klucz utworzysz w panelu: **Ustawienia → Klucze API** — nadaj tylko potrzebne uprawnienia. Zobacz [API z kluczem](https://andcze.mintlify.site/pl/developers/api-keys.md).

```php theme={null}
use ItemShop\Client;

$client = new Client(apiKey: getenv('ITEMSHOP_API_KEY'));   // or just new Client() with $ITEMSHOP_API_KEY set
$shopId = '664f1a2b3c4d5e6f7a8b9c0d';

// every page, lazily (a generator)
foreach ($client->orders->list($shopId, status: 'completed', fromDate: '2026-10-01') as $order) {
    echo $order->orderNumber, ' ', $order->customer?->nickname, PHP_EOL;
}

$page = $client->orders->list($shopId, limit: 50);   // just the first page
$page->items; $page->total; $page->pages; $page->hasNext;
$next = $page->nextPage();

$product = $client->products->create($shopId, [
    'name' => 'VIP', 'price' => 19.99, 'serverId' => '…', 'commands' => ['lp user {PLAYER} parent set vip'],
]);
$client->products->update($shopId, $product->id, ['price' => 14.99]);
$client->orders->fulfill($shopId, $orderId);
```

Zasoby: `shops`, `team`, `servers`, `products`, `paymentMethods`, `blacklist`, `discounts`, `vouchers`, `stats`, `uploads`, `orders`, `paymentErrors`, `site` (`files`, `draft`, `revisions`, `builds`, `deployments`), `templates`, `agents`, `skills`. Ustawienia i tokeny AI Workspace są tylko dla panelu, więc nie ma ich w kliencie.

### Strona: wdrożenie z CI

```php theme={null}
$client->site->publish($shopId, __DIR__ . '/dist', message: 'CI build');   // upload → build → deploy (production)
$client->site->upload($shopId, 'site.zip');                                // just a new revision (a directory needs ext-zip)

$client->site->files->list($shopId);                                       // draft; rev: '<id>' for a revision
$client->site->files->write($shopId, 'src/site.config.ts', $source);
$client->site->files->read($shopId, 'src/App.tsx');
$client->site->draft->commit($shopId, ['message' => 'New colors']);
$build = $client->site->builds->create($shopId, ['revisionId' => 'draft']);
$build = $client->site->builds->wait($shopId, $build->id);
echo $client->site->builds->log($shopId, $build->id);
$client->site->deployments->promote($shopId, $olderDeploymentId);          // rollback
```

## Błędy

Każdy wyjątek rozszerza `ItemShop\Exception\ItemShopException` i ma zawsze ustawione `$errorCode` (UPPER\_SNAKE\_CASE): kod z backendu, jeśli był, inaczej wyprowadzony ze statusu, inaczej kod SDK (`NETWORK_ERROR`, `TIMEOUT`, `CONFIG_ERROR`, `VALIDATION_FAILED`). `$status` wynosi 0, gdy nie było odpowiedzi.

```php theme={null}
use ItemShop\ErrorCode;
use ItemShop\Exception\{ApiException, ConnectionException, RateLimitException, ValidationException};

try {
    $sf->checkout->create($input);
} catch (ValidationException $e) {            // odrzucone przed wysłaniem, np. LEGAL_CONSENT_REQUIRED
} catch (RateLimitException $e) {
    sleep((int) ($e->retryAfter() ?? 5));
} catch (ApiException $e) {
    if ($e->is(ErrorCode::LEGAL_DOCUMENTS_CHANGED)) { /* pobierz $shop->legal ponownie i zapytaj o zgody */ }
    if ($e->is(ErrorCode::CAPTCHA_REQUIRED)) { /* pokaż Turnstile, ponów z captchaToken: */ }
} catch (ConnectionException $e) {            // brak sieci / TimeoutException
}
```

| Exception | Status |
| - | - |
| `BadRequestException` | 400 |
| `AuthenticationException` | 401 |
| `PermissionDeniedException` | 403 (klucz bez uprawnienia) |
| `NotFoundException` | 404 |
| `ConflictException` | 409 (`IDEMPOTENCY_KEY_REUSED`, `CHECKOUT_IN_PROGRESS`…) |
| `UnprocessableEntityException` | 422 |
| `RateLimitException` | 429 (`retryAfter()`) |
| `ServerException` | 5xx |

## Ponowienia, limity czasu, klient HTTP

GET oraz zapisy z `Idempotency-Key` (kasa) są ponawiane po błędach sieci i statusach 408/425/429/500/502/503/504; inne zapisy tylko po 429. Backoff wykładniczy od 300 ms, maks. 5 s, z jitterem, z poszanowaniem `Retry-After`.

```php theme={null}
new Client(apiKey: $key, maxRetries: 4, timeout: 10.0);
new Client(apiKey: $key, httpClient: $anyPsr18Client, requestFactory: $psr17, streamFactory: $psr17);
new Client(apiKey: $key, apiBase: 'http://localhost:3000');     // local backend
```

## Webhooki

Backend nie wysyła jeszcze podpisanych webhooków wychodzących (zamówienia trafiają na Discord i do pluginu) — zob. [Webhooki](https://andcze.mintlify.site/pl/developers/webhooks.md). `Webhook` implementuje planowany schemat [Standard Webhooks](https://www.standardwebhooks.com) (`webhook-id`, `webhook-timestamp`, `webhook-signature: v1,<base64 HMAC-SHA256>`), więc możesz pisać odbiornik już dziś.

```php theme={null}
use ItemShop\Webhook;
use ItemShop\Exception\WebhookVerificationException;

$webhook = new Webhook(getenv('ITEMSHOP_WEBHOOK_SECRET'));              // "whsec_…"
try {
    $event = $webhook->verify(file_get_contents('php://input'), getallheaders());   // raw body!
    // or $webhook->verifyRequest($psr7Request)
} catch (WebhookVerificationException) {
    http_response_code(400);
    exit;
}
```

## Modele

Modele są w `ItemShop\Model` jako klasy `final` z polami `public readonly`, generowane z OpenAPI. `Model::fromArray()` buduje model z formatu `camelCase`, `->toArray()` / `json_encode($model)` wraca. Pola nieznane SDK trafiają do `$model->extra`.

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

> **[Błędy i limity](https://andcze.mintlify.site/pl/developers/errors-and-limits.md)**
>
> Kody błędów i limity zapytań.


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