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:
      operationId: createPayment
      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ść."
      tags:
        - Płatności
      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);

  /api/product/pay:
    post:
      operationId: createProduct
      summary: Utwórz produkt (sklep)
      description: |
        Tworzy produkt reprezentujący Twój sklep w systemie Paymove.
        Wszystkie płatności są tworzone w ramach tego produktu. Produkt tworzysz jednorazowo, przy starcie integracji.
        Pole `id` z odpowiedzi to `productId` używany w pozostałych wywołaniach.
      tags:
        - Produkty
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProductRequest'
            example:
              partnerId: "78562c79-2f5c-4415-8af4-c871eea92ef2"
              productType: "PAY"
              name: "Sklep Testowy"
              shortName: "SHOP1"
              location: "Warszawa"
              timezone: "Europe/Warsaw"
              productMetadata:
                locale: "pl-PL"
      responses:
        '200':
          description: Produkt utworzony — zwraca pełny obiekt produktu
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
              example:
                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
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            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" }
              }'

  /api/pay/plugin/webhook:
    post:
      operationId: registerWebhook
      summary: Zarejestruj webhook
      description: |
        Rejestruje webhook — adres URL po Twojej stronie, który Paymove wywołuje po każdej zakończonej płatności.
        W `requestTemplate` możesz użyć zmiennych `{{externalId}}` (orderId merchanta) i `{{price}}` (kwota).
        Po rejestracji webhook musi zostać przypisany do produktu osobnym wywołaniem.
      tags:
        - Webhooki
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterWebhookRequest'
            example:
              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"
      responses:
        '200':
          description: Webhook zarejestrowany — odpowiedź zawiera `id` (webhookId) oraz `signingSecret`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
              example:
                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"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            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"] }
              }'

  /api/pay/plugin/webhook/{webhookId}/products/{productId}:
    post:
      operationId: assignWebhookToProduct
      summary: Przypisz webhook do produktu
      description: |
        Powiązuje zarejestrowany webhook z produktem. Dzięki temu każde zdarzenie płatności
        dotyczące tego produktu automatycznie trafia pod wskazany URL webhooka.
      tags:
        - Webhooki
      parameters:
        - name: webhookId
          in: path
          required: true
          description: Identyfikator zarejestrowanego webhooka
          schema:
            type: string
            format: uuid
            example: "6b23ecd9-14c8-47fc-add0-b71ec50e9d66"
        - name: productId
          in: path
          required: true
          description: UUID produktu (sklepu)
          schema:
            type: string
            format: uuid
            example: "891412c8-8717-4449-9543-e34112bec470"
      responses:
        '200':
          description: Webhook przypisany — zwraca obiekt webhooka potwierdzający powiązanie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
              example:
                id: "6b23ecd9-14c8-47fc-add0-b71ec50e9d66"
                name: "OrderPaymentHook"
                endpoint: "https://merchant-shop.com/api/payments/webhook"
                method: "POST"
                expectedCode: 200
                retries: 3
                type: "PAYMENT"
                signingSecret: "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        '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/plugin/webhook/6b23ecd9-14c8-47fc-add0-b71ec50e9d66/products/891412c8-8717-4449-9543-e34112bec470 \
              --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

  /api/pay/plugin/webhook/{webhookId}/products:
    get:
      operationId: listWebhookProducts
      summary: Lista produktów webhooka
      description: Zwraca listę produktów powiązanych z webhookiem — przydatne do weryfikacji konfiguracji.
      tags:
        - Webhooki
      parameters:
        - name: webhookId
          in: path
          required: true
          description: Identyfikator webhooka
          schema:
            type: string
            format: uuid
            example: "6b23ecd9-14c8-47fc-add0-b71ec50e9d66"
      responses:
        '200':
          description: Tablica produktów powiązanych z webhookiem
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductSummary'
              example:
                - id: "891412c8-8717-4449-9543-e34112bec470"
                  name: "Sklep Testowy"
                  productType: "PAY"
                  status: "ACTIVE"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl --request GET \
              --url https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook/6b23ecd9-14c8-47fc-add0-b71ec50e9d66/products \
              --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

  /api/pay/product/{productId}/subproduct/{externalId}:
    patch:
      operationId: updatePaymentPrice
      summary: Zmień kwotę płatności
      description: |
        Aktualizuje kwotę istniejącej płatności. Wywołanie zmienia wyłącznie pole `price` —
        pozostałe pola żądania są pomijane.

        Użyj tego wywołania zamiast ponownego tworzenia płatności z tym samym `externalId`:
        powtórzone tworzenie zwraca `200` z pierwotnym `redirectUrl` i po cichu odrzuca nową kwotę.
      tags:
        - Płatności
      parameters:
        - name: productId
          in: path
          required: true
          description: UUID produktu (sklepu)
          schema:
            type: string
            format: uuid
            example: "891412c8-8717-4449-9543-e34112bec470"
        - name: externalId
          in: path
          required: true
          description: >-
            Identyfikator płatności. Akceptowany jest zarówno `externalId` przekazany przy
            tworzeniu płatności, jak i 10-znakowy skrót z `redirectUrl`.
          schema:
            type: string
            example: "order-123"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - price
              properties:
                price:
                  type: integer
                  format: int64
                  description: Nowa kwota w groszach jako liczba całkowita
                  example: 1500
            example:
              price: 1500
      responses:
        '200':
          description: >-
            Kwota zaktualizowana. Pola `description` i `details` pojawiają się w odpowiedzi
            tylko wtedy, gdy mają wartość.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: Identyfikator pozycji cennika, nie płatności
                  price:
                    type: integer
                    format: int64
                    description: Aktualna kwota w groszach
                  userDefinedAmount:
                    type: boolean
                    description: Czy kwotę ustala klient na checkoucie
                  description:
                    type: string
                  details:
                    type: object
                    additionalProperties: true
              example:
                id: "01f9754e-78e3-4b2c-8186-d6862598a95a"
                price: 1500
                userDefinedAmount: false
        '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 PATCH \
              --url https://gateway-api.sandbox.paymove.io/api/pay/product/891412c8-8717-4449-9543-e34112bec470/subproduct/order-123 \
              --header 'Content-Type: application/json' \
              --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
              --data '{ "price": 1500 }'

  /api/payment/product/{productId}/subproduct/{paymentHash}/status:
    get:
      operationId: getPaymentStatus
      summary: Sprawdź status płatności
      description: |
        Zwraca aktualny status płatności. Przydatne jako uzupełnienie webhooków — na przykład
        gdy klient wrócił na `returnUrl`, a powiadomienie jeszcze nie dotarło.

        **Uwaga: ten endpoint jest obsługiwany przez inny host niż pozostałe wywołania.**
        Sandbox: `https://pay-api.sandbox.paymove.io`, produkcja: `https://pay-api.paymove.io`.
        Ta ścieżka nie jest routowana przez `gateway-api.sandbox.paymove.io` ani
        `api.paymove.io` — wywołanie jej tam kończy się kodem 404 i odpowiedzią
        `text/plain` o treści `No route found for: GET …`, czyli nawet nie w formacie
        `{status, message}`.

        Endpoint nie wymaga klucza API, więc **nie przekazuj do niego danych wrażliwych**
        i nie traktuj samej odpowiedzi jako dowodu płatności w krytycznych przepływach —
        wiarygodnym potwierdzeniem jest zweryfikowany webhook.
      tags:
        - Płatności
      security: []
      servers:
        - url: https://pay-api.sandbox.paymove.io
          description: Sandbox — usługa płatności
        - url: https://pay-api.paymove.io
          description: Produkcja — usługa płatności
      parameters:
        - name: productId
          in: path
          required: true
          description: UUID produktu (sklepu) lub jego `shortName`
          schema:
            type: string
            example: "891412c8-8717-4449-9543-e34112bec470"
        - name: paymentHash
          in: path
          required: true
          description: >-
            10-znakowy skrót płatności z parametru `externalId` w `redirectUrl`.
            To NIE jest `externalId` przekazany przy tworzeniu płatności.
          schema:
            type: string
            example: "ec6RtwTZKb"
      responses:
        '200':
          description: Aktualny status płatności
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - INITIALIZED
                      - PENDING
                      - COMPLETED
                      - CANCELED
                      - ERROR
                      - REFUNDED
                      - WAITING_FOR_EXTERNAL_ACTION
                    description: Status płatności, zawsze jako łańcuch znaków
                  orderId:
                    type: string
                    description: Wewnętrzny identyfikator zamówienia w Paymove
              example:
                status: "COMPLETED"
                orderId: "PAY1784798914400"
        '404':
          description: Nie znaleziono płatności o podanym skrócie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
              example:
                status: 404
                message: "Payments for externalId ec6RtwTZKb not found"
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl --request GET \
              --url https://pay-api.sandbox.paymove.io/api/payment/product/891412c8-8717-4449-9543-e34112bec470/subproduct/ec6RtwTZKb/status

  /api/pay/plugin/webhook/{webhookId}/rotate-secret:
    post:
      operationId: rotateWebhookSecret
      summary: Wymień sekret podpisujący webhooka
      description: |
        Generuje nowy `signingSecret` dla webhooka.

        Poprzedni sekret przestaje działać **natychmiast** — nie ma okresu przejściowego,
        w którym oba byłyby akceptowane. Zaktualizuj konfigurację po swojej stronie w tym
        samym momencie, w przeciwnym razie weryfikacja podpisu zacznie odrzucać powiadomienia.
      tags:
        - Webhooki
      parameters:
        - name: webhookId
          in: path
          required: true
          description: Identyfikator webhooka
          schema:
            type: string
            format: uuid
            example: "6b23ecd9-14c8-47fc-add0-b71ec50e9d66"
      responses:
        '200':
          description: Nowy sekret wygenerowany
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
              example:
                id: "6b23ecd9-14c8-47fc-add0-b71ec50e9d66"
                name: "OrderPaymentHook"
                signingSecret: "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '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/plugin/webhook/6b23ecd9-14c8-47fc-add0-b71ec50e9d66/rotate-secret \
              --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

