> ## 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. Registering a webhook requires both `retries` and `expectedCode` - omitting either returns HTTP 500, and `retries: 0` means no retry at all. 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. If the merchant has no `productId` yet, do not stop at a placeholder: ask for the API key, the `partnerId` and the shop name, propose defaults for `location` and `timezone` (`Europe/Warsaw`), offer the optional fields once, then create the product for them with `POST /api/product/pay` and take `productId` from `id` in the response. Do the same for the webhook: register it, then assign it to the product. Guided procedure: https://docs.paymove.io/skill.md (Steps 1-2).
> 15. 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

# Zwróć płatność

> Zwraca płatność w całości. Kod `200` oznacza, że Paymove **przyjął zwrot do realizacji** -
nie że pieniądze już wróciły do klienta; typową odpowiedzią jest `status: PENDING`. Stan
końcowy sprawdzaj przez `GET` pod tym samym adresem.

Wymagania i ograniczenia:

- **Zwroty przez API nie są dostępne dla każdego partnera** - to funkcja włączana
  indywidualnie, po uprzednim zgłoszeniu do Paymove (integration@paymove.io). Dopóki nie
  jest aktywna, odpowiedzią jest `403`. Klucz API musi mieć uprawnienie do zapisu.
- **Zwroty częściowe nie są obsługiwane.** `amountMinor` można pominąć; jeśli je podasz,
  musi być równe pełnej kwocie płatności.
- **Jeden zwrot na płatność.** Istniejący zwrot w stanie `PENDING` lub `COMPLETED` blokuje
  kolejne próby.
- Zwrócić można wyłącznie płatność ze statusem `COMPLETED`. Udany zwrot ustawia status
  płatności na `REFUNDED`.
- **Nie ma klucza idempotencji** - przed ponowieniem żądania sprawdź listę zwrotów.
- Żądanie **nie zawiera `productId`** - produkt i dane rozliczeniowe Paymove odczytuje
  z samej płatności.

Pełny opis: [Zwroty](/refunds).




## OpenAPI

````yaml /openapi.yaml post /api/pay/payment/{orderId}/refund
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: Zwroty
    description: Zwrot płatności i lista zwrotów
  - name: Produkty
    description: Zarządzanie produktami (merchantami)
  - name: Webhooki
    description: Rejestracja i przypisywanie webhooków
paths:
  /api/pay/payment/{orderId}/refund:
    post:
      tags:
        - Zwroty
      summary: Zwróć płatność
      description: >
        Zwraca płatność w całości. Kod `200` oznacza, że Paymove **przyjął zwrot
        do realizacji** -

        nie że pieniądze już wróciły do klienta; typową odpowiedzią jest
        `status: PENDING`. Stan

        końcowy sprawdzaj przez `GET` pod tym samym adresem.


        Wymagania i ograniczenia:


        - **Zwroty przez API nie są dostępne dla każdego partnera** - to funkcja
        włączana
          indywidualnie, po uprzednim zgłoszeniu do Paymove (integration@paymove.io). Dopóki nie
          jest aktywna, odpowiedzią jest `403`. Klucz API musi mieć uprawnienie do zapisu.
        - **Zwroty częściowe nie są obsługiwane.** `amountMinor` można pominąć;
        jeśli je podasz,
          musi być równe pełnej kwocie płatności.
        - **Jeden zwrot na płatność.** Istniejący zwrot w stanie `PENDING` lub
        `COMPLETED` blokuje
          kolejne próby.
        - Zwrócić można wyłącznie płatność ze statusem `COMPLETED`. Udany zwrot
        ustawia status
          płatności na `REFUNDED`.
        - **Nie ma klucza idempotencji** - przed ponowieniem żądania sprawdź
        listę zwrotów.

        - Żądanie **nie zawiera `productId`** - produkt i dane rozliczeniowe
        Paymove odczytuje
          z samej płatności.

        Pełny opis: [Zwroty](/refunds).
      operationId: createRefund
      parameters:
        - name: orderId
          in: path
          required: true
          description: >-
            Wewnętrzny identyfikator zamówienia nadany przez Paymove - pole
            orderId payloadu webhooka albo odpowiedzi zapytania o status. Nie
            jest to externalId ani skrót płatności z redirectUrl
          schema:
            type: string
            example: PAY1784798914400
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRefundRequest'
            examples:
              pelnaKwota:
                summary: Zwrot całej kwoty (puste body)
                value: {}
              zPowodem:
                summary: Zwrot z kwotą, e-mailem i powodem
                value:
                  amountMinor: 15000
                  email: klient@example.com
                  reason: Klient zrezygnował z zamówienia
      responses:
        '200':
          description: Zwrot przyjęty do realizacji
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '400':
          description: >-
            Płatność nieukończona; amountMinor różne od pełnej kwoty; płatność
            ma już zwrot; płatność bez przypisanego produktu; płatności nie da
            się zwrócić przez API; zwrot odrzucony. Odpowiedź przy odrzuconym
            zwrocie nie ma treści, a sama próba jest widoczna na liście zwrotów
            ze statusem ERROR
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            Zwroty przez API nie są włączone dla konta, płatność należy do
            innego partnera albo płatność nie istnieje. Nieistniejąca płatność
            zwraca 403, a nie 404 - celowo, by nie potwierdzać, które
            identyfikatory zamówień istnieją
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
              example:
                status: 403
                message: Access denied
        '404':
          description: >-
            Niespójność danych po stronie Paymove - płatność istnieje, ale
            brakuje jej rekordu źródłowego. Zgłoś na integration@paymove.io
        '500':
          $ref: '#/components/responses/ServerError'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |
            curl --request POST \
              --url https://gateway-api.sandbox.paymove.io/api/pay/payment/PAY1784798914400/refund \
              --header 'Content-Type: application/json' \
              --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
              --data '{"amountMinor": 15000, "reason": "Klient zrezygnował z zamówienia"}'
