InPost Pay sypie błędami w Twoim sklepie?Sklepy WooCommerce
← Blog
WooCommerce

InPost Pay w WooCommerce: wdrożenie, testy i błąd ReserveStockException

Zamówienie jest w WooCommerce i BaseLinkerze, ale nie w panelu InPost, a klient widzi „Load failed”. Skąd bierze się ReserveStockException, jak go obejść bez łatania wtyczki i jak pokazać NIP z InPost Pay.

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.

EtapCo się dziejeSkutek
Utworzenie zamówieniaWtyczka zapisuje zamówienie w bazie WooCommerceIntegracje, np. BaseLinker, mogą je już pobrać
FinalizacjaWtyczka ręcznie wywołuje hook checkoutu, WooCommerce rezerwuje stanBrak towaru: ReserveStockException
Odpowiedź do InPostNieprzechwycony wyjątek, HTTP 500InPost 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_prefix i tax_id: prefiks kraju i NIP,
  • company_name, name, surname,
  • adres: city, street, building, flat, postal_code, country_code,
  • mail i additional_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:

  1. Zamówienie z fakturą na firmę: NIP widoczny w panelu zamówienia i zapisany w _billing_nip.
  2. Zamówienie bez faktury: blok faktury się nie pokazuje.
  3. Zamówienie przez zwykły checkout: stary blok faktury działa jak wcześniej.
  4. Porównanie zamówień InPost Pay w WooCommerce, BaseLinkerze i panelu InPost za ten sam okres, żeby wyłapać zamówienia osierocone.
  5. Przegląd logu wtyczki pod kątem powtarzających się błędów.
  6. 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.

Źródła: WooCommerce, klasa ReserveStock (GitHub); WooCommerce, wc-stock-functions.php (GitHub); InPost Pay.
Kamil Perzowski
Kamil Perzowski
CEO i co-founder ASAPDevs. Programuje i utrzymuje sklepy, aplikacje webowe i serwery klientów. LinkedIn · Sklepy WooCommerce
Masz ten sam problem?

InPost Pay sypie błędami w Twoim sklepie?

Kamil Perzowski, ASAPDevs
Kamil Perzowski
CEO & Co-founder, ASAPDevs
Przejrzymy logi WooCommerce i wtyczki, znajdziemy zamówienia bez płatności i wdrożymy poprawkę, która przetrwa aktualizację InPost Pay.

Wysyłając formularz, przekazujesz nam dane, żebyśmy mogli odpowiedzieć. Szczegóły w polityce prywatności.

FAQ

Pytania i odpowiedzi

Dlaczego zamówienie InPost Pay jest w WooCommerce, ale nie ma go w panelu InPost?

+
Wtyczka zapisuje zamówienie w bazie sklepu, zanim InPost dostanie odpowiedź. Jeśli po drodze poleci nieprzechwycony wyjątek, na przykład ReserveStockException przy produkcie bez stanu, InPost dostaje błąd 500 i płatność nie przechodzi. W sklepie zostaje zamówienie bez płatności, które integracje typu BaseLinker zdążyły już pobrać.

Czy ustawienie czasu rezerwacji na 0 dla InPost Pay wyłącza kontrolę stanów magazynowych?

+
Nie. Filtr woocommerce_order_hold_stock_minutes wyłącza tylko tymczasową rezerwację towaru na czas płatności, i to wyłącznie dla tych zamówień. Stan nadal zmniejsza się po opłaceniu zamówienia, a InPost prowadzi płatność własnym procesem i rezerwacji z WooCommerce nie używa.

Gdzie InPost Pay zapisuje NIP i dane do faktury?

+
W metadanych zamówienia z prefiksem impost_invoice_ (z literówką, impost zamiast inpost), np. impost_invoice_tax_id i impost_invoice_company_name. Wtyczka nie ustawia pól, które zapisuje zwykły checkout, więc motyw albo integracja szukająca NIP-u w polu _billing_nip go nie znajdzie.

Czy poprawkę lepiej wpisać bezpośrednio do plików wtyczki?

+
Poprawka w plikach wtyczki zniknie przy najbliższej aktualizacji. Bezpieczniej trzymać ją w motywie albo we własnej wtyczce i opierać na hookach WooCommerce, bo wtedy aktualizacja wtyczki jej nie nadpisze.

Jak sprawdzić, czy InPost Pay nie zostawia zamówień bez płatności?

+
Porównaj te zamówienia w WooCommerce z listą u InPost za ten sam okres i przejrzyj log fatal-errors w WooCommerce, Status, Logi. Zamówienie obecne w sklepie, którego brakuje w panelu InPost, to sygnał, że odpowiedź do InPost się nie udała.

Przeczytaj też