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

# Bramka płatnicza: podstawowa integracja

> Od zera do działającego checkoutu: utwórz sklep, wygeneruj płatność i przekieruj klienta do bramki Paymove.

Podstawowy scenariusz integracji z bramką płatniczą Paymove składa się z trzech kroków: **jednorazowo** tworzysz produkt (reprezentujący Twój sklep) i konfigurujesz webhook, a następnie **dla każdego zamówienia** tworzysz płatność i przekierowujesz klienta na otrzymany `redirectUrl`.

Wszystkie żądania autoryzujesz kluczem API przekazywanym w nagłówku `X-API-KEY`.

<Info>
  Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. Klucz API i `partnerId` otrzymasz od Paymove.
</Info>

## Krok 1: Utworzenie produktu (sklepu)

Produkt reprezentuje Twój sklep w systemie Paymove - wszystkie płatności tworzysz w jego ramach. Tworzysz go **tylko raz**, przy starcie integracji.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request POST \
  --url https://gateway-api.sandbox.paymove.io/api/product/pay \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --data '{
    "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2",
    "productType": "PAY",
    "name": "Sklep Testowy",
    "shortName": "SHOP1",
    "location": "Warszawa",
    "timezone": "Europe/Warsaw",
    "productMetadata": { "locale": "pl-PL" }
  }'
```

**Odpowiedź (200):**

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "891412c8-8717-4449-9543-e34112bec470",
  "name": "Sklep Testowy",
  "location": "Warszawa",
  "partner": {
    "id": "78562c79-2f5c-4415-8af4-c871eea92ef2",
    "name": "Nazwa Partnera Sp. z o.o.",
    "productTypes": ["PAY"]
  },
  "timezone": "Europe/Warsaw",
  "shortName": "SHOP1",
  "emailEnabled": true,
  "smsEnabled": false,
  "fee": {
    "id": "01f9754e-78e3-4b2c-8186-d6862598a95a",
    "minimum": 30,
    "amount": 5,
    "fixed": false
  },
  "productType": "PAY",
  "status": "ACTIVE",
  "creator": "PAYMOVE",
  "reviewEnabled": false,
  "createdAt": 1783246791.745352526,
  "updatedAt": 1783246791.745352526
}
```

Pole `id` z odpowiedzi to **`productId`** - identyfikator Twojego sklepu używany w kolejnych krokach.

## Krok 2: Konfiguracja webhooka

Webhook powiadamia Twój system o zakończonej płatności. Najpierw go rejestrujesz, potem przypisujesz do produktu. W `requestTemplate` możesz użyć zmiennych `{{externalId}}` (Twój orderId) i `{{price}}` (kwota) - Paymove podstawi je przy wysyłce.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request POST \
  --url https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --data '{
    "name": "OrderPaymentHook",
    "endpoint": "https://merchant-shop.com/api/payments/webhook",
    "method": "POST",
    "requestTemplate": {
      "orderId": "{{externalId}}",
      "price": "{{price}}"
    },
    "responseTemplate": { "status": "ok" },
    "expectedCode": 200,
    "expectedResponse": "{ \"status\": \"ok\" }",
    "retries": 3,
    "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2",
    "type": "PAYMENT",
    "headers": { "Content-Type": ["application/json"] }
  }'
```

**Odpowiedź (200):** obiekt webhooka z nadanym `id` (`webhookId`) oraz polem `signingSecret`:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "6b23ecd9-14c8-47fc-add0-b71ec50e9d66",
  "name": "OrderPaymentHook",
  "endpoint": "https://merchant-shop.com/api/payments/webhook",
  "method": "POST",
  "requestTemplate": {
    "orderId": "{{externalId}}",
    "price": "{{price}}"
  },
  "responseTemplate": { "status": "ok" },
  "expectedCode": 200,
  "expectedResponse": "{ \"status\": \"ok\" }",
  "retries": 3,
  "type": "PAYMENT",
  "headers": { "Content-Type": ["application/json"] },
  "signingSecret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

<Warning>
  `signingSecret` służy do weryfikacji podpisu żądań przychodzących od Paymove. Zapisz go bezpiecznie po stronie swojego systemu.
</Warning>

Następnie przypisz webhook do produktu (podmień `webhookId` i `productId`):

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request POST \
  --url https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook/6b23ecd9-14c8-47fc-add0-b71ec50e9d66/products/891412c8-8717-4449-9543-e34112bec470 \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
```

**Odpowiedź (200):** obiekt webhooka potwierdzający powiązanie.

## Krok 3: Utworzenie płatności

