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

# Utwórz płatność

> Tworzy płatność w ramach głównego produktu. Każde wywołanie tworzy płatność, którą klient może opłacić poprzez otrzymany redirectUrl. Po utworzeniu płatności API zwraca redirectUrl do checkoutu, na którym klient może sfinalizować płatność.



## OpenAPI

````yaml /openapi.yaml post /api/pay/product/{productId}/subproduct/pricing
openapi: 3.1.0
info:
  title: Paymove Payment Gateway API
  version: 1.0.0
  description: >
    API bramki płatniczej Paymove umożliwia merchantom tworzenie płatności
    online

    oraz zarządzanie webhookami i produktami.


    ## Zanim zaczniesz — sześć rzeczy, które trzeba wiedzieć


    1. **Kwoty to liczby całkowite w groszach.** `1000` = 10,00 PLN. Wartość z
    częścią
       dziesiętną (np. `12.99`) zostanie po cichu obcięta do `12` groszy, a API zwróci `200`.
       Przeliczaj przez `Math.round(kwota * 100)`.
    2. **Bramka rozlicza wyłącznie w PLN.** W żądaniu nie ma pola waluty.

    3. **Autoryzacja nagłówkiem `X-API-KEY`**, nigdy `Authorization: Bearer`.
    Wywołuj API
       wyłącznie po stronie serwera — żądanie z przeglądarki zostanie odrzucone.
    4. **Nieznane pola w body są po cichu ignorowane**, a API zwraca `200`. Zły
    kształt
       żądania wygląda więc jak sukces — trzymaj się dokładnie udokumentowanej struktury.
    5. **Brak `externalId` kończy się kodem `500`**, a nie `400`. Brak `price`
    albo
       `details.returnUrl` nie zgłasza natomiast żadnego błędu — dostajesz `200`.
    6. **Błędy mają kształt `{"status": <int>, "message": "<tekst>"}`.**
    Rozgałęziaj logikę
       po statusie HTTP — treść `message` bywa niestabilna i nie należy jej parsować.

    Nie istnieją: limity zapytań, kod `429`, kod `422`, nagłówek
    `Idempotency-Key`

    ani wersjonowanie API.
  contact:
    name: Paymove Integration Team
    email: integration@paymove.io
    url: https://paymove.io
servers:
  - url: https://gateway-api.sandbox.paymove.io
    description: Środowisko Sandbox (testowe)
  - url: https://api.paymove.io
    description: Środowisko produkcyjne
security:
  - ApiKeyAuth: []
tags:
  - name: Płatności
    description: Tworzenie płatności
  - name: Produkty
    description: Zarządzanie produktami (merchantami)
  - name: Webhooki
    description: Rejestracja i przypisywanie webhooków
