Wróć do bloga

Zamknięty sklep B2B na WooCommerce: cztery decyzje architektoniczne

Zamknięty sklep B2B na WooCommerce wymaga decyzji o dostępie, stronach produktów, dostępności z ERP i akceptacji kont. Wtyczki nie podejmą ich za Ciebie.

Jakub Czechowski

Buduje strony i sklepy internetowe w JC Web Studio, prowadzi StackCompass – publikację o architekturze treści i decyzjach stackowych – i współorganizuje CMS Conf, konferencję o systemach treści.

/ / 6 min czytania

Zamknięty sklep B2B na WooCommerce nie jest zwykłym sklepem, do którego na końcu dodano ekran logowania. W opisanym wdrożeniu goście nie mogą przeglądać katalogu, strony pojedynczych produktów zostały celowo usunięte, a źródłem prawdy o dostępności jest system ERP, nie panel WordPressa. Te założenia są sprzeczne z częścią domyślnych zachowań WooCommerce, dlatego trzeba potraktować je jako reguły całego systemu, a nie zestaw ustawień interfejsu. Poniższe cztery decyzje pochodzą z działającego sklepu hurtowego opartego na własnym motywie potomnym.

Produkty takie jak B2BKing, Barn2 Wholesale Pro i rozszerzenia B2B z marketplace’u WooCommerce zazwyczaj wychodzą od podobnego modelu: standardowego katalogu z cenami zależnymi od roli i opcjonalnym ograniczeniem dostępu. W wielu sklepach ten model działa dobrze. Staje się niewygodny, gdy katalog jest z definicji prywatny, adres produktu nie uczestniczy w procesie zakupowym, a stan magazynowy pochodzi z zewnętrznego systemu. Wtedy zespół musi jawnie przejąć odpowiedzialność za cztery decyzje.

Dostęp do katalogu trzeba kontrolować na granicy żądania

Częsta implementacja wymagania „ukryj ceny przed gośćmi” filtruje woocommerce_get_price_html i zwraca pustą wartość albo komunikat o logowaniu. Ukrywa to jedno pole, nie katalog. Zdjęcia produktów, tytuły, slugi, etykiety dostępności, adresy kategorii i dane strukturalne nadal mogą zostać wyrenderowane, zindeksowane lub pobrane automatycznie.

W tym sklepie główna reguła dostępu działa w template_redirect z priorytetem 15. Jeśli niezalogowany użytkownik wywoła is_shop(), is_product_category(), is_product_tag(), is_cart(), is_checkout() albo listę życzeń, żądanie zostaje przekierowane do /my-account i zakończone, zanim wyrenderuje się szablon sklepu. Filtr woocommerce_get_price_html pozostaje dodatkowym zabezpieczeniem, ale przekierowanie wyznacza granicę bezpieczeństwa.

Szybki podgląd wymaga osobnej reguły, ponieważ żądania AJAX nie przechodzą przez tę samą ścieżkę szablonów. Akcja szybkiego podglądu dla niezalogowanych użytkowników zwraca HTTP 401. Gdy modal staje się głównym interfejsem produktu, jego endpoint pełni funkcję prywatnego API i musi egzekwować tę samą politykę dostępu co trasy katalogu.

Ceny zależne od roli są następnie nakładane przez woocommerce_product_get_price i woocommerce_product_get_sale_price. Filtr woocommerce_sale_flash ukrywa etykiety promocji dla najwyższego poziomu hurtowego, ponieważ jego cennik już uwzględnia wynegocjowane warunki. Przedstawianie ceny kontraktowej jako czasowej promocji wprowadzałoby kupującego w błąd.

Proces zakupowy nie potrzebuje strony produktu

WooCommerce traktuje /product/{slug} jako podstawowy interfejs produktu: umieszcza ten adres w mapie witryny, dodaje dane schema.org Product i łączy galerię, opis, warianty, produkty powiązane oraz opinie. To właściwy model dla zakupów B2C. W zamkniętym katalogu hurtowym zadaniem kupującego jest dodanie wielu SKU z poziomu listy kategorii. Osobna strona produktu tworzy kolejny adres, szablon i interfejs do zabezpieczenia, ale nie usprawnia tego procesu.