components:
  schemas:
    CreateRefundRequest:
      type: object
      description: >-
        Wszystkie pola są opcjonalne - puste body {} zwraca pełną kwotę
        płatności. Żądanie nie zawiera productId; produkt i dane rozliczeniowe
        Paymove odczytuje z płatności.
      properties:
        amountMinor:
          type: integer
          format: int64
          minimum: 1
          description: >-
            Opcjonalnie - kwota zwrotu w groszach jako liczba całkowita. Musi
            być równa pełnej kwocie płatności: zwroty częściowe nie są
            obsługiwane, a inna wartość kończy się kodem 400. Pominięcie pola
            zwraca całą kwotę.
          example: 15000
        email:
          type: string
          format: email
          description: >-
            Opcjonalnie - adres e-mail płatnika, używany przy autoryzacji
            poprzedzającej zwrot. Bez niego wstawiana jest wartość zastępcza;
            płatności przyjęte bez adresu e-mail zwracają się poprawnie i bez
            tego pola.
          example: klient@example.com
        reason:
          type: string
          description: >-
            Opcjonalnie - dowolny tekst zapisywany przy zwrocie i zwracany w
            polu comment
          example: Klient zrezygnował z zamówienia
    Refund:
      type: object
      description: >-
        Obiekt zwrotu. POST zwraca jeden taki obiekt, GET tablicę obiektów tego
        samego typu.
      properties:
        id:
          type: string
          format: uuid
          description: Identyfikator zwrotu w Paymove
          example: 9f2c1b44-6d2f-4f0e-9a71-2b8c5d3e1a90
        type:
          type: string
          enum:
            - REFUND
          description: Zawsze REFUND
        orderId:
          type: string
          description: >-
            Identyfikator SAMEGO ZWROTU w postaci {orderId płatności}-rN.
            Identyfikator płatności znajdziesz w parentOrderId.
          example: PAY1784798914400-r1
        parentOrderId:
          type: string
          description: orderId zwracanej płatności - ten sam, który podałeś w ścieżce
          example: PAY1784798914400
        amountMinor:
          type: integer
          format: int64
          description: >-
            Zwrócona kwota w groszach. Przy tworzeniu płatności to samo pole
            nazywa się price
          example: 15000
        status:
          type: string
          enum:
            - INITIALIZED
            - PENDING
            - WAITING_FOR_EXTERNAL_ACTION
            - COMPLETED
            - CANCELED
            - ERROR
          description: >-
            Status zwrotu. Jedyny status oznaczający sukces to COMPLETED;
            CANCELED pisane przez jedno l. Zwrot osiąga stan końcowy zwykle w
            ciągu kilkudziesięciu sekund.
          example: PENDING
        paymentMethod:
          type: string
          description: Metoda płatności, którą zapłacono zwracaną płatność
          example: BLIK
        partnerId:
          type: string
          format: uuid
          description: Identyfikator partnera, do którego należy płatność
          example: 3a7d1f52-8b64-4c19-9e03-7d2a5f8c1b44
        productId:
          type: string
          format: uuid
          description: UUID produktu, w ramach którego powstała płatność
          example: 891412c8-8717-4449-9543-e34112bec470
        locale:
          type: string
          description: Locale płatności
          example: pl
        comment:
          type: string
          description: Tekst przekazany w polu reason żądania
          example: Klient zrezygnował z zamówienia
        createdAt:
          type: string
          format: date-time
          description: Czas utworzenia zwrotu (ISO 8601, UTC)
          example: '2026-10-06T11:42:03Z'
        updatedAt:
          type: string
          format: date-time
          description: Czas ostatniej zmiany statusu zwrotu (ISO 8601, UTC)
          example: '2026-10-06T11:42:04Z'
    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:
    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
    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.

````

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