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