Druga funkcja podpięta do template_redirect z priorytetem 20 wykrywa is_product(), wywołuje $wp_query->set_404() oraz wysyła status_header(404) i nocache_headers(). Adres produktu celowo nie istnieje, zamiast przekierowywać do kategorii. Ma to znaczenie dla robotów wyszukiwarek, monitoringu i CDN: zasób nie został przeniesiony, lecz nie jest udostępniany w tym sklepie.

Stronę zastępuje modal szybkiego podglądu. Z listy kategorii kupujący otwiera galerię, opis, macierz wariantów i cenę zależną od roli, a następnie dodaje produkt do koszyka bez opuszczania listy. Dane do modalu dostarcza uwierzytelniona odpowiedź AJAX. Pozostaje jeden interfejs produktu do zabezpieczenia i przetestowania, a adresy używane przez kupujących ograniczają się do list katalogowych oraz konta.

To ta sama zasada, która pojawia się przy wyborze między rozrostem wtyczek a własną warstwą integracyjną: przed dostosowaniem interfejsu trzeba sprawdzić, czy proces rzeczywiście go potrzebuje. Tutaj usunięcie strony produktu zmniejsza publiczną część systemu i dopasowuje sklep do sposobu składania zamówień przez klientów hurtowych.

Widoczność produktu ma trzy niezależne wymiary

Zespoły synchronizujące WooCommerce z ERP często traktują widoczność produktu jak jedną wartość logiczną. WooCommerce udostępnia co najmniej trzy powiązane, ale niezależne stany:

  1. post_status w wp_posts: publish, draft, private albo pending. Określa, czy WordPress uwzględnia produkt w standardowych zapytaniach.
  2. _stock_status w post meta: instock, outofstock albo onbackorder. Reprezentuje dostępność używaną przez kontrolki zakupu i komunikaty o stanie.
  3. product_visibility: terminy taksonomii, takie jak exclude-from-catalog, exclude-from-search i outofstock. WooCommerce utrzymuje je w cyklu życia produktu i wykorzystuje w zapytaniach katalogowych.

Łączenie tych stanów prowadzi do niepotrzebnych problemów. Zmiana post_status na draft za każdym razem, gdy ERP zgłasza zerowy stan, nie usuwa historycznych sum zamówień, ponieważ WooCommerce zapisuje wartości pozycji osobno. Może jednak pogorszyć działanie panelu administracyjnego i raportów, które nadal odwołują się do produktu, aby wyświetlić link, miniaturę albo SKU. Przeniesienie produktu do kosza lub jego usunięcie jeszcze bardziej osłabia te powiązania. Dostępność produktu i dalsze istnienie jego rekordu to dwa różne zagadnienia.

W tym wdrożeniu potrzebne rekordy produktów zachowują post_status = publish, a bieżąca dostępność jest zapisywana w _stock_status. Ograniczenie podpięte do woocommerce_product_query z priorytetem 11 dodaje do głównego zapytania katalogu warunek meta_query wymagający _stock_status = instock. Niedostępne produkty nadal można rozpoznać w historycznych zamówieniach, ale znikają z listy widocznej dla kupującego. Inny sklep może potrzebować polityki archiwizacji, lecz sam brak zapasu nie powinien jej automatycznie uruchamiać.

Ta decyzja ma istotny koszt implementacyjny. Ze względu na wydajność integracja z ERP aktualizuje _stock_status kontrolowanymi operacjami SQL, zamiast wywoływać wc_update_product_stock_status() dla każdego SKU. Omija w ten sposób część cyklu życia WooCommerce i może pozostawić niespójne terminy product_visibility lub pamięć podręczną. Własne zapytanie katalogowe celowo odczytuje autorytatywne pole meta, ale każdy inny mechanizm korzystający z widoczności produktu trzeba osobno sprawdzić. Nie jest to ogólna rekomendacja omijania API WooCommerce, lecz optymalizacja przenosząca odpowiedzialność za spójność, unieważnianie pamięci podręcznej i testy regresji na zespół projektu. Szersza zasada jest taka sama jak przy imporcie własnych pól produktów WooCommerce: wybierz jedno autorytatywne źródło, a następnie jawnie uwzględnij wszystkie mechanizmy, które standardowo utrzymują pozostałe reprezentacje.

