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

> ## Agent Instructions
> ## paymove integration rules for AI agents
>
> These rules are verified against the live API. Follow them exactly.
>
> 1. Amounts are INTEGERS in grosze (minor units). `1000` means 10.00 PLN. PLN is the only supported currency and there is no currency field in the API.
> 2. A float `price` (e.g. `12.99`) is SILENTLY TRUNCATED to 12 grosze and still returns HTTP 200. Always send an integer: `Math.round(pln * 100)`.
> 3. Authenticate with the `X-API-KEY` header. Never `Authorization: Bearer`. Sandbox keys start with `sk_test_`, production keys with `sk_live_`.
> 4. Server-side only. A call from a merchant page is rejected. Never put the key in frontend code.
> 5. Unknown request fields are silently ignored and still return HTTP 200 — a wrong body shape looks like success. Match the documented shape exactly.
> 6. A missing `externalId` returns HTTP 500 `Something went wrong`, not 400. A missing `price` or `details.returnUrl` returns HTTP 200 and a usable `redirectUrl` — no error at all. Validate the body yourself before sending it.
> 7. Always verify the `X-Paymove-Signature` header on incoming webhooks before trusting them: https://docs.paymove.io/en/webhook-signature.md
> 8. Errors are `{"status": <int>, "message": "<text>"}`. Branch on the HTTP status only — never on `message`, which is unstable and leaks internal class names.
> 9. There is no rate limiting, no HTTP 429, no HTTP 422, no `Idempotency-Key` header and no API versioning. Do not write code that handles them.
> 10. Re-POSTing an `externalId` that already exists returns HTTP 200 with the ORIGINAL `redirectUrl` and silently discards EVERY field you send — the new price, description and details are all ignored. Use `PATCH /api/pay/product/{productId}/subproduct/{externalId}` to change a price.
> 11. Webhooks fire only on `COMPLETED` by default, and `retries` defaults to `0` (no retries) unless you set it explicitly. Delivery counts as successful when the HTTP status equals `expectedCode` — the response body is never inspected.
> 12. Never fulfil an order on the `returnUrl` redirect. The checkout does not redirect there by itself: the customer has to click "back to shop", and inside the widget modal that redirect never happens. Fulfil only in the webhook handler, after verifying the signature.
> 13. Do not pin a version of `@paymove-io/sdk` — install the latest.
> 14. Full documentation index: https://docs.paymove.io/llms.txt · Copy-paste quickstart: https://docs.paymove.io/en/quickstart.md · Agent skill: https://docs.paymove.io/skill.md

# Statusy płatności

> Statusy płatności, zawartość payloadu webhooka oraz sprawdzanie stanu płatności zapytaniem do API.

## Statusy

| Status                        | Znaczenie                                              | Końcowy |
| ----------------------------- | ------------------------------------------------------ | ------- |
| `INITIALIZED`                 | Płatność utworzona, klient jeszcze nie zapłacił        | Nie     |
| `PENDING`                     | Płatność w toku po stronie operatora                   | Nie     |
| `COMPLETED`                   | Płatność zakończona sukcesem - **realizuj zamówienie** | Tak     |
| `CANCELED`                    | Klient anulował płatność                               | Tak     |
| `ERROR`                       | Płatność nie powiodła się                              | Tak     |
| `REFUNDED`                    | Płatność zwrócona                                      | Tak     |
| `WAITING_FOR_EXTERNAL_ACTION` | Stan przejściowy wewnątrz Paymove                      | Nie     |

<Info>
  Status jest zawsze przekazywany jako **łańcuch znaków** - `"COMPLETED"`, nie liczba. Wartości liczbowe spotykane w starszych integracjach są wewnętrzną reprezentacją bazodanową i nie pojawiają się ani w API, ani w webhookach.
</Info>

