Integracje płatności: webhooks, retry, edge-case’y i jak nie wtopić na produkcji

Integracja bramki płatniczej wygląda prosto tylko na slajdach. W praktyce, gdy pojawiają się opóźnione webhooki, podwójne callbacki, timeouty po stronie PSP i użytkownik odświeżający checkout pięć razy, zaczyna się prawdziwy test architektury. Ten materiał to praktyczny playbook: co przygotować przed wdrożeniem, jak projektować retry i idempotencję, jak przejść przez edge-case’y bez strat finansowych i bez pożaru na produkcji.

Dlaczego integracje płatności psują się w najmniej wygodnym momencie

System płatności to nie jedna transakcja i jeden status. To sekwencja zdarzeń wykonywanych asynchronicznie przez różne systemy: frontend, backend, operator płatności, bank, czasem antyfraud i ERP. Każdy z tych elementów ma własne timeouty, kolejki i mechanizmy ponowień. Dlatego “zapłacone” nie zawsze oznacza “od razu potwierdzone w Twojej bazie”.

  • Webhook może przyjść z opóźnieniem lub kilka razy.
  • Użytkownik może wrócić z payment page zanim webhook dotrze.
  • Provider może zwrócić 200 w API, ale rozliczenie nastąpi później.
  • Sieć może przerwać połączenie po obciążeniu karty, ale przed odpowiedzią.

Wniosek: traktuj płatności jako proces eventual consistency, nie pojedynczy request/response.

Minimalna architektura, która wytrzyma produkcję

1. Jedno źródło prawdy dla stanu płatności

Trzymaj osobną tabelę/encję payment_transactions z historią statusów i eventów. Zamówienie może mieć status biznesowy, ale transakcja płatnicza powinna mieć własny lifecycle: created, pending, authorized, captured, failed, refunded, chargeback.

2. Idempotency key na każdym krytycznym kroku

Przy tworzeniu płatności, capture, refund i obsłudze webhooków stosuj klucz idempotencji. Dzięki temu ponowione żądanie nie wykona operacji drugi raz. Klucz powinien być stabilny (np. orderId + operation + attemptScope) i logowany.

3. Inbox dla webhooków + worker asynchroniczny

Endpoint webhooka nie powinien robić ciężkiej logiki. Najpierw: walidacja podpisu, zapis eventu do inboxu, szybkie 2xx. Dopiero worker przetwarza event z retriable queue. To radykalnie zmniejsza ryzyko timeoutów i utraty zdarzeń.

4. Outbox dla zdarzeń biznesowych

Po zmianie statusu płatności publikuj zdarzenie (np. do kolejki lub event busa) przez mechanizm outbox. Dzięki temu mail, faktura czy odblokowanie usługi nie “zgubią się” przy awarii po commitcie.

Webhooki: jak zrobić to dobrze

Weryfikacja autentyczności

  • Sprawdzaj podpis HMAC / certyfikaty zgodnie z dokumentacją PSP.
  • Waliduj timestamp i okno czasowe (ochrona przed replay attack).
  • Whitelistuj tylko wymagane źródła, jeśli dostawca to wspiera.
  • Nigdy nie ufaj polom typu status=success bez pełnej weryfikacji.

Dokładnie raz vs co najmniej raz

Większość providerów dostarcza webhooki semantyką “co najmniej raz”. To oznacza duplikaty i odwróconą kolejność. Twoja logika musi być odporna na oba przypadki:

  • Przechowuj providerEventId i blokuj ponowne przetworzenie.
  • Wprowadzaj monotoniczne przejścia statusów (np. nie cofaj captured do pending).
  • Dla konfliktów stosuj reguły priorytetu statusu i timestamp źródłowy.

Szybka odpowiedź HTTP

Webhook endpoint powinien odpowiadać w 100–300 ms. Nie rób tam zapytań do zewnętrznych systemów. Każda sekunda opóźnienia zwiększa szansę retry po stronie PSP, a więc lawinę duplikatów.

Retry: strategia, która nie robi DDoS na własny backend

Zasady retry dla wywołań do PSP

  • Retry tylko dla błędów przejściowych: 429, 5xx, timeout, connection reset.
  • Nie retry’uj bezwarunkowo błędów biznesowych (np. karta odrzucona).
  • Używaj exponential backoff + jitter (np. 1s, 2s, 4s, 8s ± losowy offset).
  • Limit liczby prób i dead-letter queue po przekroczeniu.

Retry dla webhook workerów

Jeżeli przetwarzanie webhooka zależy od innych usług (ERP, CRM), trzymaj retry niezależnie od przyjęcia webhooka. Sam webhook został przyjęty poprawnie; retry dotyczy tylko dalszych kroków integracyjnych.

Circuit breaker i ochrona budżetu błędów

Gdy provider ma awarię, agresywne retry może zabić Twój system. Włącz circuit breaker: po przekroczeniu progu błędów przechodzisz w tryb degradacji, ograniczasz ruch i dajesz klarowną informację użytkownikowi.

