> ## 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": , "message": ""}`. 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 # Integracja z agentami AI Source: https://docs.paymove.io/ai-agents Jak skierować Cursor, Claude Code, Windsurf czy Lovable na dokumentację Paymove, żeby wdrożyły integrację samodzielnie. Dokumentacja Paymove jest przygotowana tak, by mógł z niej korzystać nie tylko człowiek, ale i agent AI piszący kod. Poniżej znajdziesz gotowe sposoby, żeby z tego skorzystać. ## Najszybsza droga Wklej swojemu agentowi ten adres: ``` https://docs.paymove.io/skill.md ``` To wszystko - bez żadnego zdania wyjaśniającego. Pod tym adresem leży kompletna procedura wdrożenia: jakie dane zebrać od Ciebie, jak utworzyć płatność, jak zweryfikować webhook i czego nie robić. Agent pobierze plik i wykona integrację. Działa w Cursorze, Claude Code, Windsurfie, Lovable i wszędzie tam, gdzie agent potrafi pobrać adres URL. ## Trwała konfiguracja w projekcie Jeśli chcesz, żeby agent pamiętał reguły Paymove przy każdym zadaniu, dodaj je raz do swojego repozytorium. Skopiuj zawartość: ``` https://docs.paymove.io/agents.md ``` i wklej do pliku `AGENTS.md`, `CLAUDE.md` albo `.cursor/rules/paymove.md` w swoim projekcie. Od tego momentu wystarczy polecenie w stylu „dodaj płatności" - agent sam sięgnie po właściwe reguły. ## Każda strona jako Markdown Do dowolnego adresu w tej dokumentacji możesz dopisać `.md` i otrzymasz czysty Markdown, bez elementów interfejsu: ``` https://docs.paymove.io/rest-api.md https://docs.paymove.io/en/webhook-signature.md ``` Ten sam efekt daje menu kontekstowe w prawym górnym rogu każdej strony - znajdziesz tam kopiowanie treści oraz otwarcie strony bezpośrednio w Claude, ChatGPT, Cursorze czy VS Code. ## Mapa dokumentacji dla modeli | Adres | Zawartość | | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | [`/llms.txt`](https://docs.paymove.io/llms.txt) | Spis wszystkich stron z opisami oraz zestaw reguł integracji. Dobry punkt startu, gdy agent ma sam zdecydować, co przeczytać | | [`/llms-full.txt`](https://docs.paymove.io/llms-full.txt) | Cała dokumentacja w jednym pliku | | [`/openapi.yaml`](https://docs.paymove.io/openapi.yaml) | Specyfikacja OpenAPI bramki płatniczej | | [`/skill.md`](https://docs.paymove.io/skill.md) | Gotowa procedura wdrożenia krok po kroku | | [`/agents.md`](https://docs.paymove.io/agents.md) | Krótki zestaw reguł do wklejenia w repozytorium | ## Reguły, na które agenci najczęściej się przewracają Jeśli piszesz integrację samodzielnie albo sprawdzasz kod wygenerowany przez agenta, zwróć uwagę na te punkty: 1. **Kwoty to liczby całkowite w groszach.** `12.99` zostanie po cichu obcięte do 12 groszy, a API zwróci `200`. Przeliczaj przez `Math.round(kwota * 100)`. 2. **Autoryzacja nagłówkiem `X-API-KEY`**, nie `Authorization: Bearer`. 3. **Tylko po stronie serwera.** Klucz nie może trafić do frontendu, a przeglądarka i tak nie przejdzie CORS. 4. **Zawsze weryfikuj `X-Paymove-Signature`** przed realizacją zamówienia. 5. **Nigdy nie realizuj zamówienia na `returnUrl`** - to tylko przekierowanie przeglądarki. 6. **Nieznane pola są ignorowane, a odpowiedź to `200`** - błędna struktura żądania wygląda jak sukces. 7. **Nie ma limitów zapytań, kodu `429`, `422` ani `Idempotency-Key`** - kod obsługujący te przypadki jest zbędny. Pełne uzasadnienie każdej z nich znajdziesz w [Kodach błędów](/errors) i [Weryfikacji podpisu](/webhook-signature). ## Zgłoś problem Jeśli Twój agent wygenerował niedziałającą integrację mimo skorzystania z powyższych materiałów, napisz na [integration@paymove.io](mailto:integration@paymove.io) i dołącz użyty prompt. Traktujemy to jako błąd dokumentacji. # Aktualizuj pozycję cennika Source: https://docs.paymove.io/api-reference/cennik/aktualizuj-pozycję-cennika /openapi-docpay.yaml patch /api/pay/products/{productId}/pricing/entries/{entryId} Aktualizuje pozycję cenową. Zmiana jest natychmiast widoczna dla klientów wybierających daną opcję. # Lista pozycji cennika Source: https://docs.paymove.io/api-reference/cennik/lista-pozycji-cennika /openapi-docpay.yaml get /api/pay/products/{productId}/pricing/entries Zwraca wszystkie pozycje cenowe dla produktu. # Usuń pozycję cennika Source: https://docs.paymove.io/api-reference/cennik/usuń-pozycję-cennika /openapi-docpay.yaml delete /api/pay/products/{productId}/pricing/entries/{entryId} Usuwa pozycję cenową. Po usunięciu wariant nie jest dostępny przy zakupie. # Utwórz pozycję cennika Source: https://docs.paymove.io/api-reference/cennik/utwórz-pozycję-cennika /openapi-docpay.yaml post /api/pay/products/{productId}/pricing/entries Tworzy nową pozycję cenową — opcję zakupu wyświetlaną na stronie produktu (np. „1 godzina", „cały dzień", „bilet weekendowy"). # Aktualizuj pole formularza Source: https://docs.paymove.io/api-reference/formularze/aktualizuj-pole-formularza /openapi-docpay.yaml patch /api/pay/product/{productId}/form/{formId}/field/{fieldId} Aktualizuje pole — tworzy nową wersję formularza z zaktualizowanym polem. # Dodaj pole formularza Source: https://docs.paymove.io/api-reference/formularze/dodaj-pole-formularza /openapi-docpay.yaml post /api/pay/product/{productId}/form/{formId}/fields Dodaje pole do formularza. Operacja automatycznie tworzy nową wersję formularza; poprzednia traci status bieżącej (`isCurrent`). # Lista formularzy Source: https://docs.paymove.io/api-reference/formularze/lista-formularzy /openapi-docpay.yaml get /api/pay/product/{productId}/form Zwraca listę formularzy dla produktu. # Szczegóły formularza Source: https://docs.paymove.io/api-reference/formularze/szczegóły-formularza /openapi-docpay.yaml get /api/pay/product/{productId}/form/{formId} Zwraca szczegóły formularza — pola, wersję i flagę `isCurrent`. # Szczegóły pola formularza Source: https://docs.paymove.io/api-reference/formularze/szczegóły-pola-formularza /openapi-docpay.yaml get /api/pay/product/{productId}/form/field/{fieldId} Zwraca szczegóły pojedynczego pola formularza. # Usuń formularz Source: https://docs.paymove.io/api-reference/formularze/usuń-formularz /openapi-docpay.yaml delete /api/pay/product/{productId}/form/{formId} Usuwa formularz. Nie wpływa na dane z już zrealizowanych zakupów. # Usuń pole formularza Source: https://docs.paymove.io/api-reference/formularze/usuń-pole-formularza /openapi-docpay.yaml delete /api/pay/product/{productId}/form/{formId}/field/{fieldId} Usuwa pole — tworzy nową wersję formularza bez tego pola. # Utwórz formularz Source: https://docs.paymove.io/api-reference/formularze/utwórz-formularz /openapi-docpay.yaml post /api/pay/product/{productId}/form Tworzy formularz przypisany do produktu. Formularze zbierają od klientów dane wymagane do zakupu (np. e-mail, numer rejestracyjny). Formularze są wersjonowane; każda zmiana pól tworzy nową wersję, a bieżąca oznaczana jest flagą `isCurrent`. # Aktualizuj customizację Source: https://docs.paymove.io/api-reference/personalizacja-ui/aktualizuj-customizację /openapi-docpay.yaml patch /api/pay/product/{productId}/customization/{customizationId} Aktualizuje customizację — możesz wysłać tylko te pola, które chcesz zmienić. Zmiana jest natychmiast odzwierciedlana na stronie zakupu. # Lista customizacji Source: https://docs.paymove.io/api-reference/personalizacja-ui/lista-customizacji /openapi-docpay.yaml get /api/pay/product/{productId}/customization Zwraca listę wszystkich customizacji produktu. Dla jednego produktu możesz mieć wiele customizacji z różnymi `locale`; system wybiera odpowiednią według ustawień użytkownika. # Szczegóły customizacji Source: https://docs.paymove.io/api-reference/personalizacja-ui/szczegóły-customizacji /openapi-docpay.yaml get /api/pay/product/{productId}/customization/{customizationId} Zwraca wszystkie pola customizacji. # Usuń customizację Source: https://docs.paymove.io/api-reference/personalizacja-ui/usuń-customizację /openapi-docpay.yaml delete /api/pay/product/{productId}/customization/{customizationId} Usuwa customizację. Produkt przestaje używać tej konfiguracji; inne customizacje i usługi nie są zmieniane. # Utwórz customizację Source: https://docs.paymove.io/api-reference/personalizacja-ui/utwórz-customizację /openapi-docpay.yaml post /api/pay/product/{productId}/customization Tworzy customizację dla produktu — wszystkie teksty widoczne na stronie zakupu (tytuły, przyciski, opisy) konfigurowane z poziomu API. # Aktualizuj produkt Source: https://docs.paymove.io/api-reference/produkt/aktualizuj-produkt /openapi-docpay.yaml patch /api/product/pay/{productId} Aktualizuje dane produktu. Możesz wysłać tylko pola do zmiany; pozostałe relacje (cennik, formularze, webhooki) pozostają bez zmian. # Utwórz produkt Source: https://docs.paymove.io/api-reference/produkt/utwórz-produkt /openapi-docpay.yaml post /api/product/pay Tworzy produkt reprezentujący Twoją usługę w systemie Paymove. Produkt jest rootem całego modelu — subprodukty, cennik, formularze, customizacje i webhooki są podłączane do konkretnego produktu. Tworzysz go jednorazowo. Pole `id` z odpowiedzi to `productId` używany w pozostałych endpointach. # Utwórz produkt (sklep) Source: https://docs.paymove.io/api-reference/produkty/utwórz-produkt-sklep /openapi.yaml post /api/product/pay 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. # Sprawdź status płatności Source: https://docs.paymove.io/api-reference/płatności/sprawdź-status-płatności /openapi.yaml get /api/payment/product/{productId}/subproduct/{paymentHash}/status 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. # Utwórz płatność Source: https://docs.paymove.io/api-reference/płatności/utwórz-płatność /openapi.yaml post /api/pay/product/{productId}/subproduct/pricing 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ść. # Zmień kwotę płatności Source: https://docs.paymove.io/api-reference/płatności/zmień-kwotę-płatności /openapi.yaml patch /api/pay/product/{productId}/subproduct/{externalId} 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ę. # Zarejestruj subprodukt (kod QR) Source: https://docs.paymove.io/api-reference/subprodukty-i-kody-qr/zarejestruj-subprodukt-kod-qr /openapi-docpay.yaml post /api/pay/product/{productId}/subproduct Rejestruje subprodukt — pojedynczą płatność (np. wezwanie do zapłaty, bilet) powiązaną z zewnętrznym identyfikatorem `externalId`. Odpowiedź to binarna zawartość obrazka z kodem QR w formacie wskazanym w `imageFormat` — zapisz body odpowiedzi bezpośrednio do pliku. Po zeskanowaniu kodu użytkownik trafia do płatności za ten subprodukt. # Aktualizuj webhook Source: https://docs.paymove.io/api-reference/webhooki/aktualizuj-webhook /openapi-docpay.yaml patch /api/pay/plugin/webhook/{webhookId} Aktualizuje webhook. Zmiana wpływa na wszystkie powiązane produkty. # Lista produktów webhooka Source: https://docs.paymove.io/api-reference/webhooki/lista-produktów-webhooka /openapi-docpay.yaml get /api/pay/plugin/webhook/{webhookId}/products Zwraca listę produktów powiązanych z webhookiem — przydatne do weryfikacji konfiguracji. # Lista webhooków Source: https://docs.paymove.io/api-reference/webhooki/lista-webhooków /openapi-docpay.yaml get /api/pay/plugin/webhook Zwraca listę webhooków. Opcjonalnie filtrowana po `productId` lub `partnerId`. # Przypisz webhook do produktu Source: https://docs.paymove.io/api-reference/webhooki/przypisz-webhook-do-produktu /openapi-docpay.yaml post /api/pay/plugin/webhook/{webhookId}/products/{productId} Powiązuje zarejestrowany webhook z produktem. Od tego momentu zdarzenia (np. udana płatność) dla tego produktu trafiają na wskazany `endpoint`. # Szczegóły webhooka Source: https://docs.paymove.io/api-reference/webhooki/szczegóły-webhooka /openapi-docpay.yaml get /api/pay/plugin/webhook/{webhookId} Zwraca konfigurację webhooka. # Wymień sekret podpisujący webhooka Source: https://docs.paymove.io/api-reference/webhooki/wymień-sekret-podpisujący-webhooka /openapi.yaml post /api/pay/plugin/webhook/{webhookId}/rotate-secret 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. # Zarejestruj webhook Source: https://docs.paymove.io/api-reference/webhooki/zarejestruj-webhook /openapi-docpay.yaml post /api/pay/plugin/webhook Rejestruje webhook — adres URL po Twojej stronie, który Paymove wywołuje po zakończonej płatności. Po rejestracji webhook musi zostać przypisany do produktu osobnym wywołaniem. # Branding Source: https://docs.paymove.io/branding Buttony i zasoby graficzne do integracji płatności Paymove Wykorzystaj oficjalne buttony płatnicze Paymove w swoim sklepie lub aplikacji. ## Buttony płatności ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` ## Warianty | Wariant | Użycie | | --------------------------------------------- | ---------------------------- | | **Primary** — `#1a1a1a` na białym tekście | Jasne tła, strony produktowe | | **Inverted** — `#fafafa` na tekście `#0a0d14` | Ciemne tła, dark mode | ## Wytyczne * Nie zmieniaj kolorów ani proporcji buttonów * Zachowaj minimalny padding wokół buttona * Button powinien być wyraźnie widoczny i klikalny * Używaj buttona wyłącznie do inicjowania płatności przez Paymove # Kody błędów Source: https://docs.paymove.io/errors Wszystkie błędy zwracane przez bramkę płatniczą, ich przyczyny — oraz mechanizmy, których API nie posiada. ## Kształt odpowiedzi błędu Bramka zwraca błędy w jednym, stałym formacie: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": 401, "message": "Invalid API key" } ``` | Pole | Opis | | --------- | ----------------------------------------------- | | `status` | Kod statusu HTTP, powielony w treści odpowiedzi | | `message` | Opis przeznaczony dla człowieka | **Nie opieraj logiki na treści `message`.** Rozgałęziaj wyłącznie po statusie HTTP. Komunikaty bywają zmieniane bez zapowiedzi, a część z nich zawiera wewnętrzne nazwy klas z backendu - na przykład `PaySubProductEntity not found`. Nie ma osobnego, maszynowego kodu błędu. Dwa wyjątki od powyższego kształtu warto znać: **Niepoprawny JSON w żądaniu** - błąd rozpoznawany, zanim dane trafią do logiki biznesowej: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": 400, "message": "Malformed JSON request. Please check your request body." } ``` **Nieoczekiwany błąd po stronie Paymove** - zawsze z tym samym, ogólnym komunikatem: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": 500, "message": "Something went wrong" } ``` ## Tabela błędów | Status | `message` | Przyczyna | Co zrobić | | ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `Malformed JSON request. Please check your request body.` | Body nie jest poprawnym JSON-em albo pole wyliczeniowe ma nieznaną wartość | Sprawdź składnię i typy pól | | `400` | `Bank account not found for this partner` | Konto rozliczeniowe partnera nie jest skonfigurowane | Skontaktuj się z Paymove | | `401` | `Missing credentials` | Brak nagłówka `X-API-KEY` | Dodaj nagłówek | | `401` | `Invalid API key` | Klucz nie został rozpoznany, wygasł albo został odwołany | Sprawdź, czy używasz klucza właściwego dla środowiska: `sk_test_` w sandboxie, `sk_live_` na produkcji. Nowy klucz wygenerujesz w [Panelu](https://panel.paymove.io) | | `403` | `Product does not belong to you` | `productId` należy do innego partnera | Sprawdź, czy `productId` pasuje do użytego klucza | | `404` | `PayProductEntity not found` | Produkt o podanym `productId` nie istnieje | Zweryfikuj `productId` | | `404` | `WebhookEntity not found` | Webhook o podanym `webhookId` nie istnieje | Zweryfikuj `webhookId` | | `404` | `PaySubProductEntity not found` | Płatność o podanym identyfikatorze nie istnieje | Sprawdź, czy używasz właściwego identyfikatora | | `500` | `Something went wrong` | Błąd wewnętrzny **albo brak `externalId` w żądaniu**, **albo niepoprawny format UUID w ścieżce** | Najpierw sprawdź kompletność body i poprawność `productId` - dopiero potem ponów wywołanie | ## Czego w tym API nie ma Ta sekcja jest równie ważna jak tabela powyżej. Poniższych mechanizmów bramka **nie posiada** - pisanie kodu, który je obsługuje, jest zbędne i wprowadza w błąd: | Mechanizm | Stan | | ------------------------------------ | --------------------------------------------------------------- | | Limity liczby zapytań i status `429` | Nie istnieją - bramka nie ogranicza tempa wywołań | | Status `422` | Nie występuje. Błędy walidacji zwracane są jako `400` lub `500` | | Nagłówek `Idempotency-Key` | Nie istnieje. Rolę klucza idempotencji pełni `externalId` | | Wersjonowanie API | Brak. Nie ma prefiksu `/v1/` ani nagłówka wersji | | Nagłówki `Deprecation` / `Sunset` | Nie są wysyłane | | Maszynowy kod błędu (`code`, `type`) | Nie istnieje. Dostępne są wyłącznie `status` i `message` | ## Błędy, które wyglądają jak sukces Najgroźniejsza kategoria: API zwraca `200`, choć żądanie było błędne. Sprawdź te przypadki, zanim uznasz integrację za działającą. **Kwota z częścią dziesiętną jest po cichu obcinana.** `"price": 12.99` zostanie zapisane jako `12` grosze, a odpowiedź to `200`. Przeliczaj przez `Math.round(kwota * 100)`. **Nieznane pola są ignorowane.** Wysłanie `amount` zamiast `price`, `currency` czy `returnUrl` na najwyższym poziomie zamiast w `details` nie zgłosi błędu - płatność powstanie z niekompletnymi danymi. **Powtórzony `externalId` zwraca starą płatność.** Odpowiedź to `200` z `redirectUrl` utworzonym wcześniej, a nowa kwota jest odrzucana. Kwotę zmieniaj przez `PATCH /api/pay/product/{productId}/subproduct/{externalId}`. **Brak `externalId` kończy się kodem `500`, a nie `400`.** Zanim potraktujesz `500` jako błąd przejściowy i ponowisz wywołanie, sprawdź, czy Twoje body zawiera `externalId`. **Brak `price` albo `details.returnUrl` nie zgłasza żadnego błędu.** Dostajesz `200` i działający `redirectUrl` - do płatności bez kwoty albo bez powrotu do sklepu. Kompletność tych pól musisz sprawdzić po swojej stronie, przed wysłaniem żądania. **Niepoprawny format UUID w ścieżce też zwraca `500`.** Literówka w `productId` nie da czytelnego `400` - dostaniesz ogólne `Something went wrong`. ## Obsługa błędów w kodzie ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} const response = await fetch(url, options); if (!response.ok) { const error = await response.json(); // { status, message } switch (response.status) { case 401: case 403: // Problem z konfiguracją - ponawianie nie pomoże throw new PaymentConfigError(error.message); case 404: throw new PaymentNotFoundError(error.message); case 400: // Błędne żądanie - napraw dane, nie ponawiaj throw new PaymentRequestError(error.message); case 500: // Najpierw zweryfikuj kompletność body, dopiero potem ponów throw new PaymentServerError(error.message); default: throw new Error(`Paymove ${response.status}: ${error.message}`); } } ``` Ponawianie ma sens wyłącznie przy `500` i błędach sieciowych, i tylko po upewnieniu się, że żądanie było kompletne. Kody `400`, `401`, `403` i `404` oznaczają problem po Twojej stronie - ponowienie zwróci ten sam wynik. ## Błędy SDK [SDK dla Node.js](/sdk/javascript) opakowuje powyższe odpowiedzi w typowane wyjątki: | Klasa | Kiedy | | ------------------------ | --------------------------------------------------------------------------------------------------- | | `PaymoveValidationError` | Argument odrzucony przez SDK jeszcze przed wysłaniem żądania. Pole `field` wskazuje parametr | | `PaymoveApiError` | API zwróciło status inny niż 2xx. Pola `statusCode` i `responseBody` zawierają oryginalną odpowiedź | | `PaymoveNetworkError` | Żądanie nie doszło do skutku. Pole `cause` zawiera pierwotny wyjątek | Walidacja `amount` w SDK sprawdza wyłącznie, czy wartość jest liczbą większą od zera. Kwota `49.99` przejdzie tę kontrolę, a API obetnie ją do 49 groszy. ## Co dalej Poprawna struktura żądania i pełne odpowiedzi. Obsługa błędów po stronie odbioru webhooka. # Start Source: https://docs.paymove.io/introduction Przegląd produktów Paymove i adresy bazowe API — punkt wejścia do dokumentacji technicznej. Wprowadź swoje płatności w nową erę. Z nami otworzysz się na nową grupę odbiorców, od tradycyjnych klientów, po inteligentne systemy AI. Postaw na nowoczesną integrację i przyjmuj płatności jeszcze dzisiaj z wykorzystaniem REST API, webhooków i gotowych tutoriali krok po kroku. ## Produkty
Bramka płatnicza