<Warning>
  `WAITING_FOR_EXTERNAL_ACTION` to stan przejściowy, ustawiany na chwilę w trakcie przetwarzania po stronie Paymove. Nie jest stanem końcowym i nie oznacza, że płatność wymaga działania klienta. Możesz go zobaczyć, odpytując API w niefortunnym momencie - potraktuj go jak `PENDING`.
</Warning>

Zwróć uwagę na pisownię `CANCELED` - przez jedno „l".

## Kiedy przychodzi webhook

<Warning>
  **Domyślnie Paymove wysyła webhook wyłącznie dla statusu `COMPLETED`.** Powiadomienia o `CANCELED`, `ERROR` czy `REFUNDED` wymagają włączenia po stronie Paymove dla konkretnego produktu. Jeśli ich potrzebujesz, napisz na [integration@paymove.io](mailto:integration@paymove.io).
</Warning>

Oznacza to, że w domyślnej konfiguracji brak webhooka nie odróżnia płatności nieudanej od płatności trwającej. Jeśli musisz rozpoznać nieudane płatności, użyj [zapytania o status](#sprawdzenie-statusu-zapytaniem) albo poproś o włączenie pełnych powiadomień.

## Payload webhooka

Kształt zależy od tego, czy przy rejestracji webhooka ustawiłeś `requestTemplate`.

### Bez `requestTemplate`

Paymove wysyła pełny obiekt płatności. Pola bez wartości są pomijane, więc konkretne powiadomienie może zawierać ich mniej:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "891412c8-8717-4449-9543-e34112bec470",
  "name": "MerchantShop",
  "fullName": "Merchant Shop Sp. z o.o.",
  "shortName": "MShop",
  "location": "PL",
  "externalId": "order-123",
  "orderId": "PAY1784798914400",
  "price": 950,
  "status": "COMPLETED",
  "paymentMethod": "BLIK",
  "email": "klient@example.com",
  "date": 1783246791.745352526,
  "requestId": "8f2b1c44-0d7e-4a91-b2c3-5e7f9a1d3c60"
}
```

| Pole                                        | Opis                                                           |
| ------------------------------------------- | -------------------------------------------------------------- |
| `id`                                        | UUID Twojego produktu (sklepu), nie płatności                  |
| `name`, `fullName`, `shortName`, `location` | Dane produktu z konfiguracji                                   |
| `externalId`                                | **Twój** identyfikator zamówienia - po nim dopasujesz płatność |
| `orderId`                                   | Wewnętrzny identyfikator zamówienia w Paymove                  |
| `price`                                     | Kwota w groszach, **pomniejszona o prowizję Paymove**          |
| `status`                                    | Status płatności jako łańcuch znaków                           |
| `paymentMethod`                             | Użyta metoda płatności                                         |
| `email`                                     | Adres e-mail klienta, jeśli był znany                          |
| `date`                                      | Czas utworzenia płatności (epoka uniksowa)                     |
| `requestId`                                 | Identyfikator żądania, przydatny przy zgłoszeniach do wsparcia |

<Warning>
  **`price` w tym payloadzie to kwota po odjęciu prowizji**, a nie kwota pobrana od klienta. Nie używaj go do weryfikacji, czy klient zapłacił właściwą sumę - porównuj z wartością zapisaną u siebie w momencie tworzenia płatności.
</Warning>

<Info>
  Payload nie zawiera pola `event` ani `type` i nie jest opakowany w kopertę. To płaski obiekt, a rodzaj zdarzenia rozpoznajesz po polu `status`.
</Info>

### Z `requestTemplate`

Payload to dokładnie to, co wyrenderuje Twój szablon. Dla szablonu `{"orderId": "{{externalId}}", "price": "{{price}}"}` otrzymasz:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "orderId": "order-123",
  "price": "1000"
}
```

W szablonie możesz użyć **każdego pola domyślnego payloadu** - w tym `{{status}}`, `{{paymentMethod}}`, `{{email}}`, `{{date}}` czy `{{orderId}}`, nie tylko `{{externalId}}` i `{{price}}`. Zwróć uwagę, że wartości wstawiane do szablonu tekstowego trafiają do payloadu jako **łańcuchy znaków**.

