Gdy płatność InPost Pay w WooCommerce kończy się komunikatem „Load failed”, a zamówienie jest w sklepie, ale nie ma go w panelu InPost, przyczyną może być nieprzechwycony wyjątek ReserveStockException przy produkcie bez stanu. Wtyczka tworzy zamówienie, WooCommerce próbuje zarezerwować towar, którego nie ma, i rzuca wyjątek, którego nikt nie łapie. Opisujemy ten przypadek ze sklepu KwiatyDonice, w którym zajmujemy się WooCommerce. Do tego drugi problem, który wychodzi przy wdrożeniu InPost Pay: brak NIP-u i danych do faktury w panelu zamówienia. Na końcu lista testów do zrobienia przed uruchomieniem płatności i po każdej aktualizacji wtyczki.
Jak InPost Pay tworzy zamówienie w WooCommerce?
Oficjalna wtyczka InPost Pay nie przechodzi przez standardowy formularz zamówienia. Zamówienie powstaje przez jej własny endpoint REST, a potem wtyczka sama wywołuje hooki, które normalnie uruchamia checkout WooCommerce, w tym woocommerce_checkout_order_created.
Z tego wynikają dwie rzeczy. Kod motywu podpięty pod formularz checkoutu, np. pod woocommerce_checkout_create_order i dane z $_POST, dla tych zamówień się nie wykona. Z kolei funkcje rdzenia WooCommerce podpięte pod wywoływane ręcznie hooki działają w kontekście, którego wtyczka nie zawsze obsługuje. Oba problemy poniżej biorą się z tej konstrukcji.
Dlaczego płatność InPost Pay kończy się błędem „Load failed”?
Zgłoszenie od sklepu brzmiało tak: klienci nie mogą dokończyć płatności przez InPost. Objawy były powtarzalne:
- klient widział komunikat „Load failed” i płatność nie przechodziła,
- zamówienie pojawiało się w WooCommerce i w BaseLinkerze,
- w panelu InPost tego zamówienia nie było.
W logu fatal-errors WooCommerce przy każdym takim zamówieniu był wpis CRITICAL z Uncaught ReserveStockException. Oba zamówienia z dnia zgłoszenia zawierały produkt, którego stan wynosił zero.
Czym jest ReserveStockException i skąd się bierze?
WooCommerce podpina pod woocommerce_checkout_order_created funkcję wc_reserve_stock_for_order. Rezerwuje ona towar na czas płatności, zgodnie z ustawieniem „Przetrzymuj magazyn” (hold stock). Jeśli produkt ma włączone zarządzanie stanem, stan wynosi zero lub mniej, a zamówienia oczekujące (backorders) są wyłączone, klasa ReserveStock rzuca ReserveStockException.
W zwykłym checkoucie WooCommerce łapie ten wyjątek i pokazuje klientowi komunikat o braku towaru. We wtyczce metoda tworząca zamówienie ma blok try/catch, a metoda finalizująca, która wywołuje hooki, już nie. Wyjątek wychodzi więc jako błąd krytyczny PHP i endpoint wtyczki zwraca 500 zamiast czytelnego 409.
| Etap | Co się dzieje | Skutek |
|---|---|---|
| Utworzenie zamówienia | Wtyczka zapisuje zamówienie w bazie WooCommerce | Integracje, np. BaseLinker, mogą je już pobrać |
| Finalizacja | Wtyczka ręcznie wywołuje hook checkoutu, WooCommerce rezerwuje stan | Brak towaru: ReserveStockException |
| Odpowiedź do InPost | Nieprzechwycony wyjątek, HTTP 500 | InPost nie rejestruje zamówienia i płatność wisi |
Najgorsze są zamówienia osierocone: istnieją w sklepie i w BaseLinkerze, ale nie mają płatności ani odpowiednika w InPost. Ktoś w sklepie może je spakować, zanim zauważy, że nie są opłacone.
Jak naprawić ReserveStockException bez modyfikowania wtyczki?
Najprostsza poprawka to dopisanie try/catch w pliku wtyczki. Zniknęłaby przy pierwszej aktualizacji wtyczki, więc odpadła. Poprawkę umieściliśmy w motywie sklepu jako osobny moduł, oparty na filtrze WooCommerce woocommerce_order_hold_stock_minutes.
Dla zamówień opłacanych przez InPost Pay filtr zwraca 0. Przy zerowym czasie rezerwacji ReserveStock::reserve_stock_for_order kończy działanie od razu, zanim dojdzie do sprawdzania stanu i rzucenia wyjątku. Od WooCommerce 8.8.0 filtr dostaje obiekt zamówienia jako drugi argument, więc można sprawdzić metodę płatności. Uproszczona wersja:
add_filter( 'woocommerce_order_hold_stock_minutes', function ( $minutes, $order = null ) {
$inpost = [ 'inpost_pay_virtual_payment_gateway', 'inpost-izi' ];
if ( $order && in_array( $order->get_payment_method(), $inpost, true ) ) {
return 0;
}
return $minutes;
}, 10, 2 );Rezerwacja niczego tu nie chroniła. InPost prowadzi płatność własnym procesem i z mechanizmu hold stock w WooCommerce nie korzysta. Pozostałe metody płatności rezerwują stan tak jak wcześniej.
Uwaga: ta poprawka zatrzymuje awarię płatności, ale nie sprawdza dostępności. Koszyk InPost nadal przepuszcza do płatności produkty, których nie ma w magazynie i to trzeba rozwiązać osobno: walidacją koszyka po stronie sklepu albo zgłoszeniem do InPost.
Masz ten sam problem? Zamówienia InPost Pay lądują w sklepie bez płatności? Sprawdzimy logi i wdrożymy poprawkę odporną na aktualizacje wtyczki. Opisz sytuację →
Dlaczego zamówienia InPost Pay nie mają NIP-u ani danych do faktury?
Drugi problem zgłoszono przy wdrożeniu: zamówienia z tej płatności nie pokazywały w panelu ani informacji o fakturze, ani NIP-u. Wtyczka zapisuje dane faktury w metadanych zamówienia z prefiksem impost_invoice_, z literówką w nazwie. Nadpisuje też dane rozliczeniowe (firma, imię, nazwisko, adres) i wkleja NIP do notatki faktury.
Motyw sklepu szukał pól _iwantinvoice i _billing_nip, które ustawia wyłącznie zwykły checkout, przy zapisie danych z formularza. Wtyczka omija checkout, więc ich nie ustawia. Wtyczka zapisuje m.in. takie pola:
legal_form: forma prawna (osoba albo firma),tax_id_prefixitax_id: prefiks kraju i NIP,company_name,name,surname,- adres:
city,street,building,flat,postal_code,country_code, mailiadditional_information.
Wdrożyliśmy trzy zmiany. Pod woocommerce_admin_order_data_after_billing_address doszedł blok z danymi faktury, widoczny tylko wtedy, gdy zamówienie ma impost_invoice_legal_form. Pod hook wtyczki inpostpay_invoice_details, który nie miał żadnego słuchacza, podpięliśmy mapowanie na pola natywne: _iwantinvoice = 'yes' i _billing_nip złożony z prefiksu i numeru. Hook wywołuje się przed zapisem zamówienia, więc ręczny save() nie jest potrzebny, a eksporty i integracje czytające _billing_nip widzą NIP także z tych zamówień.
Przy okazji w starym bloku faktury znaleźliśmy błąd. Warunek sprawdzał, czy _iwantinvoice jest niepuste, a wartość „no” też jest niepustym napisem, więc blok faktury pokazywał się również klientom, którzy faktury nie chcieli. Teraz warunek porównuje ściśle z „yes”, a zamówienia InPost są ze starego bloku wyłączone, żeby dane nie wyświetlały się dwa razy.
Jak przetestować InPost Pay w WooCommerce przed i po wdrożeniu?
Scenariusza z niedostępnym produktem nie da się odtworzyć bez prawdziwej płatności InPost na produkcji. Weryfikacja poprawki polega więc na obserwacji: przy kolejnym takim zamówieniu w logu fatal-errors nie może pojawić się nowy ReserveStockException, a zamówienie musi pojawić się u InPost. Pozostałe testy da się zaplanować z góry:
- Zamówienie z fakturą na firmę: NIP widoczny w panelu zamówienia i zapisany w
_billing_nip. - Zamówienie bez faktury: blok faktury się nie pokazuje.
- Zamówienie przez zwykły checkout: stary blok faktury działa jak wcześniej.
- Porównanie zamówień InPost Pay w WooCommerce, BaseLinkerze i panelu InPost za ten sam okres, żeby wyłapać zamówienia osierocone.
- Przegląd logu wtyczki pod kątem powtarzających się błędów.
- Po aktualizacji wtyczki: powtórka punktów 1 i 2. Jeśli producent poprawi literówkę w prefiksie
impost_invoice_, mapowanie NIP-u trzeba będzie dostosować.
Co jeszcze trzeba zgłosić producentowi wtyczki InPost Pay?
Dwie rzeczy zostały poza zakresem tej poprawki. Pierwsza to wspomniany brak walidacji dostępności w koszyku InPost: produkt bez stanu nie powinien w ogóle dojść do płatności. Druga to log wtyczki, w którym co około 5 minut pojawiał się wpis TOKEN: Recursion limit reached, preventing infinite loop, czyli token przestał się odnawiać. Nie powodował on opisanych awarii zamówień, ale to osobny błąd wtyczki do zgłoszenia InPost.
Jak pracujemy z płatnościami i integracjami w sklepach WooCommerce?
W ASAPDevs utrzymujemy sklepy WooCommerce jako zewnętrzny zespół IT. W KwiatyDonice InPost Pay działa obok synchronizacji z BaseLinkerem i cenników hurtowych, a szerzej opisujemy ten sklep w case study KwiatyDonice. Przy błędach integracji zaczynamy od logów i kodu wtyczki, a poprawki trzymamy poza nią, żeby aktualizacja ich nie usunęła. Budową i integracjami zajmujemy się w ofercie sklepów WooCommerce, a stabilnością i błędami w logach w ramach optymalizacji WooCommerce.