Dla każdego zamówienia tworzysz płatność w ramach produktu. W URL podmień `productId` z kroku 1.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request POST \
  --url https://gateway-api.sandbox.paymove.io/api/pay/product/891412c8-8717-4449-9543-e34112bec470/subproduct/pricing \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --data '{
    "price": 1000,
    "externalId": "order-123",
    "details": {
      "returnUrl": "https://merchant-shop.com/payment/success",
      "productName": "Koszulka sportowa",
      "customerId": "user-567",
      "email": "klient@example.com",
      "locale": "pl-PL",
      "triggerPayment": "BLIK"
    }
  }'
```

| Pole                     | Wymagane | Opis                                                                                                                                                                                                                                                  |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `price`                  | Tak      | Kwota w groszach jako **liczba całkowita** (np. `1000` = 10,00 PLN). Waluta to zawsze PLN.                                                                                                                                                            |
| `externalId`             | Tak      | Unikalny identyfikator zamówienia w Twoim systemie.                                                                                                                                                                                                   |
| `details.returnUrl`      | Tak      | URL powrotu klienta po płatności.                                                                                                                                                                                                                     |
| `details.productName`    | Nie      | Nazwa produktu widoczna na checkoucie.                                                                                                                                                                                                                |
| `details.customerId`     | Nie      | Dowolna wartość przekazywana na wylot - checkout jej nie wyświetla.                                                                                                                                                                                   |
| `details.email`          | Nie      | Adres e-mail klienta, uzupełniany na checkoucie.                                                                                                                                                                                                      |
| `details.locale`         | Nie      | Nadpisuje locale z głównego produktu (np. `pl-PL`).                                                                                                                                                                                                   |
| `details.triggerPayment` | Nie      | Metoda wybrana z góry w checkoucie -  `BLIK`, `GPAY`, `APAY`, `TRANSFER`, `PAY_BY_LINK` lub `PAYPO`. Automatycznie startują wyłącznie `GPAY` i `APAY` (i tylko z `details.email`); pozostałe są jedynie zaznaczone. Szczegóły: [REST API](/rest-api). |
| `details.qrStepEnabled`  | Nie      | `true` sprawia, że na desktopie checkout zaczyna od kodu QR, a klient kończy płatność na telefonie. Szczegóły: [REST API](/rest-api).                                                                                                                 |
| `details.autoclose`      | Nie      | Po ilu ms od udanej płatności checkout sam wraca na `returnUrl`, z odliczaniem w przycisku „Wróć do sklepu". Szczegóły: [REST API](/rest-api).                                                                                                        |

<Warning>
  `price` musi być liczbą całkowitą. Wartość z częścią dziesiętną (np. `12.99`) zostanie **po cichu obcięta do 12 groszy**, a API i tak zwróci `200`. Przeliczaj złotówki przez `Math.round(kwota * 100)`.
</Warning>

**Odpowiedź (200):**

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "redirectUrl": "https://checkout.sandbox.paymove.io/891412c8-8717-4449-9543-e34112bec470?externalId=ec6RtwTZKb"
}
```

<Info>
  Parametr `externalId` w `redirectUrl` (tutaj `ec6RtwTZKb`) to **wygenerowany przez Paymove 10-znakowy skrót płatności**, a nie Twój `externalId` (`order-123`). Zapisz go u siebie - to nim posługujesz się przy sprawdzaniu statusu płatności.
</Info>

## Krok 4: Przekierowanie klienta

Przekieruj klienta na otrzymany `redirectUrl`:

```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
window.location.href = data.redirectUrl;
```

Klient zobaczy stronę checkoutu Paymove z kwotą, nazwą Twojego sklepu i produktu oraz metodami płatności (BLIK, Google Pay, Apple Pay, przelew). Po zakończonej płatności:

1. Paymove wyśle webhook na Twój `endpoint` - domyślnie tylko dla statusu `COMPLETED`.
2. Twój system **weryfikuje nagłówek `X-Paymove-Signature`** ([jak to zrobić](/webhook-signature)), odpowiada `200` i `{ "status": "ok" }`, po czym realizuje zamówienie.
3. Klient widzi ekran potwierdzenia i może wrócić do sklepu pod adres `returnUrl`.

<Warning>
  Nie realizuj zamówienia w obsłudze `returnUrl`. Przekierowanie jest sterowane przez przeglądarkę klienta i można je wywołać bez opłacenia płatności. Jedynym wiarygodnym potwierdzeniem jest zweryfikowany webhook.
</Warning>

## Co dalej?

<CardGroup cols={2}>
  <Card title="Bramka płatnicza: webhooki" icon="webhook" href="/products/payment-gateway/tutorial-webhook">
    Rozbuduj integrację o webhooki i reaguj na każdą opłaconą transakcję.
  </Card>

  <Card title="SDK JavaScript" icon="js" href="/sdk/javascript">
    Gotowy klient do integracji w Node.js.
  </Card>
</CardGroup>