<Info>
  Szablon wymusza własny kształt payloadu i zawiera wyłącznie to, co w nim wypiszesz. Jeśli chcesz rozróżniać statusy, dodaj `{{status}}` do szablonu albo w ogóle nie ustawiaj `requestTemplate`.
</Info>

## Sprawdzenie statusu zapytaniem

Przydatne jako uzupełnienie webhooka - na przykład gdy klient wrócił na `returnUrl`, a powiadomienie jeszcze nie dotarło.

<Warning>
  Ten endpoint działa pod **innym adresem bazowym niż reszta API**. Nie jest routowany przez `gateway-api.sandbox.paymove.io` ani `api.paymove.io` - wywołanie tam zwróci `404` z odpowiedzią `text/plain` o treści `No route found for: GET …`, czyli nawet nie w standardowym formacie `{status, message}`.
</Warning>

| Środowisko | Adres bazowy dla zapytania o status  |
| ---------- | ------------------------------------ |
| Sandbox    | `https://pay-api.sandbox.paymove.io` |
| Produkcja  | `https://pay-api.paymove.io`         |

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl "https://pay-api.sandbox.paymove.io/api/payment/product/{productId}/subproduct/{paymentHash}/status"
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "status": "COMPLETED",
  "orderId": "PAY1784798914400"
}
```

| Parametr      | Opis                                                                |
| ------------- | ------------------------------------------------------------------- |
| `productId`   | UUID Twojego produktu lub jego `shortName`                          |
| `paymentHash` | 10-znakowy skrót płatności z parametru `externalId` w `redirectUrl` |

Gdy płatność o podanym skrócie nie istnieje, otrzymasz:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "status": 404,
  "message": "Payments for externalId ec6RtwTZKb not found"
}
```

<Warning>
  `paymentHash` to **nie** jest `externalId` przekazany przy tworzeniu płatności. To wartość wygenerowana przez Paymove, którą otrzymujesz w `redirectUrl` - na przykład `ec6RtwTZKb` w adresie `https://checkout.sandbox.paymove.io/{productId}?externalId=ec6RtwTZKb`. Zapisz ją przy tworzeniu płatności.
</Warning>

<Info>
  Endpoint nie wymaga klucza API. Nie przekazuj do niego danych wrażliwych i nie traktuj samej odpowiedzi jako jedynego dowodu płatności w krytycznych przepływach - wiarygodnym potwierdzeniem jest zweryfikowany webhook.
</Info>

## Zalecany przepływ

1. Utwórz płatność i zapisz u siebie `externalId` oraz skrót płatności z `redirectUrl`.
2. Przekieruj klienta na checkout.
3. Na stronie `returnUrl` pokaż komunikat „przetwarzamy płatność" - bez realizacji zamówienia.
4. Poczekaj na webhook, [zweryfikuj jego podpis](/webhook-signature) i zrealizuj zamówienie idempotentnie.
5. Jeśli po powrocie klienta webhook jeszcze nie dotarł, możesz odpytać o status, żeby od razu pokazać właściwy komunikat.

## Zwroty

Zwroty realizuje Paymove - **nie ma publicznego endpointu API do ich wykonywania**. Jeśli potrzebujesz zwrócić płatność, skontaktuj się z [integration@paymove.io](mailto:integration@paymove.io). Po wykonaniu zwrotu płatność przyjmuje status `REFUNDED`.

## Co dalej

<CardGroup cols={2}>
  <Card title="Weryfikacja podpisu" icon="shield-check" href="/webhook-signature">
    Obowiązkowy krok przed realizacją zamówienia.
  </Card>

  <Card title="Konfiguracja webhooka" icon="webhook" href="/webhooks">
    Rejestracja webhooka i przypisanie go do produktu.
  </Card>
</CardGroup>