Czwarty stan również jest jawny: cały sklep staje się niedostępny, gdy ERP nie może dostarczyć wiarygodnych danych magazynowych. Dedykowany szablon wyświetla komunikat o przerwie technicznej zamiast prezentować katalog z potencjalnie nieaktualną dostępnością. Potraktowanie awarii ERP jako stanu aplikacji pozwala testować jej obsługę i zapobiega przyjmowaniu zamówień przy nieznanym stanie zapasów.

Onboarding konta to kolejka akceptacji

Domyślna rejestracja WooCommerce jest zoptymalizowana pod szybkie nadanie klientowi dostępu. Zamknięty kanał hurtowy wymaga innego cyklu życia konta. Zgłoszenie może wymagać sprawdzenia w rejestrze przedsiębiorców, weryfikacji identyfikatora podatkowego i oceny zgodności z polityką kredytową. Rola nadana po akceptacji określa również ceny i części katalogu dostępne dla konta.

Dla kont oczekujących woocommerce_registration_auth_new_customer blokuje automatyczne uwierzytelnienie. Osoba składająca wniosek widzi komunikat o trwającej weryfikacji i nie otrzymuje sesji klienta. Gdy administrator zatwierdzi konto, własna akcja odczytuje przypisaną rolę i uruchamia odpowiednią podklasę WC_Email. Standardowy i najwyższy poziom hurtowy otrzymują inne szablony, komunikaty powitalne oraz odnośniki pierwszego logowania.

Najważniejsza decyzja nie dotyczy szablonu maila, lecz momentu przypisania roli — podczas akceptacji, a nie rejestracji. Automatyczne zalogowanie każdego wnioskodawcy z domyślną rolą klienta przyznaje dostęp zbyt wcześnie i zmusza administratora do późniejszego korygowania uprawnień. W tym czasie może już zostać wysłana niewłaściwa wiadomość, a historia konta przestaje odzwierciedlać rzeczywisty proces akceptacji.

Rezygnacja z SEO produktów upraszcza model

Kompromis jest jawny: ten sklep nie wykorzystuje stron produktów jako kanału pozyskiwania ruchu organicznego. Nie ma strategii kanonicznych adresów produktów, danych schema Product, kart Open Graph dla pojedynczych SKU ani produktów w mapie witryny. Kupujący trafiają do sklepu dzięki relacji z handlowcem, wiadomości e-mail albo bezpośredniemu odnośnikowi do /my-account, więc udostępnianie adresów produktów wyszukiwarkom zwiększałoby złożoność bez wsparcia modelu sprzedaży.

W zamian granica systemu jest mniejsza. Trasy produktów zwracają 404 zamiast polegać na noindex; nie trzeba utrzymywać kanonicznych adresów produktów ani szablonów danych strukturalnych; niezalogowani użytkownicy są przekierowywani, zanim wyrenderuje się prywatny katalog. Model zmienia się z „publicznego katalogu z prywatnymi nakładkami” w „prywatny katalog”. Przekierowanie, brak stron produktów, filtr dostępności i kolejka akceptacji egzekwują tę samą zasadę.

Uniwersalna wtyczka B2B nie rozstrzygnie za firmę, że publiczne adresy produktów i ruch organiczny nie mają wartości. Praktyczny test jest prosty: jeśli zwracanie 404 dla /product/{slug} jest nie do zaakceptowania, sklep nie jest zamknięty w znaczeniu przyjętym w tym artykule i standardowy model wtyczki B2B prawdopodobnie sprawdzi się lepiej. Jeśli adresy produktów rzeczywiście nie pełnią żadnej publicznej funkcji, bezpośrednie przejęcie tych czterech decyzji tworzy spójny system, którego zachowanie pod obciążeniem, podczas awarii ERP i w trakcie audytu można racjonalnie przewidzieć.