paths:
  /api/pay/product/{productId}/subproduct/pricing:
    post:
      tags:
        - Płatności
      summary: Utwórz płatność
      description: >-
        Tworzy płatność w ramach głównego produktu. Każde wywołanie tworzy
        płatność, którą klient może opłacić poprzez otrzymany redirectUrl. Po
        utworzeniu płatności API zwraca redirectUrl do checkoutu, na którym
        klient może sfinalizować płatność.
      operationId: createPayment
      parameters:
        - name: productId
          in: path
          required: true
          description: UUID produktu (sklepu) otrzymany przy jego tworzeniu
          schema:
            type: string
            format: uuid
            example: 891412c8-8717-4449-9543-e34112bec470
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
            example:
              price: 1000
              externalId: order-123
              details:
                returnUrl: https://merchant-shop.com/payment/success
                productName: Koszulka sportowa
                email: klient@example.com
                locale: pl-PL
                triggerPayment: BLIK
                qrStepEnabled: true
      responses:
        '200':
          description: Płatność utworzona pomyślnie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePaymentResponse'
              example:
                redirectUrl: >-
                  https://checkout.sandbox.paymove.io/891412c8-8717-4449-9543-e34112bec470?externalId=ec6RtwTZKb
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            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",
                  "email": "klient@example.com",
                  "locale": "pl-PL",
                  "triggerPayment": "BLIK",
                  "qrStepEnabled": true
                }
              }'
        - lang: JavaScript
          label: Node.js (SDK)
          source: |
            import { PaymoveClient } from "@paymove-io/sdk";

            const client = new PaymoveClient({
              apiKey: process.env.PAYMOVE_API_KEY,
              productId: "891412c8-8717-4449-9543-e34112bec470",
              environment: "sandbox",
            });

            const response = await client.createPayment({
              amount: Math.round(10.0 * 100), // liczba całkowita w groszach
              currency: "PLN", // wymagane przez SDK, ignorowane przez API
              externalId: "order-123",
              returnUrl: "https://merchant-shop.com/payment/success",
              productName: "Koszulka sportowa",
              email: "klient@example.com",
              locale: "pl-PL",
              triggerPayment: "BLIK",
              qrStepEnabled: true,
            });

            console.log(response.redirectUrl);
        - lang: JavaScript
          label: Node.js (fetch)
          source: >
            const url =
            `https://gateway-api.sandbox.paymove.io/api/pay/product/${productId}/subproduct/pricing`;


            const body = {
              price: Math.round(10.0 * 100), // liczba całkowita w groszach
              externalId: "order-123",
              details: {
                returnUrl: "https://merchant-shop.com/payment/success",
                productName: "Koszulka sportowa",
                email: "klient@example.com",
                locale: "pl-PL",
                triggerPayment: "BLIK",
                qrStepEnabled: true,
              },
            };


            const response = await fetch(url, {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-API-KEY": apiKey,
              },
              body: JSON.stringify(body),
            });


            const data = await response.json();

            console.log(data.redirectUrl);