Odbieraj płatności za produkty i usługi online.

DocPay

Płatności w dokumentach i linkach płatniczych.

Link płatności

Linki i kody QR do płatności za cokolwiek.

Pay\&Go 🔜

Szybkie płatności mobilne i w punkcie sprzedaży.

QR Terminal 🔜

Terminal płatniczy oparty o kody QR - bez dodatkowego sprzętu.

AI Payments 🔜

Płatności dla agentów i inteligentnych systemów AI.

## Rest API | Środowisko | Base URL | | ---------- | ---------------------------------------- | | Sandbox | `https://gateway-api.sandbox.paymove.io` | | Production | `https://api.paymove.io` | # Metody płatności Source: https://docs.paymove.io/payment-methods Obsługiwane metody płatności w Paymove Natychmiastowe płatności kodem BLIK. Płatności jednym kliknięciem na urządzeniach Apple. Szybkie płatności na Androidzie i w Chrome. Visa i Mastercard, płatność natychmiastowa. Poza powyższymi checkout obsługuje też **Pay by Link** (przelew online przez stronę banku), **przelew tradycyjny** oraz **PayPo** (odroczona płatność). Dostępność poszczególnych metod zależy od konfiguracji Twojego produktu - jeśli którejś nie widzisz na checkoucie, napisz na [integration@paymove.io](mailto:integration@paymove.io). **Wkrótce dostępne:** BLIK OneClick, Klarna, MCP # Statusy płatności Source: https://docs.paymove.io/payment-status 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 | 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. `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`. Zwróć uwagę na pisownię `CANCELED` - przez jedno „l". ## Kiedy przychodzi webhook **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). 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 | **`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. Payload nie zawiera pola `event` ani `type` i nie jest opakowany w kopertę. To płaski obiekt, a rodzaj zdarzenia rozpoznajesz po polu `status`. ### 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**. 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`. ## Sprawdzenie statusu zapytaniem Przydatne jako uzupełnienie webhooka - na przykład gdy klient wrócił na `returnUrl`, a powiadomienie jeszcze nie dotarło. 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}`. | Ś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" } ``` `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. 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. ## 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 Obowiązkowy krok przed realizacją zamówienia. Rejestracja webhooka i przypisanie go do produktu. # Start Source: https://docs.paymove.io/products/ai-payments Płatności dla agentów i inteligentnych systemów AI AI Payments to zestaw integracji, dzięki którym agenci AI - zarówno tekstowi (LLM podłączone przez MCP), jak i głosowi (ElevenLabs) - mogą samodzielnie sprawdzać katalog produktów i płacić za nie BLIK-iem w imieniu użytkownika. Każda płatność to osobna, świeża transakcja BLIK, potwierdzana kodem z aplikacji bankowej - obecnie agent nigdy nie płaci automatycznie, bez udziału człowieka w danej transakcji. ## Jak to działa? Każdy agent AI jest powiązany z jednym kontem (`agentId` + klucz API) w Agent Panel. Wszystkie kwoty w API AI Payments wyrażone są w **groszach** (najmniejsza jednostka PLN, liczba całkowita) - to obowiązuje zarówno w katalogu produktów, jak i w żądaniach płatności. Płatność BLIK-iem nie jest pobierana z zasilonego wcześniej salda portfela w Agent Panel - to osobna, każdorazowo potwierdzana płatność. Saldo portfela dotyczy innego mechanizmu (automatyczne płatności x402 bez potwierdzenia BLIK), który na ten moment jest wyłączony - patrz sekcja MCP. ```mermaid theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} sequenceDiagram participant U as Użytkownik participant A as Agent AI (MCP / ElevenLabs) participant P as Agent Panel API participant PM as Paymove (BLIK) U->>A: "Kup mi bilet do Kopernika" A->>P: Sprawdź katalog produktów A->>U: Poproś o kod BLIK U->>A: Podaje kod BLIK A->>P: Zainicjuj płatność (agentId, cena, kod BLIK) P->>PM: Utwórz płatność BLIK PM->>P: Status płatności A->>P: Sprawdzaj status aż do rozstrzygnięcia A->>U: Potwierdzenie sukcesu / niepowodzenia ``` ## Dwie ścieżki integracji Serwer Model Context Protocol dla agentów LLM (Claude i inni klienci MCP) - narzędzia do przeglądania katalogu, płatności BLIK i historii transakcji. Agent głosowy osadzony w Agent Panel - klient rozmawia głosowo, agent sprawdza katalog i finalizuje płatność BLIK przez narzędzia webhook. Guardrails (limity kwotowe) skonfigurowane dla agenta w Agent Panel nie są dziś egzekwowane przy płatności BLIK - żadna z integracji nie sprawdza ich przed zainicjowaniem płatności. Bezpieczeństwo zapewnia w tej chwili wyłącznie to, że każdą płatność BLIK musi osobno potwierdzić człowiek w aplikacji bankowej. # ElevenLabs Source: https://docs.paymove.io/products/ai-payments/elevenlabs Agent Panel osadza widget [ElevenLabs Conversational AI](https://elevenlabs.io/conversational-ai), dzięki czemu użytkownik może porozmawiać głosowo z agentem sprzedażowym, który sprawdza katalog produktów i finalizuje płatność BLIK - bez wpisywania czegokolwiek w interfejsie. ## 1. Osadzenie widgetu W aplikacji ładowany jest skrypt embed ElevenLabs, a widget montowany jest z `dynamic-variables` przekazującymi `agentId` portfela, do którego przypisana jest rozmowa: ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` `agentId` trafia do agenta ElevenLabs jako zmienna dynamiczna i jest przekazywany dalej w każdym wywołaniu narzędzi płatniczych - to on wiąże rozmowę głosową z konkretnym portfelem. To są dwa różne identyfikatory o podobnej nazwie - łatwo je pomylić: * **`agent-id`** (atrybut widgetu) - ID bota w ElevenLabs. Wskazuje, którą konfigurację agenta głosowego załadować (prompt, głos, narzędzia). Stała wartość, ta sama dla każdej rozmowy. * **`agentId`** (wewnątrz `dynamic-variables`) - identyfikator portfela Paymove (ten sam co w `agentId` transakcji/agenta w Agent Panel). Zmienia się w zależności od tego, kto otwiera czat, i to on trafia do `process_blik_payment`, żeby narzędzie wiedziało, z którego portfela zejść płatność. ## 2. Konfiguracja narzędzi (webhook tools) W panelu ElevenLabs → agent → **Narzędzia** skonfiguruj trzy narzędzia webhook wskazujące na Twoje środowisko Agent Panel (np. `https://`): ### `get_available_products` | | | | ------ | ----------------------------- | | Metoda | `GET` | | URL | `/api/products` | | Auth | brak - katalog jest publiczny | Zwraca listę produktów z `id`, `name` i `price` (**w groszach**). ### `process_blik_payment` | | | | ------ | -------------------- | | Metoda | `POST` | | URL | `/api/payments/blik` | Body (JSON): | Pole | Źródło | Opis | | ------------------- | -------------------------- | -------------------------------------------------------------------- | | `agentId` | dynamic variable | Portfel, z którego realizowana jest płatność | | `price` | z `get_available_products` | Cena **w groszach** - bez przeliczania na PLN | | `email` | LLM / rozmowa | E-mail klienta | | `authorizationCode` | LLM / rozmowa | 6-cyfrowy kod BLIK podany przez klienta | | `productId` | z `get_available_products` | Identyfikator produktu (potrzebny, żeby paragon miał nazwę produktu) | Odpowiedź: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "continueUrl": "https://...", "externalId": "agent_123abc456", "txId": "blik_9f2e...", "amount": 3000, "currency": "PLN" } ``` Ta odpowiedź **nie zawiera jeszcze wyniku płatności** - `externalId` służy wyłącznie do sprawdzenia statusu w kolejnym kroku. Agent nie powinien na tej podstawie ogłaszać sukcesu. ### `check_payment_status` | | | | ----------- | --------------------------------------------------------- | | Metoda | `GET` | | URL | `/api/payments/blik/status` | | Query param | `externalId` - **zmienna dynamiczna**, nie pytanie do LLM | Odpowiedź: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": "pending" | "completed" | "failed", "paymove": { "status": "...", "orderId": "..." } } ``` ## 3. Przechwycenie `externalId` jako dynamic variable `externalId` powstaje dopiero w odpowiedzi `process_blik_payment`, więc w konfiguracji tego narzędzia w sekcji **przypisań (assignments)** dodaj: * pole odpowiedzi `externalId` → zapisz jako dynamic variable, np. `payment_external_id` Następnie w `check_payment_status` parametr `externalId` ustaw jako tę samą zmienną dynamiczną (`{{payment_external_id}}`), a nie jako podpowiedź LLM. ## 4. Kontrakt groszowy Wszystkie kwoty w tych trzech narzędziach są w **groszach** (liczba całkowita, `30 zł` = `3000`). Cena pobrana z `get_available_products` trafia bez żadnej modyfikacji do `process_blik_payment.price`. To samo dotyczy pola `amount` w odpowiedzi - agent powinien podzielić je przez 100, żeby wypowiedzieć cenę klientowi w złotówkach. W opisie parametru `price` w konfiguracji narzędzia `process_blik_payment` w panelu ElevenLabs upewnij się, że jest napisane wprost, że jednostką są **grosze** (np. "Cena w groszach - 30 zł należy wysłać jako 3000"). Sam opis w promptcie/systemie nie wystarczy, jeśli opis parametru narzędzia sugeruje PLN - LLM kieruje się przede wszystkim opisem pola. ## 5. System prompt - obsługa statusu Dodaj do system promptu jednoznaczną instrukcję, że wynik płatności rozstrzyga wyłącznie pole `status` z `check_payment_status`, a nie sam fakt, że wywołanie się powiodło: ``` Po process_blik_payment ZAWSZE wywołuj check_payment_status, dopóki status nie przestanie być "pending" (maks. 10 razy, co 2-3 sekundy). Nigdy nie informuj klienta o wyniku płatności przed otrzymaniem statusu innego niż "pending". - status "completed" → potwierdź klientowi sukces zakupu i podaj nazwę produktu oraz cenę. - status "failed" → poinformuj klienta, że płatność się nie powiodła, zaproponuj podanie nowego kodu BLIK. - status "pending" → powiedz, że płatność się przetwarza, i sprawdź ponownie. ``` Agent Panel niezależnie śledzi te same transakcje i wyświetla użytkownikowi wiadomości o postępie zakupu głosowego w czacie - `check_payment_status` w ElevenLabs jest potrzebny, żeby **agent głosowy** wiedział, co powiedzieć klientowi, a nie po to, żeby zainicjować samą płatność. # MCP Source: https://docs.paymove.io/products/ai-payments/mcp Nasz serwer [Model Context Protocol](https://modelcontextprotocol.io) pozwala dowolnemu klientowi MCP (np. Claude Desktop, Claude Code, innym agentom LLM) łączyć się bezpośrednio z agentem w Agent Panel, przeglądać katalog produktów i płacić za nie BLIK-iem w jego imieniu - każda płatność jest osobno potwierdzana przez użytkownika kodem BLIK w aplikacji bankowej. Serwer obsługuje też protokół [x402](https://www.x402.org/) do w pełni automatycznych płatności (bez potwierdzenia BLIK), ale to narzędzie jest na ten moment wyłączone - patrz sekcję [Dostępne narzędzia](#dostępne-narzędzia) poniżej. ## 1. Klucz API Każdy agent (portfel) ma własny klucz API, widoczny w Agent Panel w zakładce **Ustawienia → Klucze API**. Klucz jest tworzony razem z portfelem agenta i wykorzystywany do uwierzytelniania sesji MCP. ## 2. Uruchomienie serwera lokalnie Serwer MCP to część `agent-facilitator` (`npm run mcp`, w Dockerze uruchamiany razem z główną usługą - patrz `Dockerfile`). Musi mieć dostęp do dwóch rzeczy, żeby narzędzia płatnicze działały: | Zmienna | Do czego służy | Wartość domyślna (jeśli nieustawiona) | | ------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | `MONGO_URI` | Baza z agentami/portfelami i katalogiem - **musi być ta sama**, z której korzysta Agent Panel | `mongodb://localhost:27017/?replicaSet=rs` | | `DASHBOARD_API_URL` | Adres Agent Panel - tam żyje integracja z PAYTEL, `process_blik_payment`/`check_payment_status` wołają jego REST API | `http://localhost:3002` | ## 3. Konfiguracja klienta MCP ### Claude Code / Claude CLI Najprostszy sposób - serwer wspiera natywny transport HTTP, więc nie jest potrzebny żaden dodatkowy wrapper: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} claude mcp add --transport http paymove-mcp http://localhost:4021/mcp \ --header "x-api-key: " \ --scope user ``` * `--transport http` - wskazuje na Streamable HTTP (nie SSE, nie stdio) * `http://localhost:4021/mcp` - adres serwera; w środowisku innym niż lokalne (staging/prod) podmień na właściwy host, na którym działa proces `npm run mcp` z `agent-facilitator` (domyślny port to `4021`, konfigurowalny przez zmienną `MCP_PORT`) * `--header "x-api-key: ..."` - klucz API agenta z Agent Panel (krok 1) * `--scope user` - serwer dostępny we wszystkich projektach danego użytkownika Claude Code (użyj `--scope project`, jeśli ma być widoczny tylko w jednym repo) Po dodaniu serwer widoczny jest na liście `claude mcp list`, a narzędzia (`list_products`, `process_blik_payment`, `check_payment_status`, `list_transactions`) - w rozmowie z Claude Code. ### Custom connector (Claude.ai, Claude Desktop, Cowork, aplikacje mobilne) Działa we wszystkich klientach Claude poza Claude Code, bez żadnej konfiguracji lokalnej. Connector łączy się z serwerem z chmury Anthropic, nie z Twojego urządzenia, więc **serwer musi mieć publiczny adres** - `localhost` nie zadziała. Do lokalnego developmentu użyj Claude Code (sekcja wyżej). 1. Otwórz ustawienia connectorów:
a) Free/Pro/Max: **Customize → Connectors → "Add custom connector"**.
b) Team/Enterprise: Owner dodaje connector w **Organization settings → Connectors → Add → Custom → Web**, a każdy użytkownik włącza go potem u siebie w **Customize → Connectors**. 2. Podaj adres serwera: `https:///mcp`. 3. W **Request headers** dodaj nagłówek `x-api-key` z kluczem API agenta i zaznacz go jako **Required**. 4. Kliknij **Add**, a potem włącz connector w rozmowie przyciskiem **"+" → Connectors**. Uwierzytelnianie przez nagłówki (**Request headers**) jest funkcją w becie i może nie być jeszcze widoczne na Twoim koncie - jeśli tak, poproś Anthropic o dostęp. ## Dostępne narzędzia * [`list_products`](#list_products) * [`process_blik_payment`](#process_blik_payment) * [`check_payment_status`](#check_payment_status) * [`list_transactions`](#list_transactions) Narzędzia `fetch_x402_resource` (automatyczna płatność x402 z salda portfela), `check_wallet_balance` (saldo portfela + status guardrails) oraz `buy_via_acp` (automatyczna płatność u merchanta Agentic Commerce Protocol, również z salda portfela) są tymczasowo wyłączone w kodzie serwera. MCP obsługuje obecnie wyłącznie katalog produktów i płatność BLIK, każdorazowo potwierdzaną przez użytkownika. ### `list_products` Zwraca katalog dostępnych produktów (`id`, nazwa, cena w PLN i w groszach). Nie przyjmuje parametrów. `id` z odpowiedzi jest wymagany do wywołania `process_blik_payment`. ### `process_blik_payment` Płaci BLIK-iem za wybrany produkt w imieniu agenta przypisanego do klucza API. To osobna, świeża płatność potwierdzana kodem BLIK przy każdym zakupie - nie jest pobierana z żadnego zgromadzonego wcześniej salda. | Parametr | Wymagany | Opis | | ------------------- | -------- | ------------------------------------------- | | `productId` | tak | Identyfikator produktu z `list_products` | | `authorizationCode` | tak | 6-cyfrowy kod BLIK podany przez użytkownika | Cena jest pobierana samodzielnie z katalogu na podstawie `productId` (LLM nie podaje jej ręcznie - eliminuje to błędy przy przeliczaniu na grosze). Narzędzie tylko **inicjuje** płatność i zwraca `txId` oraz `externalId` - wynik nie jest jeszcze znany w tym momencie. Po `process_blik_payment` agent musi wywołać `check_payment_status` zanim poinformuje użytkownika o wyniku. Sam brak błędu przy inicjacji płatności nie oznacza sukcesu. `txId` można pokazać użytkownikowi jako numer referencyjny. `externalId` jest wyłącznie techniczny - służy do wywołania `check_payment_status` i nie powinien być pokazywany ani wspominany użytkownikowi. ### `check_payment_status` Sprawdza rzeczywisty wynik wcześniej zainicjowanej płatności BLIK. | Parametr | Wymagany | Opis | | ------------ | -------- | --------------------------------------------- | | `externalId` | tak | Wartość zwrócona przez `process_blik_payment` | Zwraca `status`: `pending`, `completed` albo `failed`. Dopóki status to `pending`, agent powinien odczekać kilka sekund i sprawdzić ponownie (maks. ok. 10 razy) zamiast informować użytkownika o wyniku. ### `list_transactions` Zwraca historię transakcji portfela przypisanego do klucza API, od najnowszej. Obsługuje opcjonalny zakres dat, np. żeby sprawdzić zakupy z konkretnego kwartału - samo `from` bez `to` zwraca wszystko od podanej daty do dziś. | Parametr | Wymagany | Opis | | -------- | -------- | ---------------------------------------------------------------------------------- | | `limit` | nie | Maksymalna liczba transakcji (domyślnie 10, a 50 gdy podano `from`/`to`, maks. 50) | | `from` | nie | Początek zakresu (włącznie), data ISO np. `2026-01-01` | | `to` | nie | Koniec zakresu (włącznie), data ISO np. `2026-03-31` | | `offset` | nie | Liczba transakcji do pominięcia - do pobierania kolejnej strony wyników | Każdy wpis zawiera datę, status, nazwę produktu (jeśli dostępna), kwotę i `txId` - przydatne, żeby agent mógł odpowiedzieć na pytania w stylu "co ostatnio kupiłem" albo "co kupiłem w Q1", albo sprawdzić, czy dane zamówienie nie zostało już zrealizowane, zanim spróbuje zapłacić ponownie. Jeśli wyników jest więcej niż zwrócony limit, odpowiedź narzędzia zawiera na końcu informację, ile transakcji zostało jeszcze do pokazania. Agent powinien wtedy zapytać użytkownika, czy pokazać kolejne, a nie automatycznie pobierać wszystkie kolejne strony. Guardrails (limity godzinowe/dobowe) można skonfigurować w ustawieniach portfela, ale nic w aplikacji nie sprawdza faktycznych wydatków względem tych limitów. Realną granicą jest dziś to, że każdą płatność musi osobno potwierdzić człowiek kodem BLIK. # Start Source: https://docs.paymove.io/products/docpay DocPay — płatności za pojedyncze dokumenty: wezwania do zapłaty, bilety i faktury, z kodem QR. DocPay (PAY API) umożliwia przyjmowanie płatności za pojedyncze dokumenty - wezwania do zapłaty, bilety, faktury. Integracja w podstawowym scenariuszu jest prosta: **produkt tworzysz raz**, a następnie dla każdej pojedynczej płatności rejestrujesz **subprodukt**. W odpowiedzi otrzymujesz kod QR, który przekierowuje klienta bezpośrednio do strony płatności. ## Jak działa płatność? ```mermaid theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} sequenceDiagram participant M as Twój system participant P as Paymove API participant K as Klient Note over M,P: Jednorazowo M->>P: Utwórz produkt (partnerId, name) P->>M: { productId } Note over M,P: Dla każdej płatności M->>P: Utwórz subprodukt (externalId, price, details) P->>M: Kod QR M->>K: Przekazanie kodu QR (np. na dokumencie) K->>P: Skanuje kod QR i opłaca dokument ``` | Krok | Kto | Co się dzieje | | ---- | ----------- | ------------------------------------------------------------------------------- | | 1 | Twój system | Tworzy produkt - jednorazowo, przy starcie integracji | | 2 | Twój system | Dla każdej płatności tworzy subprodukt z `externalId`, kwotą i danymi dokumentu | | 3 | Paymove | Zwraca kod QR przekierowujący do płatności | | 4 | Klient | Skanuje kod QR (np. z wezwania do zapłaty) i opłaca dokument | Krok po kroku: utworzenie produktu i subproduktu z kodem QR. Pełna dokumentacja endpointów: cennik, formularze, personalizacja UI, webhooki, subprodukt. ## Co potrzebujesz do integracji | Dane | Format | Opis | | ----------- | ----------------------------------------------------- | ------------------------------------------------------- | | `apiKey` | `sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | Klucz autoryzacyjny przekazywany w nagłówku `X-API-KEY` | | `partnerId` | `78562c79-2f5c-4415-8af4-c871eea92ef2` | UUID identyfikujący partnera w systemie Paymove | `Klucz API` i `partnerId` otrzymasz od Paymove. Adres środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. # REST API Source: https://docs.paymove.io/products/docpay/rest-api Pełna referencja endpointów DocPay: produkt, subprodukty i kody QR, cennik, formularze, personalizacja UI, webhooki. Dokumentacja endpointów PAY API (DocPay): produkt, subprodukt i kody QR, cennik, formularze, personalizacja UI oraz webhooki. Podstawowy scenariusz integracji (produkt + subprodukt) opisany jest w [Tutorialu](/products/docpay/tutorial). *** ## 1. Produkt Produkt reprezentuje Twoją usługę w systemie Paymove i jest rootem całego modelu - subprodukty, cennik, formularze, customizacje i webhooki są podłączane do konkretnego produktu. Produkt tworzysz **jednorazowo**. ### Endpointy – Produkt | Metoda | Endpoint | Opis | | ------ | ------------------------------ | -------------------- | | POST | `/api/product/pay` | Tworzy produkt. | | PATCH | `/api/product/pay/{productId}` | Aktualizuje produkt. | ### Utworzenie produktu ``` POST /api/product/pay ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2", "productType": "PAY", "name": "Testowy Produkt", "shortName": "3456", "location": "Warszawa", "timezone": "Europe/Warsaw" } ``` | Pole | Opis | | ------------- | ------------------------------------------------ | | `partnerId` | Identyfikator partnera (nadawany przez Paymove). | | `productType` | Typ produktu - dla DocPay: `PAY`. | | `name` | Nazwa produktu. | | `shortName` | Krótka nazwa wyświetlana. | | `location` | Lokalizacja (np. miasto). | | `timezone` | Strefa czasowa IANA (np. `Europe/Warsaw`). | **Odpowiedź (200):** ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "id": "d0a834f9-94a4-4b3a-aa10-27d911e3633f", "name": "Testowy Produkt", "location": "Warszawa", "partner": { "id": "78562c79-2f5c-4415-8af4-c871eea92ef2", "name": "Nazwa Partnera Sp. z o.o.", "productTypes": ["PAY"] }, "timezone": "Europe/Warsaw", "shortName": "3456", "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": 1783245692.623763835, "updatedAt": 1783245692.623763835 } ``` Pole `id` to identyfikator produktu (`productId`) używany w pozostałych endpointach. ### Aktualizacja produktu ``` PATCH /api/product/pay/{productId} ``` Możesz wysłać tylko pola do zmiany; pozostałe relacje (cennik, formularze, webhooki) pozostają bez zmian. *** ## 2. Subprodukt i kody QR Subprodukt reprezentuje pojedynczą płatność (np. wezwanie do zapłaty, bilet) powiązaną z zewnętrznym identyfikatorem (`externalId`). W odpowiedzi generowany jest kod QR w wybranym formacie (PNG/SVG). Po zeskanowaniu kodu użytkownik trafia do płatności za dany subprodukt. ### Dane wejściowe | Pole | Opis | | ------------- | --------------------------------------------------------------------------------------------- | | `externalId` | Identyfikator dokumentu w Twoim systemie. Wyświetlany w panelu Paymove jako numer dokumentu. | | `imageFormat` | Format grafiki kodu QR: `svg` lub `png`. | | `bannerType` | Typ banera/karty (np. `ticket`) - wpływa na prezentację/układ. | | `price` | Kwota w groszach (np. `25000` = 250,00 PLN). | | `details` | Pary klucz-wartość wyświetlane klientowi na stronie płatności (np. `"Nr. dokumentu": "..."`). | ### Endpoint – Subprodukt | Metoda | Endpoint | Opis | | ------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | POST | `/api/pay/product/{productId}/subproduct` | Rejestruje subprodukt na podstawie `externalId` i generuje kod QR. Po rejestracji użytkownik może opłacić subprodukt po zeskanowaniu kodu. | ### Rejestracja subproduktu ``` POST /api/pay/product/{productId}/subproduct ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "externalId": "test-external#2", "imageFormat": "svg", "bannerType": "ticket", "price": 25000, "details": { "Nr. dokumentu": "test-external#2" } } ``` Odpowiedź (200) to binarna zawartość obrazka z kodem QR w formacie wskazanym w `imageFormat` (`Content-Type: application/octet-stream`) - zapisz body odpowiedzi bezpośrednio do pliku. Grafika to gotowy do druku baner z kodem QR przekierowującym do płatności za ten subprodukt. *** ## 3. Cennik Cennik definiuje opcje płatności dostępne dla klienta - np. „1 godzina", „cały dzień", „bilet weekendowy". Każda pozycja (pricing entry) to oddzielna opcja zakupu wyświetlana na stronie produktu. ### Dane pozycji cenowej | Pole | Opis | | ------------- | -------------------------------------------------- | | `price` | Wartość w groszach (np. 150 = 1,50 PLN). | | `description` | Opis słowny opcji (np. „1 godzina parkowania"). | | `details` | JSON z dodatkowymi danymi (np. `currency`, `vat`). | ### Endpointy – Cennik | Metoda | Endpoint | Opis | | ------ | --------------------------------------------------------- | -------------------------------------------------------------------------- | | GET | `/api/pay/products/{productId}/pricing/entries` | Zwraca wszystkie pozycje cenowe dla produktu. | | POST | `/api/pay/products/{productId}/pricing/entries` | Tworzy nową pozycję cenową. | | PATCH | `/api/pay/products/{productId}/pricing/entries/{entryId}` | Aktualizuje pozycję cenową. | | DELETE | `/api/pay/products/{productId}/pricing/entries/{entryId}` | Usuwa pozycję cenową. Po usunięciu wariant nie jest dostępny przy zakupie. | ### Utworzenie pozycji cennika ``` POST /api/pay/products/{productId}/pricing/entries ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "price": 150, "description": "1 godzina parkowania", "details": "{\"currency\":\"PLN\",\"vat\":23}" } ``` ### Aktualizacja pozycji cennika ``` PATCH /api/pay/products/{productId}/pricing/entries/{entryId} ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "price": 150000000, "details": "{\"currency\":\"PLN\",\"vat\":23}" } ``` Zmiana jest natychmiast widoczna dla klientów wybierających daną opcję. *** ## 4. Formularze Formularze służą do zbierania od klientów danych wymaganych do zakupu - np. e-mail, numer rejestracyjny, dane kontaktowe. Formularze są wersjonowane; każda zmiana (dodanie/edycja/usunięcie pola) tworzy nową wersję. Bieżąca wersja oznaczana jest flagą `isCurrent`. ### Dane pola formularza (Field) | Pole | Opis | | ------------ | -------------------------------------------------- | | `name` | Nazwa pola (np. `email`). | | `type` | Typ: `TEXT`, `NUMBER`, `SELECT`, `EMAIL` itd. | | `labels` | Etykiety językowe (np. `pl`, `en`). | | `validators` | Walidacje: `required`, `pattern`, `maxLength` itd. | ### Endpointy – Formularze | Metoda | Endpoint | Opis | | ------ | -------------------------------------------- | ----------------------------------------------------------------- | | GET | `/api/pay/product/{productId}/form` | Lista formularzy dla produktów PAY. | | GET | `/api/pay/product/{productId}/form/{formId}` | Szczegóły formularza (pola, wersja, isCurrent). | | POST | `/api/pay/product/{productId}/form` | Tworzy formularz przypisany do produktu. | | DELETE | `/api/pay/product/{productId}/form/{formId}` | Usuwa formularz. Nie wpływa na dane z już zrealizowanych zakupów. | ### Endpointy – Pola (Fields) | Metoda | Endpoint | Opis | | ------ | ------------------------------------------------------------ | --------------------------------------------------------------------------- | | GET | `/api/pay/product/{productId}/form/field/{fieldId}` | Szczegóły pola. | | POST | `/api/pay/product/{productId}/form/{formId}/fields` | Dodaje pole - tworzy nową wersję formularza, nowa wersja staje się bieżąca. | | PATCH | `/api/pay/product/{productId}/form/{formId}/field/{fieldId}` | Aktualizuje pole - tworzy nową wersję formularza z zaktualizowanym polem. | | DELETE | `/api/pay/product/{productId}/form/{formId}/field/{fieldId}` | Usuwa pole - tworzy nową wersję bez tego pola. | ### Utworzenie formularza ``` POST /api/pay/product/{productId}/form ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "fields": [], "version": 1, "isCurrent": true } ``` ### Dodanie pola (np. e-mail) ``` POST /api/pay/product/{productId}/form/{formId}/fields ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "name": "email", "type": "TEXT", "labels": { "en": "Email", "pl": "Adres e-mail" }, "validators": { "required": true, "maxLength": 100, "pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" } } ``` Operacja automatycznie tworzy nową wersję formularza; poprzednia traci status bieżącej (`isCurrent`). *** ## 5. Personalizacja UI Customization pozwala zmienić wygląd i treści widoczne na stronie zakupu produktu. Wszystkie teksty (tytuły, przyciski, opisy) są konfigurowane z poziomu API - frontend jest w pełni sterowany przez partnera bez zmian w kodzie. ### Parametry customizacji - co modyfikują | Parametr | Co modyfikuje | Gdzie się wyświetla | | ------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `title` | Główny tytuł strony/productu. | Nagłówek na górze widoku zakupu. | | `subtitle` | Tekst pod tytułem. | Bezpośrednio pod tytułem (subtytuł). | | `buttonText` | Tekst przycisku akcji (np. „Kup teraz", „Zapłać"). | Przycisk potwierdzenia na stronie płatności. | | `summaryHeader` | Nagłówek sekcji podsumowania. | Nagłówek bloku z podsumowaniem zamówienia przed płatnością. | | `summaryFirstLine` | Pierwsza linia w sekcji podsumowania. | Tekst w podsumowaniu (np. opis kwoty lub produktu). | | `summarySecondLine` | Druga linia w sekcji podsumowania. | Kolejna linia w podsumowaniu. | | `cardDescription` | Opis karty produktu. | Opis wyświetlany na karcie/listingu produktu (np. w wyborze produktu lub w podglądzie). | | `locale` | Język/wersja językowa (np. `pl-PL`, `en-GB`). | Określa, która wersja tekstów (dla danej customizacji) jest używana; pozwala mieć wiele customizacji (np. osobno PL i EN). | Dla jednego produktu możesz mieć wiele customizacji z różnymi `locale`; system wybiera odpowiednią według ustawień użytkownika lub kontekstu. ### Endpointy – Personalizacja | Metoda | Endpoint | Opis | | ------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | GET | `/api/pay/product/{productId}/customization` | Lista wszystkich customizacji. | | GET | `/api/pay/product/{productId}/customization/{id}` | Szczegóły customizacji (wszystkie pola). | | POST | `/api/pay/product/{productId}/customization` | Tworzy customizację dla produktu. | | PATCH | `/api/pay/product/{productId}/customization/{id}` | Aktualizuje customizację (dowolne pola). Zmiana jest od razu widoczna na froncie. | | DELETE | `/api/pay/product/{productId}/customization/{id}` | Usuwa customizację. Produkt przestaje używać tej konfiguracji; inne customizacje i usługi nie są zmieniane. | ### Utworzenie customizacji ``` POST /api/pay/product/{productId}/customization ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "title": "Nowy produkt", "subtitle": "Subtytuł", "summaryHeader": "Nagłówek podsumowania", "summaryFirstLine": "Pierwsza linia", "summarySecondLine": "Druga linia", "buttonText": "Kup teraz", "cardDescription": "Opis karty", "locale": "pl-PL" } ``` ### Aktualizacja customizacji ``` PATCH /api/pay/product/{productId}/customization/{id} ``` Możesz wysłać tylko te pola, które chcesz zmienić, np.: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "summaryFirstLine": "Pierwsza linia", "summarySecondLine": "Druga linia", "buttonText": "Kup teraz", "cardDescription": "Opis karty", "locale": "pl-PL" } ``` Zmiana jest natychmiast odzwierciedlana w prezentacji produktu na stronie zakupu. *** ## 6. Webhooki Webhooki służą do automatycznego informowania partnera o zakończonej płatności lub do pobrania cennika z systemu zewnętrznego. Po zarejestrowaniu webhooka należy przypisać go do produktu - wtedy zdarzenia związane z tym produktem są wysyłane na wskazany endpoint. ### Pola konfiguracji webhooka | Pole | Opis | | ------------------ | -------------------------------------------------------------------- | | `endpoint` | URL, na który Paymove wysyła żądanie (POST). | | `method` | Metoda HTTP (np. `POST`). | | `headers` | Nagłówki dołączane do żądania (np. `Authorization`, `Content-Type`). | | `requestTemplate` | Szablon body żądania (zmienne podstawiane przez Paymove). | | `responseTemplate` | Oczekiwana struktura odpowiedzi od partnera. | | `expectedCode` | Oczekiwany kod HTTP odpowiedzi (np. 200). | | `expectedResponse` | Oczekiwana treść odpowiedzi (np. `{ "status": "ok" }`). | | `retries` | Liczba ponownych prób przy niepowodzeniu. | | `type` | Typ zdarzenia (np. `PAYMENT`). | ### Endpointy – Webhooki | Metoda | Endpoint | Opis | | ------ | ---------------------------------------------------------- | ------------------------------------------------------------ | | GET | `/api/pay/plugin/webhook` | Lista webhooków. Opcjonalne query: `productId`, `partnerId`. | | GET | `/api/pay/plugin/webhook/{webhookId}` | Szczegóły webhooka. | | GET | `/api/pay/plugin/webhook/{webhookId}/products` | Lista produktów powiązanych z webhookiem. | | POST | `/api/pay/plugin/webhook` | Tworzy webhook. | | POST | `/api/pay/plugin/webhook/{webhookId}/products/{productId}` | Przypisuje webhook do produktu. | | PATCH | `/api/pay/plugin/webhook/{webhookId}` | Aktualizuje webhook. Wpływa na wszystkie powiązane produkty. | ### Utworzenie webhooka ``` POST /api/pay/plugin/webhook ``` ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "name": "PaymentSuccessHook", "endpoint": "https://example.com/webhooks/payment-success", "method": "POST", "requestTemplate": { "productId": "productId", "event": "event" }, "responseTemplate": { "status": "ok" }, "expectedCode": 200, "expectedResponse": "{ \"status\": \"ok\" }", "retries": 3, "partnerId": "6909ca83-410f-47c4-910d-2057f8565a8c", "type": "PAYMENT", "headers": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] } } ``` **Odpowiedź (200):** obiekt webhooka z nadanym `id` (identyfikator webhooka) oraz polem `signingSecret`: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "id": "18e19688-bdda-4843-8777-0f04d0143c77", "name": "PaymentSuccessHook", "endpoint": "https://example.com/webhooks/payment-success", "method": "POST", "requestTemplate": { "productId": "productId", "event": "event" }, "responseTemplate": { "status": "ok" }, "expectedCode": 200, "expectedResponse": "{ \"status\": \"ok\" }", "retries": 3, "type": "PAYMENT", "headers": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] }, "signingSecret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` `signingSecret` służy do weryfikacji podpisu żądań przychodzących od Paymove. Przechowuj go bezpiecznie. Po utworzeniu wywołaj **POST** `/api/pay/plugin/webhook/{webhookId}/products/{productId}`, aby powiązać webhook z produktem (odpowiedź: obiekt webhooka). Od tego momentu zdarzenia (np. udana płatność) dla tego produktu trafiają na Twój `endpoint`. # DocPay: podstawowa integracja Source: https://docs.paymove.io/products/docpay/tutorial Podstawowa integracja z DocPay: utwórz produkt, dodaj subprodukt i odbierz kod QR prowadzący prosto do płatności. Podstawowy scenariusz integracji DocPay składa się z dwóch kroków. **Produkt tworzysz jednorazowo**, a następnie dla każdej pojedynczej płatności (np. wezwania do zapłaty) tworzysz **subprodukt**. W odpowiedzi otrzymujesz kod QR, który przekierowuje klienta do strony płatności. Wszystkie żądania autoryzujesz kluczem API przekazywanym w nagłówku `X-API-KEY`. Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. ## Krok 1: Utworzenie produktu Produkt reprezentuje Twoją usługę w systemie Paymove i jest kontenerem dla subproduktów. 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": "Testowy Produkt", "shortName": "3456", "location": "Warszawa", "timezone": "Europe/Warsaw" }' ``` | Pole | Wymagane | Opis | | ------------- | -------- | ------------------------------------------------ | | `partnerId` | Tak | Identyfikator partnera (nadawany przez Paymove). | | `productType` | Tak | Typ produktu - dla DocPay zawsze `PAY`. | | `name` | Tak | Nazwa produktu. | | `shortName` | Tak | Krótka nazwa wyświetlana. | | `location` | Tak | Lokalizacja (np. miasto). | | `timezone` | Tak | Strefa czasowa IANA (np. `Europe/Warsaw`). | **Odpowiedź (200):** ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "id": "d0a834f9-94a4-4b3a-aa10-27d911e3633f", "name": "Testowy Produkt", "location": "Warszawa", "partner": { "id": "78562c79-2f5c-4415-8af4-c871eea92ef2", "name": "Nazwa Partnera Sp. z o.o.", "productTypes": ["PAY"] }, "timezone": "Europe/Warsaw", "shortName": "3456", "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": 1783245692.623763835, "updatedAt": 1783245692.623763835 } ``` Pole `id` z odpowiedzi to identyfikator produktu (`productId`), którego użyjesz w kroku 2. ## Krok 2: Utworzenie subproduktu Subprodukt reprezentuje **pojedynczą płatność** - np. jedno wezwanie do zapłaty. W URL podmień identyfikator produktu (`productId`) otrzymany w 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/1c32c81e-8f0e-40e1-8ad0-f5eeef9e3503/subproduct \ --header 'Content-Type: application/json' \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ --data '{ "externalId": "test-external#2", "imageFormat": "svg", "bannerType": "ticket", "price": 25000, "details": { "Nr. dokumentu": "test-external#2" } }' ``` | Pole | Wymagane | Opis | | ------------- | -------- | --------------------------------------------------------------------------------------------- | | `externalId` | Tak | Identyfikator dokumentu w Twoim systemie. Wyświetlany w panelu Paymove jako numer dokumentu. | | `imageFormat` | Tak | Format grafiki kodu QR: `svg` lub `png`. | | `bannerType` | Tak | Typ banera/karty (np. `ticket`) - wpływa na prezentację. | | `price` | Tak | Kwota w groszach (np. `25000` = 250,00 PLN). | | `details` | Nie | Pary klucz-wartość wyświetlane klientowi na stronie płatności (np. `"Nr. dokumentu": "..."`). | Wartości z `details` są wyświetlane użytkownikowi wizualnie na stronie płatności - klucz to etykieta, a wartość to prezentowany tekst. ## Odpowiedź: kod QR Odpowiedź (200) to **binarna zawartość obrazka** z kodem QR w formacie wskazanym w `imageFormat` (SVG lub PNG), zwracana z nagłówkiem `Content-Type: application/octet-stream`. To nie jest JSON - zapisz body odpowiedzi bezpośrednio do pliku, np.: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --request POST \ --url https://gateway-api.sandbox.paymove.io/api/pay/product/{productId}/subproduct \ --header 'Content-Type: application/json' \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ --data '{ ... }' \ --output kod-qr.svg ``` Grafika zawiera gotowy do druku baner z kodem QR przekierowującym do płatności (dla `png` np. 300x780 px). Umieść go np. na wezwaniu do zapłaty - klient po zeskanowaniu trafi bezpośrednio do opłacenia dokumentu. ## Co dalej? Pełna dokumentacja endpointów: cennik, formularze, personalizacja UI i webhooki (powiadomienia o wpłacie). # DocPay: webhooki Source: https://docs.paymove.io/products/docpay/tutorial-webhook Dodaj webhook do DocPay i otrzymuj automatyczne powiadomienia o każdej zakończonej płatności. Webhook automatycznie powiadamia Twój system o zakończonej płatności. Konfiguracja składa się z dwóch kroków: **rejestrujesz webhook**, a następnie **przypisujesz go do produktu**. Od tego momentu zdarzenia (np. udana płatność) dla tego produktu trafiają na wskazany przez Ciebie endpoint. Wszystkie żądania autoryzujesz kluczem API przekazywanym w nagłówku `X-API-KEY`. Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. Potrzebujesz `partnerId` oraz `productId` produktu utworzonego w tutorialu [DocPay: podstawowa integracja](/products/docpay/tutorial). ## Krok 1: Rejestracja webhooka ```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": "PaymentSuccessHook", "endpoint": "https://example.com/webhooks/payment-success", "method": "POST", "requestTemplate": { "productId": "productId", "event": "event" }, "responseTemplate": { "status": "ok" }, "expectedCode": 200, "expectedResponse": "{ \"status\": \"ok\" }", "retries": 3, "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2", "type": "PAYMENT", "headers": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] } }' ``` | Pole | Opis | | ------------------ | -------------------------------------------------------------------- | | `name` | Nazwa webhooka (widoczna w konfiguracji). | | `endpoint` | URL, na który Paymove wysyła żądanie. | | `method` | Metoda HTTP (np. `POST`). | | `requestTemplate` | Szablon body żądania (zmienne podstawiane przez Paymove). | | `responseTemplate` | Oczekiwana struktura odpowiedzi od partnera. | | `expectedCode` | Oczekiwany kod HTTP odpowiedzi (np. 200). | | `expectedResponse` | Oczekiwana treść odpowiedzi (np. `{ "status": "ok" }`). | | `retries` | Liczba ponownych prób przy niepowodzeniu. | | `partnerId` | Identyfikator partnera (nadawany przez Paymove). | | `type` | Typ zdarzenia - dla powiadomień o płatności: `PAYMENT`. | | `headers` | Nagłówki dołączane do żądania (np. `Authorization`, `Content-Type`). | **Odpowiedź (200):** ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "id": "18e19688-bdda-4843-8777-0f04d0143c77", "name": "PaymentSuccessHook", "endpoint": "https://example.com/webhooks/payment-success", "method": "POST", "requestTemplate": { "productId": "productId", "event": "event" }, "responseTemplate": { "status": "ok" }, "expectedCode": 200, "expectedResponse": "{ \"status\": \"ok\" }", "retries": 3, "type": "PAYMENT", "headers": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] }, "signingSecret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` Pole `id` z odpowiedzi to identyfikator webhooka (`webhookId`), którego użyjesz w kroku 2. Odpowiedź zawiera `signingSecret` - sekret do weryfikacji podpisu żądań przychodzących od Paymove. Zapisz go bezpiecznie po stronie swojego systemu i nie udostępniaj publicznie. ## Krok 2: Przypisanie webhooka do produktu Webhook zacznie działać dopiero po powiązaniu z produktem. W URL podmień `webhookId` (z kroku 1) oraz `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/18e19688-bdda-4843-8777-0f04d0143c77/products/d0a834f9-94a4-4b3a-aa10-27d911e3633f \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ``` **Odpowiedź (200):** obiekt webhooka (taki sam jak w kroku 1), potwierdzający powiązanie. Od tego momentu zdarzenia płatności dla tego produktu są wysyłane na Twój `endpoint`. ## Weryfikacja konfiguracji Listę produktów powiązanych z webhookiem sprawdzisz wywołaniem: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --url https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook/18e19688-bdda-4843-8777-0f04d0143c77/products \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ``` **Odpowiedź (200):** tablica produktów powiązanych z webhookiem: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} [ { "id": "d0a834f9-94a4-4b3a-aa10-27d911e3633f", "name": "Testowy Produkt", "productType": "PAY", "status": "ACTIVE" } ] ``` ## Jak Paymove wywołuje Twój endpoint? Po zakończonej płatności Paymove wysyła na Twój `endpoint` żądanie zgodne z `requestTemplate` i skonfigurowanymi `headers`. Twój system powinien odpowiedzieć kodem `expectedCode` (zwyczajowo `200` i body `{ "status": "ok" }`, choć treść odpowiedzi nie jest sprawdzana). W przypadku niepowodzenia Paymove ponowi próbę tyle razy, ile wskazuje `retries`. ## Co dalej? Pełna dokumentacja endpointów webhooków: lista, szczegóły, aktualizacja. # Start Source: https://docs.paymove.io/products/payment-gateway Bramka płatnicza Paymove — jak działa przepływ płatności i czego potrzebujesz do integracji. Z bramką płatniczą Paymove przyjmowanie płatności online jest proste. Jako sprzedawca inicjujesz transakcję przez nasze API lub SDK, a Twój klient opłaca ją na bezpiecznej, gotowej stronie płatności. Gdy tylko pieniądze zostaną wpłacone, Twój system natychmiast otrzyma automatyczne powiadomienie. Gotową integrację możesz przetestować w [demo sklepie Paymove](https://demo.checkout.paymove.io/). ## Jak działa płatność? ```mermaid theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} sequenceDiagram participant M as Twój serwer participant P as Paymove API participant C as Checkout Paymove participant K as Klient M->>P: Utwórz płatność (kwota, orderId, returnUrl) P->>M: { redirectUrl } M->>K: Przekieruj na redirectUrl K->>C: Klient finalizuje płatność C->>P: Płatność zakończona P->>M: Webhook + nagłówek X-Paymove-Signature M->>M: Weryfikacja podpisu M->>P: { "status": "ok" } C->>K: Klient klika „Wróć do sklepu" (opcjonalnie) ``` | Krok | Kto | Co się dzieje | | ---- | ----------- | ---------------------------------------------------------------------------------------------- | | 1 | Twój serwer | Wywołuje API Paymove z kwotą, `externalId` i `returnUrl` | | 2 | Paymove | Zwraca `redirectUrl` - adres strony checkoutu | | 3 | Klient | Zostaje przekierowany na checkout i podaje dane płatności | | 4 | Paymove | Wysyła podpisany webhook na zarejestrowany URL - domyślnie tylko dla statusu `COMPLETED` | | 5 | Twój serwer | [Weryfikuje podpis](/webhook-signature), odpowiada `{ "status": "ok" }` i realizuje zamówienie | | 6 | Klient | Widzi ekran potwierdzenia i **może** kliknąć „Wróć do sklepu", co przenosi go na `returnUrl` | Zamówienie realizuj wyłącznie po zweryfikowanym webhooku (krok 5), nigdy po powrocie klienta na `returnUrl` (krok 6). Krok 6 wymaga kliknięcia i może nie nastąpić wcale, a sam `returnUrl` można otworzyć bez opłacenia zamówienia. ## Co potrzebujesz do integracji | Dane | Format | Opis | | ----------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `apiKey` | `sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | Klucz autoryzacyjny przekazywany w nagłówku `X-API-KEY`. Sandbox: `sk_test_`, produkcja: `sk_live_` | | `productId` | `2f6c19e8-84a7-4f50-b950-8d5a05e0bbf2` | UUID identyfikujący Twój sklep w systemie Paymove | `Klucz API` i `productId` wygenerujesz w [Panelu Paymove](https://panel.paymove.io). # Bramka płatnicza: podstawowa integracja Source: https://docs.paymove.io/products/payment-gateway/tutorial 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`. Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. Klucz API i `partnerId` otrzymasz od Paymove. ## 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" } ``` `signingSecret` służy do weryfikacji podpisu żądań przychodzących od Paymove. Zapisz go bezpiecznie po stronie swojego systemu. 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). | `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)`. **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" } ``` 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. ## 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`. 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. ## Co dalej? Rozbuduj integrację o webhooki i reaguj na każdą opłaconą transakcję. Gotowy klient do integracji w Node.js. # Bramka płatnicza: webhooki Source: https://docs.paymove.io/products/payment-gateway/tutorial-webhook Skonfiguruj webhook bramki płatniczej i dowiaduj się o każdej opłaconej transakcji, zanim klient wróci do Twojego sklepu. Webhook to sposób, w jaki Paymove aktywnie powiadamia Twój system o zakończonej płatności - bez odpytywania API. Konfiguracja składa się z dwóch kroków: **rejestrujesz webhook**, a następnie **przypisujesz go do produktu** (sklepu). Od tego momentu każda opłacona transakcja w tym sklepie trafia na wskazany przez Ciebie endpoint. Wszystkie żądania autoryzujesz kluczem API przekazywanym w nagłówku `X-API-KEY`. Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. Potrzebujesz `partnerId` oraz `productId` sklepu utworzonego w tutorialu [Bramka płatnicza: podstawowa integracja](/products/payment-gateway/tutorial). ## Krok 1: Rejestracja webhooka W `requestTemplate` definiujesz body żądania, które Paymove wyśle na Twój endpoint. Możesz w nim użyć zmiennych `{{externalId}}` (identyfikator zamówienia z Twojego systemu) 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": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] } }' ``` | Pole | Opis | | ------------------ | ---------------------------------------------------------------------------------------------------- | | `name` | Nazwa webhooka (widoczna w konfiguracji). | | `endpoint` | URL, na który Paymove wysyła żądanie. | | `method` | Metoda HTTP (np. `POST`). | | `requestTemplate` | Szablon body żądania (zmienne podstawiane przez Paymove). | | `responseTemplate` | Oczekiwana struktura odpowiedzi od partnera. | | `expectedCode` | Oczekiwany kod HTTP odpowiedzi (np. 200). | | `expectedResponse` | Oczekiwana treść odpowiedzi (np. `{ "status": "ok" }`). | | `retries` | Liczba ponownych prób przy niepowodzeniu. **Domyślnie `0`** - ustaw jawnie, jeśli chcesz ponawianie. | | `partnerId` | Identyfikator partnera (nadawany przez Paymove). | | `type` | Typ zdarzenia - dla powiadomień o płatności: `PAYMENT`. | | `headers` | Nagłówki dołączane do żądania (np. `Authorization`, `Content-Type`). | **Odpowiedź (200):** ```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": { "Authorization": ["Bearer abc123"], "Content-Type": ["application/json"] }, "signingSecret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` Pole `id` z odpowiedzi to identyfikator webhooka (`webhookId`), którego użyjesz w kroku 2. Odpowiedź zawiera `signingSecret` - sekret do weryfikacji podpisu żądań przychodzących od Paymove. Zapisz go bezpiecznie po stronie swojego systemu i nie udostępniaj publicznie. ## Krok 2: Przypisanie webhooka do sklepu Webhook zacznie działać dopiero po powiązaniu z produktem. W URL podmień `webhookId` (z kroku 1) oraz `productId` swojego sklepu. ```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 (taki sam jak w kroku 1), potwierdzający powiązanie. Od tego momentu każda opłacona płatność w tym sklepie wywołuje Twój `endpoint`. ## Weryfikacja konfiguracji Listę produktów powiązanych z webhookiem sprawdzisz wywołaniem: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --url https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook/6b23ecd9-14c8-47fc-add0-b71ec50e9d66/products \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ``` **Odpowiedź (200):** tablica produktów powiązanych z webhookiem: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} [ { "id": "891412c8-8717-4449-9543-e34112bec470", "name": "Sklep Testowy", "productType": "PAY", "status": "ACTIVE" } ] ``` ## Jak wygląda powiadomienie o płatności? Po zakończonej płatności Paymove wysyła na Twój `endpoint` żądanie zgodne z `requestTemplate` i skonfigurowanymi `headers`. Dla szablonu z kroku 1 body wygląda tak: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "orderId": "order-123", "price": 1000 } ``` `orderId` to `externalId`, który przekazałeś tworząc płatność - dzięki temu jednoznacznie dopasujesz powiadomienie do zamówienia w swoim systemie. Powyższy kształt to wynik `requestTemplate`. Gdyby szablon był pusty, Paymove wysłałby pełny obiekt płatności - zestaw pól opisuje [Konfiguracja](/webhooks#payload-webhooka). Zanim zaufasz treści powiadomienia, **zweryfikuj nagłówek `X-Paymove-Signature`**. Bez tego dowolna osoba znająca Twój `endpoint` może wysłać spreparowane powiadomienie i uzyskać realizację zamówienia bez płatności. Gotowy kod: [Weryfikacja podpisu webhooka](/webhook-signature). Twój system powinien odpowiedzieć kodem **dokładnie równym** `expectedCode` (zwyczajowo `200` i body `{ "status": "ok" }`, choć treść odpowiedzi nie jest sprawdzana) - dopiero wtedy Paymove uznaje doręczenie za udane. Odpowiedź `201` czy `204` przy `expectedCode: 200` liczy się jako niepowodzenie i **nie jest ponawiana**. Doręczenie zakończone kodem 4xx, 5xx lub błędem sieci zostanie ponowione tyle razy, ile wskazuje `retries` - **domyślnie `0`, czyli ani razu**. Realizuj zamówienie po otrzymaniu webhooka, a nie po powrocie klienta na `returnUrl` - klient może zamknąć przeglądarkę zanim wróci do Twojego sklepu, a samo przekierowanie można wywołać bez opłacenia płatności. ## Co dalej? Pełny przepływ płatności: produkt, płatność, przekierowanie klienta. Szczegóły konfiguracji powiadomień o płatnościach. # Start Source: https://docs.paymove.io/products/payment-link Link płatności — przyjmowanie płatności bez sklepu internetowego, przez link lub kod QR. Link płatności (Payment Link) pozwala przyjąć płatność **za cokolwiek** - usługę, rezerwację, zbiórkę, zamówienie telefoniczne - bez sklepu internetowego i bez koszyka. Integracja w podstawowym scenariuszu jest prosta: **produkt tworzysz raz**, a następnie dla każdej płatności rejestrujesz **subprodukt**. W odpowiedzi otrzymujesz kod QR z linkiem, który przekierowuje płacącego bezpośrednio do strony płatności - możesz go wysłać e-mailem, SMS-em, umieścić na wydruku lub pokazać na ekranie. ## Jak działa płatność? ```mermaid theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} sequenceDiagram participant M as Twój system participant P as Paymove API participant K as Płacący Note over M,P: Jednorazowo M->>P: Utwórz produkt (partnerId, name) P->>M: { productId } Note over M,P: Dla każdej płatności M->>P: Utwórz subprodukt (externalId, price, details) P->>M: Kod QR z linkiem płatności M->>K: Wysyłka linku (e-mail, SMS, wydruk, ekran) K->>P: Otwiera link lub skanuje kod i płaci ``` | Krok | Kto | Co się dzieje | | ---- | ----------- | ------------------------------------------------------------------------------- | | 1 | Twój system | Tworzy produkt - jednorazowo, przy starcie integracji | | 2 | Twój system | Dla każdej płatności tworzy subprodukt z `externalId`, kwotą i opisem płatności | | 3 | Paymove | Zwraca kod QR z linkiem przekierowującym do płatności | | 4 | Płacący | Otwiera link (lub skanuje kod) i opłaca dowolną należność | Krok po kroku: utworzenie produktu i subproduktu z linkiem płatności. Pełna dokumentacja endpointów PAY API: cennik, formularze, personalizacja UI, webhooki. ## Co potrzebujesz do integracji | Dane | Format | Opis | | ----------- | ----------------------------------------------------- | ------------------------------------------------------- | | `apiKey` | `sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` | Klucz autoryzacyjny przekazywany w nagłówku `X-API-KEY` | | `partnerId` | `78562c79-2f5c-4415-8af4-c871eea92ef2` | UUID identyfikujący partnera w systemie Paymove | `Klucz API` i `partnerId` otrzymasz od Paymove. Adres środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. # Integracja Source: https://docs.paymove.io/products/payment-link/integration Podstawowa integracja z linkami płatności: utwórz produkt, dodaj subprodukt i odbierz link do płatności za cokolwiek. Podstawowy scenariusz integracji składa się z dwóch kroków. **Produkt tworzysz jednorazowo**, a następnie dla każdej płatności - niezależnie od tego, czego dotyczy - tworzysz **subprodukt**. W odpowiedzi otrzymujesz kod QR z linkiem przekierowującym płacącego do strony płatności. Wszystkie żądania autoryzujesz kluczem API przekazywanym w nagłówku `X-API-KEY`. Przykłady używają środowiska sandbox: `https://gateway-api.sandbox.paymove.io`. ## Krok 1: Utworzenie produktu Produkt reprezentuje Twoją działalność w systemie Paymove i jest kontenerem dla subproduktów (pojedynczych płatności). 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": "Płatności Online", "shortName": "PAYLINK", "location": "Warszawa", "timezone": "Europe/Warsaw" }' ``` | Pole | Wymagane | Opis | | ------------- | -------- | ------------------------------------------------ | | `partnerId` | Tak | Identyfikator partnera (nadawany przez Paymove). | | `productType` | Tak | Typ produktu - zawsze `PAY`. | | `name` | Tak | Nazwa produktu. | | `shortName` | Tak | Krótka nazwa wyświetlana. | | `location` | Tak | Lokalizacja (np. miasto). | | `timezone` | Tak | Strefa czasowa IANA (np. `Europe/Warsaw`). | **Odpowiedź (200):** ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "id": "d0a834f9-94a4-4b3a-aa10-27d911e3633f", "name": "Płatności Online", "location": "Warszawa", "partner": { "id": "78562c79-2f5c-4415-8af4-c871eea92ef2", "name": "Nazwa Partnera Sp. z o.o.", "productTypes": ["PAY"] }, "timezone": "Europe/Warsaw", "shortName": "PAYLINK", "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": 1783245692.623763835, "updatedAt": 1783245692.623763835 } ``` Pole `id` z odpowiedzi to identyfikator produktu (`productId`), którego użyjesz w kroku 2. ## Krok 2: Utworzenie subproduktu (link płatności) Subprodukt reprezentuje **pojedynczą płatność za cokolwiek** - konsultację, rezerwację, składkę, naprawę, zamówienie telefoniczne. W URL podmień identyfikator produktu (`productId`) otrzymany w kroku 1. W polu `details` przekazujesz dowolne pary klucz-wartość opisujące, czego dotyczy płatność - to one zostaną wyświetlone płacącemu na stronie płatności. ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --request POST \ --url https://gateway-api.sandbox.paymove.io/api/pay/product/d0a834f9-94a4-4b3a-aa10-27d911e3633f/subproduct \ --header 'Content-Type: application/json' \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ --data '{ "externalId": "payment-2026-0001", "imageFormat": "png", "bannerType": "ticket", "price": 15000, "details": { "Tytuł płatności": "Konsultacja online - 60 minut", "Za co": "Usługa doradcza", "Odbiorca": "Twoja Firma Sp. z o.o.", "Nr płatności": "payment-2026-0001" } }' ``` | Pole | Wymagane | Opis | | ------------- | -------- | ------------------------------------------------------------------------------------------- | | `externalId` | Tak | Identyfikator płatności w Twoim systemie. Wyświetlany w panelu Paymove. | | `imageFormat` | Tak | Format grafiki kodu QR: `svg` lub `png`. | | `bannerType` | Tak | Typ banera/karty (np. `ticket`) - wpływa na prezentację. | | `price` | Tak | Kwota w groszach (np. `15000` = 150,00 PLN). | | `details` | Nie | Dowolne pary klucz-wartość opisujące płatność - wyświetlane płacącemu na stronie płatności. | Wartości z `details` są wyświetlane płacącemu na stronie płatności - klucz to etykieta, a wartość to prezentowany tekst. Płatność może dotyczyć czegokolwiek: opisz ją tak, żeby płacący wiedział, za co płaci (np. „Tytuł płatności", „Za co", „Odbiorca"). ## Odpowiedź: kod QR z linkiem płatności Odpowiedź (200) to **binarna zawartość obrazka** z kodem QR w formacie wskazanym w `imageFormat` (SVG lub PNG), zwracana z nagłówkiem `Content-Type: application/octet-stream`. To nie jest JSON - zapisz body odpowiedzi bezpośrednio do pliku, np.: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --request POST \ --url https://gateway-api.sandbox.paymove.io/api/pay/product/{productId}/subproduct \ --header 'Content-Type: application/json' \ --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ --data '{ ... }' \ --output link-platnosci.png ``` Grafika zawiera gotowy do użycia baner z kodem QR przekierowującym do płatności (dla `png` np. 300x780 px). Możesz go wysłać e-mailem lub SMS-em, umieścić na wydruku albo wyświetlić na ekranie - płacący po zeskanowaniu (lub kliknięciu linku) trafia bezpośrednio do opłacenia należności. ## Co dalej? Dodaj webhook i otrzymuj automatyczne powiadomienia o każdej zakończonej płatności. Pełna dokumentacja endpointów PAY API: cennik, formularze, personalizacja UI i webhooki. # QR kody Source: https://docs.paymove.io/qr-codes Kody QR do inicjowania płatności Paymove Kody QR umożliwiają szybkie inicjowanie płatności - klient skanuje kod i zostaje przekierowany na checkout Paymove. ## Generowanie kodu QR Kod QR powinien zawierać `redirectUrl` otrzymany z API po utworzeniu płatności. Klient skanuje kod, otwiera checkout i finalizuje płatność. ## Wytyczne * Minimalny rozmiar kodu QR: **120x120 px** * Zachowaj białą ramkę (quiet zone) wokół kodu * Kod powinien być czytelny i kontrastowy * Testuj skanowanie na różnych urządzeniach przed wdrożeniem # Quickstart Source: https://docs.paymove.io/quickstart Od zera do pierwszej płatności — komplet kodu do skopiowania: utworzenie płatności, przekierowanie i weryfikacja webhooka. Ta strona prowadzi przez kompletną integrację bramki płatniczej: utworzenie płatności, przekierowanie klienta i bezpieczną obsługę powiadomienia o zapłacie. Kod jest niezależny od frameworka - przykłady w `curl` i Node.js. ## Czego potrzebujesz | Dane | Skąd | | ------------------------ | ------------------------------------------------------------------------------ | | `PAYMOVE_API_KEY` | Klucz `sk_test_…` wygenerowany w [Panelu Paymove](https://panel.paymove.io) | | `PAYMOVE_PRODUCT_ID` | UUID Twojego sklepu, otrzymany przy tworzeniu produktu | | `PAYMOVE_WEBHOOK_SECRET` | Pole `signingSecret` z odpowiedzi na rejestrację webhooka | | Publiczny adres webhooka | URL po Twojej stronie, dostępny z internetu - lokalnie np. przez tunel `ngrok` | Utworzenie produktu i rejestracja webhooka to czynności jednorazowe. Jeśli jeszcze ich nie wykonałeś, zacznij od [Konfiguracji](/webhooks) i wróć tutaj. Wszystkie wywołania wykonuj po stronie serwera. Klucz API nie może trafić do kodu frontendowego, a żądanie z przeglądarki zostanie odrzucone przez CORS. ## 1. Utwórz płatność ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl --request POST \ --url https://gateway-api.sandbox.paymove.io/api/pay/product/$PAYMOVE_PRODUCT_ID/subproduct/pricing \ --header 'Content-Type: application/json' \ --header "X-API-KEY: $PAYMOVE_API_KEY" \ --data '{ "price": 1000, "externalId": "order-123", "details": { "returnUrl": "https://twoj-sklep.pl/platnosc/powrot", "productName": "Koszulka sportowa", "email": "klient@example.com" } }' ``` Odpowiedź: ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "redirectUrl": "https://checkout.sandbox.paymove.io/891412c8-8717-4449-9543-e34112bec470?externalId=ec6RtwTZKb" } ``` W Node.js: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} async function createPayment(orderId, amountPln) { const response = await fetch( `https://gateway-api.sandbox.paymove.io/api/pay/product/${process.env.PAYMOVE_PRODUCT_ID}/subproduct/pricing`, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": process.env.PAYMOVE_API_KEY, }, body: JSON.stringify({ price: Math.round(amountPln * 100), // grosze, zawsze liczba całkowita externalId: orderId, details: { returnUrl: "https://twoj-sklep.pl/platnosc/powrot", productName: "Koszulka sportowa", }, }), } ); if (!response.ok) { const error = await response.json(); // { status, message } throw new Error(`Paymove ${response.status}: ${error.message}`); } const { redirectUrl } = await response.json(); return redirectUrl; } ``` `price` musi być liczbą całkowitą w groszach. Wartość `12.99` zostanie po cichu obcięta do **12 groszy**, a API i tak zwróci `200`. Stąd `Math.round(kwota * 100)` w przykładzie. Zapisz u siebie parametr `externalId` z otrzymanego `redirectUrl` (tutaj `ec6RtwTZKb`). To wygenerowany przez Paymove skrót płatności - inny niż Twój `externalId` - i to nim sprawdzisz później status. **Pracujesz w Node.js lub TypeScripcie?** Zamiast ręcznego `fetch` użyj oficjalnego SDK - `npm install @paymove-io/sdk`. Pakiet jest na licencji MIT, nie ma żadnych zależności, wymaga Node 18+ i zawiera typy TypeScript. Sam składa zagnieżdżone body żądania i zwraca typowane błędy. Szczegóły: [SDK JavaScript](/sdk/javascript). SDK **nie zawiera** funkcji weryfikacji podpisu webhooka - krok 3 piszesz samodzielnie niezależnie od wybranej drogi. ## 2. Przekieruj klienta ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} res.redirect(303, redirectUrl); ``` Klient trafia na checkout Paymove, wybiera metodę płatności i finalizuje transakcję. Po zakończeniu widzi ekran potwierdzenia z przyciskiem „Wróć do sklepu" - i dopiero kliknięcie przenosi go pod adres podany w `details.returnUrl`. Powrót na `returnUrl` **nie oznacza, że płatność się powiodła** - i nie musi w ogóle nastąpić. Klient, który zamknie kartę, nigdy tam nie trafi, a sam adres można otworzyć bezpośrednio, bez płacenia. Na tej stronie wyświetl jedynie komunikat „przetwarzamy płatność" - zamówienie realizuj dopiero po webhooku. ## 3. Odbierz i zweryfikuj webhook To jedyne wiarygodne potwierdzenie zapłaty. Poniższy handler robi cztery rzeczy: pobiera surowe body, weryfikuje podpis, odpowiada natychmiast i realizuje zamówienie idempotentnie. ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} const express = require("express"); const crypto = require("crypto"); const app = express(); function verifyWebhook(secret, signatureHeader, rawBody) { if (!signatureHeader) return false; const [tPart, signature] = signatureHeader.split(",", 2); if (!tPart?.startsWith("t=") || !signature?.startsWith("v1=")) return false; const timestamp = tPart.slice(2); // Okno tolerancji 5 minut - ochrona przed powtórzeniem starego żądania const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)); if (!Number.isFinite(age) || age > 300) return false; const expected = "v1=" + crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`, "utf8") .digest("base64"); // timingSafeEqual rzuca wyjątkiem przy różnej długości - sprawdź ją najpierw if (expected.length !== signature.length) return false; return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } app.post( "/api/payments/webhook", express.raw({ type: "application/json" }), // surowe body - bez tego podpis się nie zgodzi async (req, res) => { const rawBody = req.body.toString("utf8"); if (!verifyWebhook(process.env.PAYMOVE_WEBHOOK_SECRET, req.get("X-Paymove-Signature"), rawBody)) { return res.status(401).json({ error: "invalid signature" }); } const event = JSON.parse(rawBody); // Odpowiedz natychmiast - Paymove nie stosuje limitu czasu na tym wywołaniu res.status(200).json({ status: "ok" }); // Realizacja asynchroniczna i idempotentna const orderId = event.externalId ?? event.orderId; try { await fulfillOrderOnce(orderId); } catch (err) { console.error("fulfilment failed", orderId, err); } } ); ``` Twój serwer musi odpowiedzieć kodem równym `expectedCode` (domyślnie `200`) - treść odpowiedzi nie jest sprawdzana. Realizuj zamówienie **idempotentnie**, po `externalId`. Paymove może doręczyć to samo powiadomienie ponownie, a podwójna realizacja oznacza wysłanie towaru dwa razy. Domyślnie webhook przychodzi wyłącznie dla statusu `COMPLETED`. Jeśli potrzebujesz powiadomień także o anulowaniach i błędach, napisz na [integration@paymove.io](mailto:integration@paymove.io). Pełen opis: [Statusy płatności](/payment-status). ## 4. Awaryjne sprawdzenie statusu Jeśli klient wrócił na `returnUrl`, a webhook jeszcze nie dotarł, możesz odpytać o status. Użyj skrótu płatności zapisanego w kroku 1: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} curl "https://pay-api.sandbox.paymove.io/api/payment/product/$PAYMOVE_PRODUCT_ID/subproduct/ec6RtwTZKb/status" ``` Zwróć uwagę na adres: to zapytanie obsługuje **inny host** (`pay-api.sandbox.paymove.io`, na produkcji `pay-api.paymove.io`) niż tworzenie płatności. Przez `gateway-api` ta ścieżka w ogóle nie jest routowana i zwróci `404` z tekstem `No route found for: GET …`. Endpoint nie wymaga klucza API. ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": "COMPLETED", "orderId": "PAY1784798914400" } ``` Traktuj to jako uzupełnienie, a nie zamiennik webhooka - to on jest źródłem prawdy o płatności. ## Zanim wypuścisz na produkcję * Zmień adres bazowy na `https://api.paymove.io` i klucz na `sk_live_…`. * Sprawdź, czy `externalId` jest unikalny dla każdego zamówienia - ponowne użycie zwróci starą płatność ze starą kwotą. * Upewnij się, że kwota trafiająca do `price` powstaje po stronie serwera, a nie przychodzi z przeglądarki. * Ustaw `retries` przy rejestracji webhooka - domyślnie wynosi `0`, czyli brak ponowień. ## Co dalej Wszystkie parametry, pełne odpowiedzi i zmiana kwoty płatności. Kod w Node.js, Pythonie i Javie oraz wektor testowy. Co znaczy każdy błąd i jak go obsłużyć. Gotowy klient dla Node.js. # REST API Source: https://docs.paymove.io/rest-api Tworzenie płatności w bramce przez zwykłe wywołanie HTTP — pełny request, nagłówki i odpowiedź. REST API pozwala tworzyć płatności w ramach głównego produktu. Każde wywołanie tworzy płatność, którą klient może opłacić poprzez otrzymany `redirectUrl`. Przed rozpoczęciem integracji przez REST API upewnij się, że masz skonfigurowany produkt i webhook. Przejdź do [Konfiguracja](/webhooks), aby wykonać wymagane kroki. Wywołuj API wyłącznie po stronie serwera. Żądanie wysłane ze strony Twojego sklepu zostanie odrzucone - a klucz i tak nigdy nie może trafić do kodu frontendowego. ## 1. Endpoint ``` POST https://gateway-api.sandbox.paymove.io/api/pay/product/{productId}/subproduct/pricing ``` | Środowisko | Adres bazowy | | ---------- | ---------------------------------------- | | Sandbox | `https://gateway-api.sandbox.paymove.io` | | Produkcja | `https://api.paymove.io` | ### Nagłówki | Nagłówek | Wartość | | -------------- | ------------------------------------------------------------------ | | `Content-Type` | `application/json` | | `X-API-KEY` | Twój klucz API - `sk_test_…` w sandboxie, `sk_live_…` na produkcji | ## 2. Przykładowe wywołanie ```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", "email": "klient@example.com", "locale": "pl-PL", "triggerPayment": "BLIK", "qrStepEnabled": true } }' ``` To samo w Node.js: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} const url = `https://gateway-api.sandbox.paymove.io/api/pay/product/${productId}/subproduct/pricing`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": process.env.PAYMOVE_API_KEY, }, body: JSON.stringify({ price: Math.round(10.0 * 100), // zawsze 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, }, }), }); if (!response.ok) { const error = await response.json(); // { status, message } throw new Error(`Paymove ${response.status}: ${error.message}`); } const data = await response.json(); console.log(data.redirectUrl); ``` ### Parametry body | Pole | Wymagane | Opis | | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `price` | Tak | Kwota w groszach jako **liczba całkowita** (`1000` = 10,00 PLN) | | `externalId` | Tak | Unikalny identyfikator zamówienia po Twojej stronie | | `details.returnUrl` | Tak | URL powrotu klienta po płatności | | `details.productName` | Nie | Nazwa produktu widoczna na checkoucie | | `details.email` | Nie | Adres e-mail klienta, uzupełniany na checkoucie | | `details.locale` | Nie | Nadpisuje locale z głównego produktu (np. `pl-PL`) | | `details.recipient` | Nie | Nazwa odbiorcy wyświetlana na checkoucie | | `details.triggerPayment` | Nie | Metoda płatności wybrana z góry w checkoucie (Google Pay i Apple Pay startują automatycznie) | | `details.qrStepEnabled` | Nie | Kod QR na desktopie - klient kończy płatność na telefonie | | `details.autoclose` | Nie | Po ilu ms od udanej płatności checkout sam wraca na `details.returnUrl`. Odliczanie widać w przycisku „Wróć do sklepu", a interakcja klienta je anuluje. W widgecie zamiast przekierowania zamyka się modal. Brak pola = bez auto-powrotu | **`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)`. Bramka rozlicza wyłącznie w **PLN** - w żądaniu nie ma pola waluty. Pole `currency`, jeśli je wyślesz, zostanie zignorowane. `details` to swobodny obiekt - możesz przekazać w nim własne pola, a Paymove je zachowa. Checkout odczytuje jednak tylko: `returnUrl`, `redirectUrl`, `productName`, `email`, `locale`, `orderId`, `recipient`, `triggerPayment`, `qrStepEnabled` i `autoclose`. Pozostałe pola (np. `customerId`) są wyłącznie przekazywane na wylot i nigdzie się nie wyświetlają. Nieznane pola w body są **po cichu ignorowane**, a API zwraca `200`. Wysłanie `amount` zamiast `price` albo `returnUrl` na najwyższym poziomie zamiast w `details` nie zgłosi błędu - płatność powstanie z niekompletnymi danymi. Trzymaj się dokładnie powyższej struktury. ### Odpowiedź ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "redirectUrl": "https://checkout.sandbox.paymove.io/891412c8-8717-4449-9543-e34112bec470?externalId=ec6RtwTZKb" } ``` | Pole | Opis | | ------------- | --------------------------------------------------------------------------------- | | `redirectUrl` | Adres checkoutu - przekieruj klienta pod ten URL w celu sfinalizowania płatności | Odpowiedź ma status HTTP `200` (nie `201`). Parametr `externalId` w zwróconym adresie (tutaj `ec6RtwTZKb`) to **wygenerowany przez Paymove 10-znakowy skrót płatności**, a nie Twój `externalId` (`order-123`). Zapisz go u siebie - posługujesz się nim przy [sprawdzaniu statusu płatności](/payment-status). ### Błędy Wszystkie błędy mają kształt `{"status": , "message": ""}`. Rozgałęziaj logikę **po statusie HTTP**, nigdy po treści `message`. ```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} { "status": 401, "message": "Invalid API key" } ``` Pełna lista: [Kody błędów](/errors). ### Wskazanie metody płatności Pole `details.triggerPayment` z góry wybiera metodę płatności w checkoucie, dzięki czemu klient nie musi jej szukać na liście. | Wartość | Metoda | Zachowanie | | ------------- | ------------------ | ---------------------------------------------- | | `GPAY` | Google Pay | Zaznaczenie **i** automatyczny start płatności | | `APAY` | Apple Pay | Zaznaczenie **i** automatyczny start płatności | | `BLIK` | BLIK | Samo zaznaczenie metody | | `TRANSFER` | Przelew tradycyjny | Samo zaznaczenie metody | | `PAY_BY_LINK` | Pay by Link | Samo zaznaczenie metody | | `PAYPO` | PayPo | Samo zaznaczenie metody | Nieznana wartość jest pomijana - checkout zachowa się wtedy standardowo. Automatyczny start - czyli otwarcie płatności tak, jakby klient kliknął **Zapłać** - dotyczy **wyłącznie Google Pay i Apple Pay**. Przy pozostałych czterech metodach kafelek jest tylko zaznaczony, a klient klika **Zapłać** sam. Nie buduj procesu, który zakłada, że BLIK czy PayPo wystartują same. Nawet dla portfeli auto-start wymaga, żeby checkout znał adres e-mail klienta - przekaż go w `details.email`. Nie zadziała też w [widgecie osadzonym w modalu](/sdk/widget) ani gdy metoda nie jest dostępna dla Twojego produktu. W każdym z tych przypadków metoda zostaje jedynie zaznaczona. Auto-start działa wyłącznie przy pierwszym wyświetleniu checkoutu. Gdy klient sam wybierze metodę płatności, nie uruchomi się ponownie - aż do odświeżenia strony. Google Pay i Apple Pay otwierają natywne okno przeglądarki, które zwykle wymaga gestu klienta - automatyczne uruchomienie może zostać przez nią zablokowane. Klient zobaczy wtedy standardowy ekran płatności i opłaci zamówienie ręcznie. ### Krok z kodem QR Pole `details.qrStepEnabled` włącza dodatkowy ekran startowy checkoutu na desktopie: zamiast formularza płatności klient widzi kod QR, skanuje go telefonem i kończy płatność na nim. Przydaje się przy BLIK-u i portfelach dostępnych wyłącznie na telefonie. Krok jest opcjonalny - pominięcie pola albo `false` oznacza, że checkout od razu pokazuje formularz płatności. Kod QR pojawia się wyłącznie na desktopie. Na telefonie oraz przy ustawionym `details.triggerPayment` checkout pomija ten krok niezależnie od wartości pola. ## 3. Przekierowanie klienta ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} window.location.href = data.redirectUrl; ``` Po zakończonej płatności checkout pokazuje ekran potwierdzenia z przyciskiem „Wróć do sklepu". Dopiero kliknięcie tego przycisku przenosi klienta pod adres podany w `details.returnUrl` - do URL-a nie są doklejane żadne parametry. Powrót na `returnUrl` **nie jest potwierdzeniem płatności** - i wcale nie musi nastąpić. Klient, który zamknie kartę po zapłaceniu, nigdy nie trafi na Twój adres, a sam `returnUrl` to zwykły publiczny URL, który można otworzyć bez płacenia. Zamówienie realizuj wyłącznie po otrzymaniu i [zweryfikowaniu webhooka](/webhook-signature). ## 4. Powtórne użycie `externalId` `externalId` jest kluczem płatności po stronie Paymove. Ponowne wysłanie żądania z tym samym `externalId` **nie tworzy nowej płatności ani nie zwraca błędu** - API odpowiada `200` i zwraca `redirectUrl` płatności utworzonej wcześniej. Przy powtórzeniu **nowa wartość `price` i pozostałe pola są po cichu odrzucane**. Jeśli ponowisz żądanie ze skorygowaną kwotą, obowiązywać będzie kwota pierwotna, a odpowiedź niczym tego nie zasygnalizuje. Dla każdego zamówienia używaj nowego, unikalnego `externalId`. Aby zmienić kwotę istniejącej płatności, użyj osobnego wywołania: ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} 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 }' ``` Wywołanie aktualizuje wyłącznie `price` - pozostałe pola są pomijane. ## 5. Co dalej Obowiązkowy krok przed realizacją zamówienia. Statusy, payload webhooka i sprawdzanie stanu płatności. Pełna lista błędów API i sposoby ich obsługi. Gotowy klient dla Node.js. # SDK Source: https://docs.paymove.io/sdk/javascript Pakiet @paymove-io/sdk dla Node.js — instalacja, konfiguracja klienta i tworzenie płatności. SDK umożliwia merchantowi tworzenie płatności w ramach głównego produktu. Każda płatność zawiera kwotę, identyfikator zewnętrzny (`externalId`), adres powrotu po płatności (`returnUrl`) i opcjonalne dane klienta. Po utworzeniu płatności SDK zwraca `redirectUrl` do checkoutu, na którym klient może sfinalizować płatność. Przed rozpoczęciem integracji przez SDK upewnij się, że masz skonfigurowany produkt i webhook. Przejdź do [Konfiguracja](/webhooks), aby wykonać wymagane kroki. SDK wywołuj wyłącznie po stronie serwera. Klucz API nigdy nie może trafić do kodu frontendowego, a wywołanie z przeglądarki i tak zostanie odrzucone przez CORS. ## 1. Instalacja ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} npm install @paymove-io/sdk ``` Nie przypinaj wersji - instaluj najnowszą. ## 2. Przykładowe wywołanie ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} import { PaymoveClient } from "@paymove-io/sdk"; const client = new PaymoveClient({ apiKey: process.env.PAYMOVE_API_KEY, // sk_test_... (sandbox) lub sk_live_... (produkcja) productId: "2f6c19e8-84a7-4f50-b950-8d5a05e0bbf2", environment: "sandbox", }); const response = await client.createPayment({ amount: 1000, // liczba całkowita w groszach: 1000 = 10,00 PLN currency: "PLN", // wymagane przez SDK, ignorowane przez API - zawsze "PLN" externalId: "order-123", returnUrl: "https://merchant-shop.com/payment/success", productName: "Koszulka sportowa", customerId: "user-567", email: "klient@example.com", locale: "pl-PL", // opcjonalnie, nadpisuje locale z głównego produktu triggerPayment: "BLIK", // opcjonalnie, checkout od razu startuje tę metodę qrStepEnabled: true, // opcjonalnie, na desktopie checkout zaczyna od kodu QR autoclose: 10000, // opcjonalnie, ms — po tylu ms checkout sam wraca na returnUrl }); console.log(response.redirectUrl); ``` ### Parametry konfiguracji | Parametr | Opis | | ------------- | ------------------------------------------ | | `apiKey` | Klucz autoryzacyjny do API / SDK | | `productId` | UUID identyfikujący Twój produkt (sklep) | | `environment` | Środowisko: `"sandbox"` lub `"production"` | ### Parametry `createPayment` | Parametr | Opis | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `amount` | Kwota w groszach jako **liczba całkowita** (np. 1000 = 10,00 PLN) | | `currency` | Wymagane przez SDK, ale ignorowane przez API - bramka rozlicza wyłącznie w PLN. Podawaj `"PLN"` | | `externalId` | Unikalny identyfikator płatności po stronie merchanta | | `returnUrl` | URL powrotu klienta po płatności | | `productName` | Nazwa produktu widoczna na checkoucie | | `customerId` | Dowolna wartość przekazywana na wylot - checkout jej nie wyświetla | | `email` | Opcjonalnie - adres e-mail klienta, uzupełniany na checkoucie | | `locale` | Opcjonalnie - nadpisuje locale z głównego produktu | | `triggerPayment` | Opcjonalnie - metoda płatności wybrana z góry w checkoucie (Google Pay i Apple Pay startują automatycznie) | | `qrStepEnabled` | Opcjonalnie - kod QR na desktopie, klient kończy płatność na telefonie | | `autoclose` | Opcjonalnie - po ilu ms od udanej płatności checkout sam wraca na `returnUrl` (odliczanie w przycisku „Wróć do sklepu", interakcja klienta je anuluje) | Wszystkie parametry przekazujesz płasko, jednym poziomem - SDK samo składa z nich obiekt `details` wysyłany do API. Dowolne dodatkowe pola trafią tam razem z pozostałymi. `amount` musi być liczbą całkowitą. SDK sprawdza tylko, czy wartość jest liczbą większą od zera, więc `49.99` przejdzie walidację, a API **po cichu obetnie ją do 49 groszy**. Przeliczaj złotówki przez `Math.round(kwota * 100)`. ### Automatyczne uruchomienie metody płatności Pole `triggerPayment` z góry wybiera metodę płatności w checkoucie, dzięki czemu klient nie musi jej szukać na liście. | Wartość | Metoda | Zachowanie | | ------------- | ------------------ | ---------------------------------------------- | | `GPAY` | Google Pay | Zaznaczenie **i** automatyczny start płatności | | `APAY` | Apple Pay | Zaznaczenie **i** automatyczny start płatności | | `BLIK` | BLIK | Samo zaznaczenie metody | | `TRANSFER` | Przelew tradycyjny | Samo zaznaczenie metody | | `PAY_BY_LINK` | Pay by Link | Samo zaznaczenie metody | | `PAYPO` | PayPo | Samo zaznaczenie metody | Inna wartość kończy się błędem `PaymoveValidationError` (pole `triggerPayment`). Pominięcie pola lub `null` oznacza standardowy checkout z wyborem metody. Automatyczny start - czyli otwarcie płatności tak, jakby klient kliknął **Zapłać** - dotyczy **wyłącznie Google Pay i Apple Pay**. Przy pozostałych czterech metodach kafelek jest tylko zaznaczony. Nawet dla portfeli auto-start zadziała tylko wtedy, gdy checkout zna adres e-mail klienta - przekaż go w `email`. Nie zadziała też w [widgecie osadzonym w modalu](/sdk/widget) ani gdy metoda nie jest dostępna dla Twojego produktu. Auto-start działa wyłącznie przy pierwszym wyświetleniu checkoutu. Gdy klient sam wybierze metodę płatności, nie uruchomi się ponownie - aż do odświeżenia strony. Google Pay i Apple Pay otwierają natywne okno przeglądarki, które zwykle wymaga gestu klienta - automatyczne uruchomienie może zostać przez nią zablokowane. Klient zobaczy wtedy standardowy ekran płatności i opłaci zamówienie ręcznie. ### Krok z kodem QR Pole `qrStepEnabled` włącza dodatkowy ekran startowy checkoutu na desktopie: zamiast formularza płatności klient widzi kod QR, skanuje go telefonem i kończy płatność na nim. Przydaje się przy BLIK-u i portfelach dostępnych wyłącznie na telefonie. Krok jest opcjonalny - pominięcie pola albo `false` oznacza, że checkout od razu pokazuje formularz płatności. Kod QR pojawia się wyłącznie na desktopie. Na telefonie oraz przy ustawionym `triggerPayment` checkout pomija ten krok niezależnie od wartości pola. `qrStepEnabled` trafiło do typów SDK po wydaniu `0.2.0`. W starszej wersji pole nadal działa (SDK przekazuje nieznane klucze dalej), ale TypeScript go nie podpowie - wystarczy zaktualizować pakiet do najnowszej wersji. ### Automatyczny powrót do sklepu Po udanej płatności checkout pokazuje ekran potwierdzenia z przyciskiem „Wróć do sklepu". Pole `autoclose` (w milisekundach) sprawia, że ten powrót wykona się sam: pozostałe sekundy widać w przycisku (`Wróć do sklepu (10s)`), a po ich upływie klient trafia na `returnUrl`. W widgecie zamiast przekierowania zamyka się modal i wywoływany jest `onComplete`. Dowolna interakcja klienta - kliknięcie, dotknięcie, klawisz - anuluje odliczanie na stałe, więc nikt nie zostanie wyrzucony w połowie akcji, na przykład przy pobieraniu potwierdzenia PDF. Pominięcie pola oznacza, że potwierdzenie zostaje na ekranie do czasu, aż klient sam z niego wyjdzie. `autoclose` trafiło do typów SDK w wydaniu `0.3.0`. W starszej wersji pole nadal działa (SDK przekazuje nieznane klucze dalej), ale TypeScript go nie podpowie. ### Odpowiedź | Pole | Opis | | ------------- | --------------------------------------------------------------------------------- | | `redirectUrl` | Adres checkoutu - przekieruj klienta pod ten URL w celu sfinalizowania płatności | ## 3. Przekierowanie klienta ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} window.location.href = response.redirectUrl; ``` Po zakończonej płatności checkout pokazuje ekran potwierdzenia z przyciskiem „Wróć do sklepu", który przenosi klienta pod adres podany w `returnUrl`. Cały proces płatności jest bezobsługowy - Paymove zarządza checkoutem i przetwarzaniem, a Ty realizujesz zamówienie po zweryfikowanym [webhooku](/webhook-signature), nie po powrocie klienta. Zamiast przekierowania możesz otworzyć ten sam adres w modalu na stronie sklepu — opisuje to [Widget przeglądarkowy](/sdk/widget), który dokłada też gotowy przycisk i pasek z metodami płatności. # Widget przeglądarkowy Source: https://docs.paymove.io/sdk/widget sdk.js — przycisk i pasek Paymove na stronie sklepu oraz checkout otwierany w modalu, bez wychodzenia ze sklepu. Widget to jeden plik `sdk.js`, który dokłada do sklepu dwie rzeczy: * **element ``** — przycisk płatności albo pasek informacyjny z sygnetem Paymove i logotypami metod, gotowy do wstawienia w koszyku lub na liście metod płatności, * **modal z checkoutem** — ten sam adres, który dziś otwierasz przekierowaniem, wyświetlony w iframie na stronie sklepu. Widget nie tworzy płatności. Płatność powstaje po stronie serwera — przez [SDK Node.js](/sdk/javascript) albo [REST API](/rest-api) — a widget dostaje gotowy `redirectUrl` z odpowiedzi. Klucz API zostaje na serwerze. Widget przyjmuje wyłącznie `redirectUrl`, nigdy `apiKey`. Wywołanie API z przeglądarki i tak odrzuci CORS. ## 1. Podłączenie skryptu ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Skrypt rejestruje element `` i wystawia obiekt `window.Paymove`. Cały interfejs siedzi w Shadow DOM, więc style sklepu nie mieszają się ze stylami widgetu i odwrotnie. Przy `async` skrypt może dojechać po Twoim kodzie — poczekaj na `ready()`: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} await window.Paymove?.ready(); ``` Ścieżka `/v1/` to alias wersjonujący - ten sam plik serwowany jest też pod `/sdk.js`. Używaj `/v1/`, dzięki czemu ewentualna przyszła wersja `/v2/` nie zepsuje istniejących integracji. Adres skryptu odpowiada środowisku checkoutu. Build `sdk.js` osadza w modalu wyłącznie własną domenę, więc skrypt z produkcji nie otworzy checkoutu sandboxowego i odwrotnie — pobieraj go z tej samej domeny, z której przychodzi `redirectUrl`. ## 2. Element `` Najkrótsza wersja to sam znacznik w HTML: ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Element renderuje przycisk z sygnetem Paymove, a pod nim kartę z logotypami metod płatności. Konfigurujesz go atrybutami: | Atrybut | Wartości | Domyślnie | Opis | | ------------------ | -------------------------- | ----------------------------- | ------------------------------------------------------------------------- | | `mode` | `payment`, `readonly` | `payment` | `payment` to przycisk, `readonly` to sam pasek informacyjny | | `label` | dowolny tekst | `Zapłać` / `Płatności Online` | Napis na przycisku lub pasku | | `methods` | lista po przecinku | — | Metody, których logotypy pokazujesz | | `methods-label` | dowolny tekst | `Dostępne metody płatności` | Opis w karcie metod pod przyciskiem | | `size` | `small`, `medium`, `large` | `medium` | Wysokość przycisku: 40 / 48 / 56 px | | `variant` | `dark`, `light`, `outline` | `dark` | Wariant kolorystyczny przycisku | | `logo` | `black`, `white` | dobierany do wariantu | Wersja sygnetu Paymove | | `arrow` | `true`, `false` | `true` | Podwójna strzałka na końcu przycisku | | `full-width` | `true`, `false` | `false` | Rozciąga element na całą szerokość rodzica | | `interactive` | `true`, `false` | `false` | Tylko dla `readonly` — czyni pasek lub kafelek klikalnym | | `method` | kod metody, np. `BLIK` | — | Tylko dla `readonly` — zamiast paska renderuje kafelek pojedynczej metody | | `description` | dowolny tekst | opis z katalogu SDK | Kafelek metody — własny opis pod nazwą | | `hide-label` | `true`, `false` | `false` | Kafelek metody — chowa nazwę; logo jest zawsze | | `hide-description` | `true`, `false` | `false` | Kafelek metody — chowa opis | Tekst po znaku `·` renderuje się pogrubiony, więc `label="Zapłać · 149,00 zł"` da „Zapłać · **149,00 zł**". Widoczne są trzy pierwsze logotypy, reszta zwija się do znacznika `+N`. | Metoda | Wartość w `methods` / `method` | | ---------------------------- | ------------------------------ | | BLIK | `BLIK` | | Google Pay | `GPAY` | | Apple Pay | `APAY` | | Płatność kartą | `CARD` | | Przelew tradycyjny | `TRANSFER` | | Przelew online (Pay by Link) | `PAY_BY_LINK` | | PayPo | `PAYPO` | Sygnetu Paymove nie da się ukryć ani przemalować — do wyboru są dwie wersje: czarna i biała. Resztę wyglądu dostosujesz — patrz sekcja **Wygląd**. ## 3. Tryb readonly `mode="readonly"` renderuje sam pasek: sygnet, Twój tekst i logotypy metod. Domyślnie jest nieinteraktywny — to element informacyjny, taki jak pozycja „Płatności online" na liście metod dostawy i płatności w koszyku. ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Gdy pasek ma sam otwierać płatność, dodaj `interactive` i podepnij kontroler (sekcja niżej). Element emituje wtedy zdarzenie `paymove:click`, obsługuje `Enter` i spację, i wystawia poprawne role dla czytników ekranu. ### Kafelki pojedynczych metod Atrybut `method` zamienia pasek w **kafelek jednej metody**: logo, nazwa i opis z wbudowanego katalogu SDK — sklep nie utrzymuje własnych logotypów ani tekstów. Poniższe przykłady są żywe, renderuje je `sdk.js` załadowany na tej stronie:
```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Logo jest zawsze. Nazwę i opis możesz schować (`hide-label`, `hide-description`) albo nadpisać (`label`, `description`):
```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Kafelek domyślnie nie ma tła, ramki ani paddingu — wtapia się w wiersz Twojej listy metod, a wiersze, ramki i radiobuttony zostają po stronie sklepu. Zmiennymi CSS z sekcji **Wygląd** (`--paymove-bg`, `--paymove-border-color`, `--paymove-padding`, `--paymove-radius`) zrobisz z niego samodzielną kartę. Z atrybutem `interactive` kafelek jest klikalny i emituje `paymove:click` — przy schowanych tekstach nazwa metody zostaje w `aria-label`, więc czytniki ekranu widzą go poprawnie. ## 4. Otwarcie checkoutu w modalu Modalem steruje kontroler z `window.Paymove`: ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` `mount()` przejmuje kliknięcia w element i sam przełącza przycisk w stan ładowania na czas pobierania adresu. Jeśli `redirectUrl` masz już w momencie renderowania strony, podaj go zamiast `fetchCheckoutUrl`: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} const checkout = window.Paymove.createCheckout({ checkoutUrl: redirectUrl }); ``` `fetchCheckoutUrl` jest wygodniejsze: płatność powstaje dopiero w chwili kliknięcia, więc nie zostawiasz porzuconych płatności po klientach, którzy tylko oglądali koszyk. ### Konfiguracja `createCheckout` | Pole | Opis | | ------------------ | ------------------------------------------------------------ | | `checkoutUrl` | Gotowy `redirectUrl` z Twojego backendu | | `fetchCheckoutUrl` | Funkcja zwracająca `redirectUrl`, wywoływana przy kliknięciu | | `flow` | `modal` (domyślnie) albo `redirect` | | `triggerPayment` | Metoda uruchamiana od razu po otwarciu checkoutu | | `onReady` | Checkout zgłosił gotowość i modal pokazał treść | | `onSuccess` | Płatność zakończona powodzeniem — `{ externalId }` | | `onComplete` | Modal zamknięty po udanej płatności — `{ externalId }` | | `onCancel` | Modal zamknięty bez płatności | | `onError` | Błąd checkoutu — `{ code, message }` | Konfiguracja przyjmuje też wszystkie pola wyglądu przycisku (`label`, `methods`, `variant`…), więc możesz opisać element wyłącznie w JavaScripcie i zamontować go w pustym `
`. `externalId` w `onSuccess` i `onComplete` to **`details.orderId`**, jeśli przekazałeś je przy tworzeniu płatności - a 10-znakowy hash Paymove dopiero wtedy, gdy `orderId` nie ustawiłeś. Jeśli dopasowujesz zamówienie po tej wartości, ustawiaj `details.orderId` konsekwentnie, żeby zawsze dostawać ten sam identyfikator. ### Kontroler | Metoda | Działanie | | --------------- | --------------------------------------------------------------------------------------------- | | `mount(target)` | Podpina się pod selektor lub element — wstawia ``, jeśli w środku go nie ma | | `unmount()` | Odpina się od elementu | | `open()` | Otwiera modal (albo przekierowuje przy `flow: 'redirect'`) | | `close()` | Zamyka modal | | `update(patch)` | Podmienia część konfiguracji, np. `label` po zmianie kwoty w koszyku | | `destroy()` | Sprząta wszystko: modal, nasłuchy i element | Gdy chcesz tylko otworzyć modal — bez własnego przycisku — użyj skrótu: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} window.Paymove.openCheckout(redirectUrl, { onSuccess: ({ externalId }) => console.log('opłacone', externalId), }); ``` ## 5. Przekierowanie zamiast modala To ta sama konfiguracja z jednym polem więcej: ```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} window.Paymove.createCheckout({ checkoutUrl: redirectUrl, flow: 'redirect', }).mount('#pay'); ``` Klient trafia na checkout Paymove, a po płatności widzi ekran potwierdzenia z przyciskiem „Wróć do sklepu", który przenosi go pod `returnUrl` przekazany przy tworzeniu płatności. Modal i przekierowanie różnią się wyłącznie tym polem, więc możesz przełączać się między nimi bez zmian w reszcie integracji. W trybie `modal` `returnUrl` nie jest używany w ogóle - przycisk powrotu zamyka modal i wywołuje `onComplete`. Jeśli Twoja integracja opiera się na handlerze `returnUrl`, w modalu nigdy się on nie wykona. Źródłem prawdy pozostaje [webhook](/webhook-signature). ## 6. Jak zachowuje się modal * Na desktopie to panel o szerokości **398 px**, wyśrodkowany, na przyciemnionym tle. Wysokość dopasowuje się do treści checkoutu. * Przy szerokości okna do **640 px** modal wysuwa się z dołu jak natywny arkusz: maksymalnie **90% wysokości okna**, z belką do przeciągania — przeciągnięcie w dół zamyka płatność. * W trakcie ładowania widać statyczny sygnet Paymove. Krzyżyka wtedy nie ma — pojawia się dopiero, gdy checkout nie odezwie się przez 10 sekund, jako wyjście awaryjne. * Po załadowaniu zamykanie przejmuje nagłówek checkoutu. Zamknąć można też klawiszem `Esc` i kliknięciem w tło. * Strona pod modalem nie przewija się, a po zamknięciu wraca w to samo miejsce. * Na czas płatności checkout blokuje przewijanie tła i sam raportuje swoją wysokość, więc modal nie skacze przy zmianie kroku. * Modal potrafi zamknąć się sam po udanej płatności, ale sterujesz tym **przy tworzeniu subproduktu**, polem [`details.autoclose`](/rest-api#parametry-body) (w milisekundach), a nie z poziomu SDK. Checkout pokazuje wtedy odliczanie w przycisku „Wróć do sklepu" i po jego upływie zamyka modal, wywołując `onComplete`. Dowolna interakcja klienta anuluje odliczanie. Część banków w Pay by Link i PayPo zabrania osadzania swoich stron w iframie. Checkout otwiera je wtedy w nowym oknie i po powrocie wraca do modala — nie wymaga to niczego po stronie sklepu, ale nie blokuj wyskakujących okien we własnym kodzie. ## 7. Wygląd Kolory, promień i typografię przycisku ustawisz konfiguracją albo bezpośrednio zmiennymi CSS na elemencie: | Pole konfiguracji | Zmienna CSS | | ----------------- | ------------------------ | | `color` | `--paymove-bg` | | `textColor` | `--paymove-color` | | `borderColor` | `--paymove-border-color` | | `radius` | `--paymove-radius` | | `fontSize` | `--paymove-font-size` | | `padding` | `--paymove-padding` | | `fontFamily` | `--paymove-font-family` | ```html theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} ``` Widget dziedziczy krój pisma ze strony sklepu, dopóki nie ustawisz `--paymove-font-family`. ## 8. React i Next.js ```jsx theme={"theme":{"light":"one-light","dark":"one-dark-pro"}} 'use client'; import Script from 'next/script'; import { useEffect, useRef } from 'react'; export function PayButton({ amount }) { const host = useRef(null); const checkout = useRef(null); useEffect(() => { let cancelled = false; window.Paymove?.ready().then((sdk) => { if (cancelled) return; checkout.current = sdk.createCheckout({ label: `Zapłać · ${amount}`, methods: ['BLIK', 'GPAY', 'APAY'], fullWidth: true, fetchCheckoutUrl: async () => { const response = await fetch('/api/payments', { method: 'POST' }); const { redirectUrl } = await response.json(); return redirectUrl; }, onSuccess: ({ externalId }) => router.push(`/zamowienie/${externalId}`), }); checkout.current.mount(host.current); }); return () => { cancelled = true; checkout.current?.destroy(); }; }, [amount]); return ( <>