Najczęstsze edge-case’y i gotowe reguły

Edge-case 1: klient zapłacił, ale wrócił z błędem

Nie finalizuj zamówienia wyłącznie na podstawie redirectu z frontendu. Redirect to sygnał pomocniczy. Finalizacja powinna następować po potwierdzonym webhooku albo aktywnym reconciliation przez API provider.

Edge-case 2: dwa webhooki “success”

Jeżeli drugi webhook dotyczy tego samego providerPaymentId, odpowiedz 2xx, ale nic nie zmieniaj. Zaloguj “duplicate ignored”. Brak idempotencji tutaj = podwójna aktywacja usługi.

Edge-case 3: częściowy refund i chargeback

Modeluj refundy jako osobne rekordy, nie jako prostą zmianę stanu płatności. Chargeback może przyjść tygodnie później i dotyczyć tylko części kwoty. Bez precyzyjnego modelu księgowość i support utkną.

Edge-case 4: out-of-order events

Możesz dostać captured przed authorized. Rozwiązanie: event sourcing light — zapisuj każde zdarzenie i wyliczaj stan docelowy funkcją deterministyczną. Kolejność dostawy nie może łamać spójności biznesowej.

Checklist przed wdrożeniem produkcyjnym

  • Idempotency key dla create/capture/refund/webhook processing.
  • Weryfikacja podpisu webhooka i ochrona przed replay.
  • Inbox + worker + dead-letter queue.
  • Pełne logowanie: request id, provider id, order id, event id.
  • Monitoring SLA webhooka (opóźnienie, retry rate, fail rate).
  • Alerty na wzrost duplikatów i wzrost timeoutów.
  • Runbook incidentowy: kto robi co i w jakiej kolejności.
  • Plan reconciliation nocnego (porównanie statusów z PSP).

Observability i operacje: bez tego będziesz “zgadywać”

W płatnościach log bez korelacji jest bezużyteczny. Ustal jeden correlationId przenoszony przez frontend, backend, kolejki i webhook worker. Dodatkowo przechowuj:

  • providerPaymentId i providerEventId,
  • czas utworzenia i przetworzenia eventu,
  • powód błędu technicznego i biznesowego rozdzielnie,
  • liczbę ponowień i finalny wynik.

Dobry dashboard powinien odpowiadać na trzy pytania w 60 sekund: czy tracimy pieniądze, czy blokujemy zamówienia, czy problem jest po naszej stronie czy po stronie PSP.

Bezpieczeństwo i compliance w praktyce

Nawet jeśli używasz hosted checkout i nie dotykasz numerów kart, nadal masz obowiązki bezpieczeństwa:

  • Tokeny API i sekrety webhooków tylko w vault/secret managerze.
  • Rotacja kluczy + procedura awaryjna natychmiastowej podmiany.
  • Ścisłe role i audyt dostępu do panelu płatności.
  • Maskowanie danych osobowych w logach.
  • Testy penetracyjne endpointów integracyjnych.

Plan testów, który naprawdę wykrywa problemy

Testy kontraktowe

Buduj testy na payloadach webhooków od providerów i wersjonuj je. Każda zmiana w strukturze eventu powinna mieć test regresji.

Testy chaosowe (kontrolowane)

  • Symuluj timeouty i 5xx po stronie PSP.
  • Symuluj duplikaty webhooków (2x, 5x, 20x).
  • Symuluj odwróconą kolejność eventów.
  • Symuluj utratę połączenia po autoryzacji płatności.

Testy operacyjne

Przećwicz “game day”: awaria webhook endpointu przez 30 minut, a potem recovery i reprocessing backlogu. Jeśli nie masz runbooka po ćwiczeniu, nie jesteś gotowy na produkcję.

Podsumowanie

Największy błąd w integracjach płatności to traktowanie ich jak prostego API. Produkcyjnie wygrywa ten, kto projektuje pod opóźnienia, duplikaty i niepewność sieci. Wdroż idempotencję, asynchroniczne przetwarzanie webhooków, sensowny retry i twardą obserwowalność — a większość “niespodzianek” zamienisz w kontrolowane scenariusze operacyjne, zamiast kryzysu przy zamknięciu miesiąca.

FAQ

Czy webhook wystarczy jako jedyne źródło prawdy?

Najlepiej łączyć webhooki z okresowym reconciliation przez API dostawcy. Webhook jest szybki, reconciliation jest bezpiecznikiem.

Ile retry to “za dużo”?

To zależy od SLA i typu operacji, ale zwykle 5–8 prób z backoff i jitter oraz DLQ to rozsądny punkt startowy.

Czy warto mieć osobny zespół ownership dla płatności?

Tak, nawet mały “payments squad” skraca czas reakcji i redukuje koszt incydentów.

Zobacz też: Rescue Sprint: kiedy projekt wymaga ratunku i jak to robimy krok po kroku

Podobne wpisy

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

dwanaście + 16 =