components:
  schemas:
    CreatePaymentRequest:
      type: object
      required:
        - price
        - externalId
        - details
      properties:
        price:
          type: integer
          format: int64
          description: >-
            Kwota w groszach jako liczba całkowita (1000 = 10,00 PLN). Bramka
            rozlicza wyłącznie w PLN. UWAGA: 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 przez Math.round(kwota * 100).
          example: 1000
        externalId:
          type: string
          description: >-
            Unikalny identyfikator płatności po stronie merchanta (orderId).
            Ponowne użycie tej samej wartości nie tworzy nowej płatności — API
            zwraca 200 z redirectUrl płatności utworzonej wcześniej, a nowa cena
            i pozostałe pola są odrzucane.
          example: order-123
        details:
          type: object
          description: >-
            Swobodny obiekt danych płatności. Możesz przekazać własne pola —
            zostaną zachowane, ale checkout odczytuje wyłącznie: returnUrl,
            redirectUrl, productName, email, locale, orderId, recipient,
            triggerPayment i qrStepEnabled.
          required:
            - returnUrl
          properties:
            returnUrl:
              type: string
              format: uri
              description: URL powrotu klienta po płatności
              example: https://merchant-shop.com/payment/success
            productName:
              type: string
              description: Nazwa produktu widoczna na checkoucie
              example: Koszulka sportowa
            recipient:
              type: string
              description: Opcjonalnie — nazwa odbiorcy wyświetlana na checkoucie
              example: Merchant Shop Sp. z o.o.
            email:
              type: string
              format: email
              description: Opcjonalnie — adres e-mail klienta, uzupełniany na checkoucie
              example: klient@example.com
            locale:
              type: string
              description: Opcjonalnie — nadpisuje locale z głównego produktu
              example: pl-PL
            triggerPayment:
              type:
                - string
                - 'null'
              enum:
                - BLIK
                - GPAY
                - APAY
                - TRANSFER
                - PAY_BY_LINK
                - PAYPO
              description: >-
                Opcjonalnie — metoda płatności uruchamiana automatycznie zaraz
                po otwarciu checkoutu, tak jakby klient kliknął przycisk Zapłać.
                Wymaga podanego details.email, w przeciwnym razie metoda
                zostanie jedynie zaznaczona. Działa tylko przy pierwszym
                wyświetleniu checkoutu. Nieznana wartość jest pomijana.
              example: BLIK
            qrStepEnabled:
              type: boolean
              description: >-
                Opcjonalnie — na desktopie checkout zaczyna od ekranu z kodem
                QR, który klient skanuje telefonem, żeby dokończyć płatność na
                nim. Pominięcie pola albo false oznacza przejście od razu do
                formularza płatności. Na telefonie oraz przy ustawionym
                details.triggerPayment krok jest pomijany niezależnie od tej
                wartości.
              example: true
            autoclose:
              type: integer
              format: int64
              minimum: 1
              description: >-
                Opcjonalnie — po ilu milisekundach od udanej płatności checkout
                sam wraca na details.returnUrl. Pozostały czas jest odliczany w
                przycisku Wróć do sklepu, a dowolna interakcja klienta anuluje
                odliczanie. W widgecie zamiast przekierowania zamyka się modal i
                wywoływany jest onComplete. Pominięcie pola oznacza brak
                auto-powrotu — potwierdzenie zostaje na ekranie.
              example: 10000
    CreatePaymentResponse:
      type: object
      properties:
        redirectUrl:
          type: string
          format: uri
          description: >-
            Adres checkoutu — przekieruj klienta pod ten URL w celu
            sfinalizowania płatności. Parametr externalId w tym adresie to
            wygenerowany przez Paymove 10-znakowy skrót płatności, a NIE
            externalId przekazany w żądaniu. To jego używasz przy sprawdzaniu
            statusu płatności.
    ApiErrorResponse:
      type: object
      description: >-
        Jednolity kształt odpowiedzi błędu. Rozgałęziaj logikę po statusie HTTP
        — treść pola message bywa niestabilna i może zawierać wewnętrzne nazwy
        klas.
      properties:
        status:
          type: integer
          description: Kod statusu HTTP, powielony w treści odpowiedzi
          example: 401
        message:
          type: string
          description: Opis błędu przeznaczony dla człowieka — nie parsuj go w kodzie
          example: Invalid API key
  responses:
    BadRequest:
      description: >-
        Żądanie odrzucone. Najczęstsza przyczyna to niepoprawny JSON lub
        nieprawidłowa wartość pola typu wyliczeniowego.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          example:
            status: 400
            message: Malformed JSON request. Please check your request body.
    Unauthorized:
      description: Brak klucza API lub klucz nieprawidłowy, wygasły albo odwołany.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            missingCredentials:
              summary: Brak nagłówka X-API-KEY
              value:
                status: 401
                message: Missing credentials
            invalidApiKey:
              summary: Klucz nieprawidłowy
              value:
                status: 401
                message: Invalid API key
    Forbidden:
      description: Klucz jest poprawny, ale wskazany zasób należy do innego partnera.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          example:
            status: 403
            message: >-
              Product <891412c8-8717-4449-9543-e34112bec470> does not belong to
              you
    NotFound:
      description: >-
        Zasób o podanym identyfikatorze nie istnieje. Treść message zawiera
        wewnętrzną nazwę encji — nie opieraj na niej logiki.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          example:
            status: 404
            message: PayProductEntity not found
    ServerError:
      description: >-
        Błąd po stronie Paymove. Uwaga: ten kod zwracany jest także wtedy, gdy w
        żądaniu zabrakło `externalId` albo gdy identyfikator w ścieżce nie jest
        poprawnym UUID — zanim ponowisz wywołanie, sprawdź kompletność body i
        poprawność ścieżki.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          example:
            status: 500
            message: Something went wrong
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: >
        Klucz API generowany w [Panelu Paymove](https://panel.paymove.io).


        Format: 51 znaków z prefiksem środowiska — `sk_test_` w sandboxie,

        `sk_live_` na produkcji.

        Przykład: `sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`


        Klucz przekazuj wyłącznie z serwera. Żądanie wysłane ze strony sklepu
        zostanie

        odrzucone — a klucz i tak nigdy nie może trafić do kodu frontendowego.

````