components:
  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.

  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"

    Conflict:
      description: >-
        Płatność o podanym externalId została już opłacona.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          example:
            status: 409
            message: "Requested resource already purchased"

    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"

  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"

    CreateProductRequest:
      type: object
      required:
        - partnerId
        - productType
        - name
        - location
        - timezone
      properties:
        partnerId:
          type: string
          format: uuid
          description: Identyfikator partnera (nadawany przez Paymove)
        productType:
          type: string
          enum: [PAY]
          description: Typ produktu — dla bramki płatniczej `PAY`
        name:
          type: string
          description: Nazwa produktu (sklepu)
        shortName:
          type: string
          description: Opcjonalnie — krótka nazwa wyświetlana
        location:
          type: string
          description: Lokalizacja (np. miasto)
        timezone:
          type: string
          description: Strefa czasowa IANA
          example: "Europe/Warsaw"
        productMetadata:
          type: object
          properties:
            locale:
              type: string
              example: "pl-PL"

    Product:
      type: object
      description: Pełny obiekt produktu zwracany przez API
      properties:
        id:
          type: string
          format: uuid
          description: Identyfikator produktu (`productId`) używany w pozostałych wywołaniach
        name:
          type: string
        location:
          type: string
        partner:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            productTypes:
              type: array
              items:
                type: string
        timezone:
          type: string
        shortName:
          type: string
        emailEnabled:
          type: boolean
        smsEnabled:
          type: boolean
        fee:
          type: object
          description: Prowizja skonfigurowana dla produktu
          properties:
            id:
              type: string
              format: uuid
            minimum:
              type: integer
            amount:
              type: integer
            fixed:
              type: boolean
        productType:
          type: string
        status:
          type: string
          example: "ACTIVE"
        creator:
          type: string
          example: "PAYMOVE"
        reviewEnabled:
          type: boolean
        createdAt:
          type: number
          description: Znacznik czasu (epoch, sekundy)
        updatedAt:
          type: number
          description: Znacznik czasu (epoch, sekundy)

    ProductSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        productType:
          type: string
        status:
          type: string

    RegisterWebhookRequest:
      type: object
      required:
        - name
        - endpoint
        - method
        - requestTemplate
        - expectedCode
        - retries
        - partnerId
        - type
      properties:
        name:
          type: string
          example: "OrderPaymentHook"
        endpoint:
          type: string
          format: uri
          description: URL, na który będą wysyłane powiadomienia
        method:
          type: string
          enum: [POST]
        requestTemplate:
          type: object
          description: |
            Szablon payloadu webhooka. Zmienne w formacie `{{zmienna}}`:
            `{{externalId}}` — orderId merchanta, `{{price}}` — kwota.
          properties:
            orderId:
              type: string
            price:
              type: string
        responseTemplate:
          type: object
          description: Oczekiwana struktura odpowiedzi od merchanta
        expectedCode:
          type: integer
          description: >-
            Kod HTTP oczekiwany od Twojego serwera. Doręczenie uznaje się za udane tylko wtedy,
            gdy odpowiedź ma DOKŁADNIE ten kod — przy wartości 200 odpowiedź 201 lub 204
            liczy się jako niepowodzenie. Pole jest wymagane; jego pominięcie kończy się kodem 500.
          example: 200
        expectedResponse:
          type: string
          example: '{ "status": "ok" }'
        retries:
          type: integer
          description: >-
            Liczba ponownych prób doręczenia. Wartość domyślna to 0 — bez jawnego ustawienia
            webhook nie jest ponawiany ani razu. Kolejne próby następują po 1 s, 5 s, 5 min,
            1 h, a następnie co 3 h. Pole jest wymagane; jego pominięcie kończy się kodem 500.
          default: 0
          example: 3
        partnerId:
          type: string
          format: uuid
        type:
          type: string
          enum: [PAYMENT]
        headers:
          type: object
          additionalProperties:
            type: array
            items:
              type: string

    Webhook:
      type: object
      description: Obiekt webhooka zwracany przez API
      properties:
        id:
          type: string
          format: uuid
          description: Identyfikator webhooka (`webhookId`)
        name:
          type: string
        endpoint:
          type: string
          format: uri
        method:
          type: string
        requestTemplate:
          type: object
        responseTemplate:
          type: object
        expectedCode:
          type: integer
        expectedResponse:
          type: string
        retries:
          type: integer
        type:
          type: string
        headers:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        signingSecret:
          type: string
          description: Sekret do weryfikacji podpisu żądań przychodzących od Paymove — przechowuj bezpiecznie
