Allegro

Zamówienia i faktury

Drenaż dziennika zdarzeń zamówień Allegro do zamówień w Medusie, drabina statusów pochodnych, zwrotny zapis realizacji i dołączanie wystawionej faktury PDF do zamówienia Allegro.

Dlaczego dziennik zdarzeń, a nie cokolwiek innego

GET /order/events to jedyne zaplanowane wejście. Odpytywanie formularzy zakupowych przez updatedAt.gte= go nie zastąpi, bo Allegro nie podbija niezawodnie pola updatedAt formularza, gdy zmienił się wyłącznie jego status realizacji. Przejście po oknie czasowym nie zobaczy więc najczęstszej zmiany statusu, jaka w ogóle występuje.

Drenaż chodzi co 20 sekund, a nie na wyrażeniu cron, bo cron w Medusie ma rozdzielczość minutową, a świeże zamówienie powinno zostać zaciągnięte szybciej niż w minutę. ALLEGRO_ORDERS_SYNC_CRON przestawia go z powrotem na cron, jeśli tak wolisz; w harmonogramie Medusy te dwa sposoby wykluczają się, a gdy ustawione są oba, wygrywa cron.

Dyscyplina kursora

Zdarzenia są konsumowane po kolei, a kursor przesuwa się wyłącznie po wiodącej serii zdarzeń, których zamówienia się powiodły. Pierwsze zdarzenie należące do zamówienia nieudanego albo odłożonego zatrzymuje przesuwanie, więc ono i wszystko po nim powtórzy się w następnym takcie.

Dwukrotne zastosowanie zamówienia jest nieszkodliwe, bo zapis jest idempotentny. Zgubienie zmiany statusu przez przejściową awarię nie jest, i dlatego kursor jest zachowawczy właśnie w tę stronę.

Trzy tryby awarii, na które synchronizacja z jednym wejściem musi mieć odpowiedź:

Jedno zepsute zamówienie nie może zakleszczyć taktu. Po pięciu kolejnych niepowodzeniach formularz trafia do kwarantanny, a kursor dostaje prawo go minąć. Bez tego wyjścia jedno trwale zepsute zamówienie przypina kursor na zawsze i w końcu nic się nie importuje.

Awarii nie wolno pomylić ze stoma zepsutymi zamówieniami. Takt, w którym każde odświeżenie zawiodło i żadne się nie udało, jest systemowy: żadna seria nie rośnie, nic nie idzie do kwarantanny, kursor stoi.

Zaległości nie mogą zagłodzić nowych zamówień. Limit na uruchomienie wydawany jest od najstarszych, bo tylko w tej kolejności zaległości maleją - ale 20 ze 100 jest zarezerwowane dla najnowszych kandydatów, stosowanych poza kolejnością kursora. Czyste „od najnowszych” zostało odrzucone, bo się zakleszcza: odłożenie najstarszego blokuje kursor na pierwszym zdarzeniu, więc ta sama strona powtarza się w nieskończoność.

Pierwsze uruchomienie

Bez kursora zapisywany jest identyfikator najnowszego zdarzenia i nic nie jest konsumowane. Odtworzenie sześćdziesięciu dni, które Allegro przechowuje, to tysiące wywołań, więc świeża instalacja zaczyna śledzić od „teraz”, a wciągnięcie historii jest świadomą decyzją operatora.

Drabina statusów

Allegro raportuje status zakupowy i zarządzany przez sprzedawcę status realizacji. Ich iloczynem jest allegro_order.derived_status:

AllegroPochodny
zakupowy CANCELLED (wygrywa ze wszystkim)cancelled
realizacja NEW + zakupowy BOUGHTpending
realizacja NEW + READY_FOR_PROCESSINGnew
PROCESSING, SUSPENDEDprocessing
READY_FOR_SHIPMENT, READY_FOR_PICKUPready_for_shipment
SENTsent
PICKED_UPdelivered
RETURNEDreturned
niezamodelowany status realizacjinic nie jest zapisywane

Wyliczenie order.status w Medusie nie ma ani sent, ani ready_for_shipment, więc na zamówienie trafiają tylko te dwa krańce, które Medusa naprawdę modeluje, i to przez własne workflowy: cancelled anuluje zamówienie, delivered je zamyka. Zapis wprost do kolumny biłby się z panelem i z procesami edycji zamówienia.

Podstawą porównania jest derived_status, a nie surowe kolumny statusów. Surowe kolumny są nadpisywane przy każdym przejściu, więc wyprowadzanie z nich na nowo czyniło jeden zdławiony zapis statusu trwałym: zabezpieczenie widziało już zawsze „brak przejścia”, a zamówienie zamarzało na tym statusie, który akurat niosło. derived_status jest zapisywany w tej samej operacji co dowolna akcja, więc zgubiony zapis po prostu się ponawia, a edycja pracownika przeżywa, bo pracownik zmienia zamówienie i zostawia status pochodny tam, gdzie postawiło go Allegro.

Kolejność odporna na awarię. Najpierw wchodzi wiersz księgowy bez synced_at, potem zamówienie w Medusie, potem akcja statusu, a znacznik na końcu. Awaria gdziekolwiek wcześniej zostawia wiersz niedokończony, więc następne przejście go naprawia.

Czego wtyczka zamówieniu nie zrobi

Niedopasowane pozycje nie gubią sprzedaży. Pozycja, której sygnatura nie pasuje do żadnego wariantu w Medusie, jest przenoszona jako pozycja własna z samym tytułem i zapisywana w line_conflicts. Sprzedaż wydarzyła się na Allegro niezależnie od tego, co mówi katalog Medusy, a zamówienie, którego nikt nie widzi, nie jest bezpieczniejsze od takiego, które jest widocznie zmapowane w połowie.

Sumy i ceny pozycji pochodzą z Allegro dosłownie, nigdy nie są przeliczane ponownie. Pieniądz jest ciągiem dziesiętnym od początku do końca, bo przepuszczenie go przez liczbę zmiennoprzecinkową to sposób, w jaki synchronizacja zaczyna wysyłać 233.20999999999998.

Sporna suma jest zapisywana, a nie poprawiana. Suma każdego zamówienia w Medusie jest porównywana z totalToPay zapisanym przez Allegro dla formularza, co do grosza i w tej samej walucie. Rozbieżność ląduje na allegro_order.conflict jako total-mismatch, z obiema kwotami w conflict_detail, a strona zamówień pokazuje ją obok sumy. Nigdy nie blokuje ani nie wycofuje zamówienia i sama znika przy kolejnym przejściu, gdy sumy się zgodzą. Zwykłą, niegroźną przyczyną jest pozycja własna z samym tytułem, która ma pełne prawo ruszyć sumę - szczegół podaje, ile takich pozycji ma zamówienie, właśnie po to, więc sprawdź tę liczbę, zanim zaczniesz badać arytmetykę.

Zwróć też uwagę, że zaimportowane zamówienia nie mają wyliczonych linii podatkowych. Branie cen pozycji z Allegro dosłownie jest właściwe dla uzgadniania, ale oznacza, że rozbicie podatkowe zamówienia z Allegro jest w Medusie puste.

Rezerwacje, żeby zamówienie dało się zrealizować

createOrderWorkflow w Medusie - przepływ, którym tworzone jest każde zamówienie z Allegro - sprawdza, że stan magazynowy istnieje, a potem celowo nie tworzy żadnej rezerwacji. Rezerwuje wyłącznie realizacja koszyka, a żadne zamówienie z Allegro przez koszyk nie przechodzi. Rdzeń Medusy odmawia realizacji każdej pozycji, której wariant zarządza stanem magazynowym i nie ma rezerwacji - więc bez tego zarówno przycisk Zrealizuj w panelu, jak i każda wtyczka realizująca zamówienie kończyły się błędem No stock reservation found for item ordli_..., a opłacone i dostarczone zamówienia wyglądały na niezrealizowane, blokując za sobą zwrotny zapis realizacji opisany niżej.

Dlatego drenaż tworzy rezerwacje sam, przez firmowy createReservationsWorkflow, zaraz po tym, jak zamówienie zaistnieje, i przed zapisaniem płatności - płatność emituje payment.captured, czyli początek łańcucha kończącego się realizacją, więc rezerwacja lądująca po niej mogłaby przegrać wyścig.

Trzy własności sprawiają, że można to bezpiecznie uruchamiać przy każdym przejściu - i tak właśnie się dzieje:

  • Idempotencja. Istniejące już rezerwacje są odejmowane od potrzeby, osobno dla każdej pozycji i każdej pozycji magazynowej. Zamówienie zarezerwowane kosztuje dwa odczyty i nie zapisuje nic.
  • Nigdy nie rezerwuje za dużo. Ilość to required_quantity x (zamówione - już zrealizowane). Zrealizowane sztuki swoją rezerwację już zużyły.
  • Nigdy nie wywraca zamówienia. Pozycja, której pozycja magazynowa nie ma stanu w żadnej lokalizacji, trafia do ostrzeżenia i jest pomijana. To problem katalogu dla człowieka; utrata sprzedaży z tego powodu byłaby gorsza.

Ponieważ działa przy każdym przejściu, przebieg uzgadniający leczy zamówienia utworzone, zanim to powstało: kolejne przejście, które takie zamówienie napotka, tworzy jego rezerwację i zamówienie da się zrealizować - bez uruchamiania jakiegokolwiek skryptu. Linia zadania raportuje wtedy reservationsCreated. Celowo nie jest to liczone jako reconcileRepaired: rezerwacji potrzebuje każde wcześniejsze zamówienie, więc wliczenie tego zgłaszałoby zdrowy dziennik zdarzeń jako zepsuty przez cały czas trwania uzupełniania.

Zamówienie anulowane jest pomijane - trzymanie towaru pod sprzedaż, która się nie odbędzie, kosztuje sklep realne pieniądze przy każdym innym zamówieniu.

order.placed dla sprzedaży z marketplace'u

Medusa nie emituje order.placed dla zamówienia utworzonego przez createOrderWorkflow - a to jego woła ten drenaż. W Medusie 2.18 zdarzenie to emitują completeCartWorkflow (zakup w sklepie) oraz convertDraftOrderWorkflow, każdy obok swojego tworzenia, a nie w jego środku. Dopóki wtyczka go nie emitowała, sprzedaż z Allegro nie zgłaszała się nikomu. Cokolwiek nasłuchiwało order.placed (powiadomienie na Slacku, wypchnięcie do ERP, mailing) było strukturalnie głuche na zamówienia z marketplace'u i jednocześnie wyglądało zdrowo: subskrybent zarejestrowany, zamówienie istnieje, nigdzie żadnego błędu.

Dlatego drenaż emituje je sam, z ładunkiem rdzenia dosłownie - { id }, z EventPriority.CRITICAL - bo subskrybent nigdy nie powinien musieć wiedzieć, którym kanałem przyszło zamówienie.

Dokładnie raz na zamówienie. Emituje je wyłącznie przebieg, który to zamówienie faktycznie utworzył. Ponowione zdarzenie Allegro, wymuszone odświeżenie, przejście rekoncyliacyjne i ścieżka adopcji podnosząca zamówienie po przerwanym przebiegu - wszystkie idą gałęzią „ta forma ma już zamówienie" i żadna nie ogłasza. Zamówienie zaadoptowane nie jest ogłaszane celowo: adopcja uruchamia się także dla zamówienia utworzonego minuty wcześniej przez przerwany przebieg, a zdublowanego „nowego zamówienia" nie da się cofnąć.

Nigdy nie jest powodem do zatrzymania kursora zdarzeń. Gdy szyna zdarzeń jest niedostępna, awaria trafia do ostrzeżenia przy zamówieniu, a drenaż idzie dalej - wstrzymanie każdego kolejnego zamówienia z powodu niedoręczonego powiadomienia byłoby dużo gorsze, a ponowienie i tak zastałoby zamówienie już utworzone i słusznie by go nie ogłosiło.

allegro.order.billing_ready, czyli moment, w którym da się wystawić fakturę

Zamówienie z Allegro powstaje ze zrzutu formularza zakupowego zrobionego zanim kupujący skończy go wypełniać, więc bardzo często nie ma adresu do faktury. Dane kupującego dochodzą kilka minut później, przy kolejnym przebiegu drenażu. W tym czasie płatność się finalizuje, leci payment.captured, wtyczka fakturowa nasłuchująca tego zdarzenia próbuje zbudować fakturę na zamówieniu bez adresu, nie przechodzi własnej bramki kompletności i odkłada zamówienie do ręcznej obsługi. Z produkcji:

12:32:22  zamówienie utworzone (kupujący jeszcze nie zapłacił i nie dokończył formularza)
12:36:22  płatność zaksięgowana
12:36:24  wtyczka fakturowa kolejkuje zamówienie po `payment.captured`
12:36:25  „buyer address is incomplete (missing: street, city, postal_code)"
12:36:41  drenaż zapisuje prawdziwy adres do faktury - 16 s za późno

Żadna z wtyczek nie była zepsuta. Strona fakturowa nasłuchiwała zdarzenia, które niesie fakt, na jaki reaguje, zamiast zdarzenia, które niesie dane, jakich potrzebuje - a takiego zdarzenia nie było, bo adres do faktury i NIP zapisywane są przez serwis modułu Order, a nie przez updateOrderWorkflow (powód: medusajs/medusa#16636), więc lądują bez order.updated i w ogóle bez zdarzenia.

Dlatego drenaż to ogłasza: allegro.order.billing_ready, ładunek { id }, z EventPriority.CRITICAL - ten sam kształt, co order.placed. Nazwę importuj z @zanreal/medusa-allegro/workflows, zamiast przepisywać string.

Emituje się na zboczu, nie na stanie. Robi to dokładnie jedna z dwóch sytuacji:

  • przebieg tworzący, gdy zamówienie powstało od razu z użytecznym adresem do faktury - inaczej zamówienie kupującego, który wypełnił formularz, zanim je zobaczyliśmy, nie dostałoby zdarzenia nigdy, bo uzupełnianie adresu i NIP-u nie ma wtedy już czego robić;
  • albo późniejszy przebieg, który faktycznie coś zmienił - uzupełnił adres lub dopisał NIP - i zostawił adres do faktury kompletny.

Oba sygnały dotyczą pojedynczego przebiegu, więc tyknięcia drenażu co ~20 s po obu stronach zmiany nie ogłaszają niczego. „Kompletny" znaczy: ulica, miasto i kod pocztowy - te trzy pola, których wymaga wystawienie faktury - wszystkie niepuste, czytane z wartości, jakie zamówienie faktycznie ma.

To informacja, że dane są. Nie polecenie „wystaw teraz fakturę". Zdarzenie celowo nie jest bramkowane płatnością ani statusem zamówienia; subskrybent sam decyduje, czy zamówienie jest opłacone, anulowane albo już zafakturowane. Odczytanie go jako polecenia oznaczałoby faktury na anulowanych zamówieniach.

Nigdy nie jest powodem do zatrzymania kursora zdarzeń, z tego samego powodu co order.placed: dane są już zapisane, a ponowienie zastałoby je kompletne i słusznie by nic nie wyemitowało. Zgubione ogłoszenie kosztuje ostrzeżenie i spada na ten mechanizm ponowień, który konsument i tak ma.

Adres do faktury wypełniony w połowie to luka, a nie adres

Warto wiedzieć, bo po cichu psuł uzupełnianie adresów. Allegro potrafi zwrócić blok faktury, w którym jest tylko imię i kod kraju, a czytnik formularza zwraca adres, jeśli którekolwiek pole jest ustawione - więc zamówienie powstaje z wierszem billing_address bez ulicy, bez miasta i bez kodu pocztowego.

Uzupełnianie pytało wcześniej „czy zamówienie ma wiersz adresu do faktury?", na co taki wiersz odpowiadał „tak" i to na zawsze. Trzy pola potrzebne fakturze nie były więc uzupełniane w żadnym przebiegu - dokładnie na tych zamówieniach, które potrzebowały tego najbardziej. Teraz pytanie brzmi „czy adres do faktury jest użyteczny" i dotyczy wartości pól, a adres częściowy jest uzupełniany tak samo jak brakujący: wypełniane są wyłącznie puste pola, więc wartość, którą wiersz już niósł (albo którą poprawił człowiek), nigdy nie zostaje nadpisana.

Zwrotny zapis realizacji

Subskrybent na zdarzeniach order.fulfillment_created i shipment.created ustawia zarządzany przez sprzedawcę status Allegro - odpowiednio READY_FOR_SHIPMENT i SENT - dla zamówień pochodzących z Allegro. Dla każdego innego zamówienia nic nie robi.

Zdarzenie jest szybką ścieżką i tylko ono melduje wysyłkę od razu. Nie jest już jednak jedynym sygnałem - i ta poprawka ma znaczenie. Realizacja rzeczywiście jest aktem w punkcie czasu, ale zdanie „to zamówienie ma realizację, która wyszła” to zwyczajny, trwały stan zapisany w fulfillment.shipped_at. Przejście uzgadniające porównuje dokładnie to z tym, co raportuje Allegro.

Nigdy nie rzuca wyjątkiem. Realizacja w Medusie w chwili jego uruchomienia już istnieje, więc wywrócenie subskrybenta jej nie cofnie, a pogrzebie powód. Błąd trafia zamiast tego do allegro_order.last_error.

Przejście uzgadniające jako ponowienie

Subskrybent odpala się raz na przesyłkę. Do 23 sierpnia 2026 była to jedyna próba, jaka w ogóle istniała, i pewne prawdziwe zamówienie pokazało, ile to kosztuje: zapis zwrotny się nie udał, kupujący miał już swój klucz licencyjny, a zamówienie na Allegro przez trzy dni pokazywało READY_FOR_SHIPMENT - i nigdzie ani słowa dlaczego.

Dlatego przejście uzgadniające - które i tak odczytuje ponownie każde otwarte zamówienie Allegro na wolnej ścieżce - dostało jedno dodatkowe porównanie. Nie doszedł żaden nowy harmonogram i nie jest potrzebny: to jedzie na przejściu, które już chodzi.

Dla każdego otwartego zamówienia, które odczytuje ponownie: jeśli w Medusie jest żywa realizacja z ustawionym shipped_at, a formularz zwrócony przed chwilą przez Allegro nie jest SENT, wysyła SENT przez pushAllegroFulfillment - czyli przez ten sam przepływ, z którego korzysta subskrybent, a nie przez drugi sposób zapisu statusu.

Cztery odmowy pilnują, żeby to zostało zabezpieczeniem, a nie drugim piszącym:

  • Allegro już mówi SENT. Bramka idempotencji. Zamówienie, którego zapis zwrotny doszedł, nie kosztuje ani jednego zapisu do marketplace'u przy kolejnych przejściach.
  • Przesyłka jest młodsza niż okno karencji (ALLEGRO_ORDERS_RECONCILE_SENT_GRACE_MS, domyślnie 10 minut). Pierwszeństwo ma subskrybent. Bez tego przejście ścigałoby się z nim - patrz opóźnienie odczytu opisane niżej, bo to właśnie dla niego to okno w ogóle istnieje.
  • Zamówienie jest delivered, returned albo cancelled, albo niesie status realizacji, którego ta wtyczka nie modeluje. SENT zostanie tam albo odrzucone, albo cofnie zakończone zamówienie o krok wstecz.
  • Nic nie wyszło. Anulowana realizacja nie jest przesyłką i jest pomijana.

Wysłanie wykonane przez przejście trafia do logu na poziomie warn i na linię zadania jako fulfillmentsPushed, bo oznacza, że zapis subskrybenta nigdy nie doszedł. Nieudane wysłanie liczy się jako fulfillmentPushFailures, niesie swój powód w allegro_order.last_error i jest ponawiane przy kolejnym przejściu - i to jest cała różnica wobec stanu sprzed, gdzie nieudany zapis zwrotny nie zostawiał żadnego śladu.

Oba przejścia rządzi ten sam przełącznik, więc wyłączenie fulfillment_writeback_enabled zatrzymuje wysyłanie zarówno z przejścia, jak i z subskrybenta.

Jedna rysa, znana i świadomie zaakceptowana: last_error to jedna kolumna, do której pisze trzech autorów. Udane podpięcie faktury na tym samym takcie czyści więc błąd nieudanego wysłania SENT, zapisany chwilę wcześniej w tym samym przebiegu. Nic przez to nie ginie na stałe - kolejne przejście po otwartych zamówieniach ponawia wysłanie i zapisuje powód od nowa, a linia błędu całego przebiegu raportuje niepowodzenie tak czy inaczej.

Ma własny przełącznik, fulfillment_writeback_enabled, i celowo nie czyta ordersSyncDisabled. Tamten wyłącznik zatrzymuje konsumowanie dziennika przez drenaż, a wstrzymanie importu to inna decyzja niż odmowa poinformowania kupującego, że przesyłka wyszła. Jeśli więc mapowanie budzi wątpliwości i boisz się oznaczyć niewłaściwe zamówienie Allegro jako wysłane, wyłącz ten jeden zapis, popraw mapowanie i włącz go z powrotem - import zamówień pozostaje nietknięty i nie trzeba łączyć konta na nowo.

Jedna znana rysa: sklep, który tworzy realizację i przesyłkę jednym ruchem, wysyła dwie aktualizacje. Wygrywa druga, co jest poprawne, choć rozrzutne.

Allegro czyta z opóźnieniem to, co samo zapisało - około 45 sekund

Warto o tym wiedzieć, zanim zacznie się tu cokolwiek diagnozować, bo wygląda to dokładnie jak awaria, a nią nie jest.

Zmierzone, nie założone: przy ręcznej naprawie zablokowanego zamówienia 25 sierpnia 2026 PUT /order/checkout-forms/{id}/fulfillment zwrócił 2xx, a następny w kolejności GET /order/checkout-forms/{id} wciąż raportował READY_FOR_SHIPMENT. Dopiero odczyt jakieś 45 sekund później pokazał SENT.

Status odczytany zaraz po wysłaniu jest przeterminowany, a nie odrzucony. Nie ponawiaj na podstawie takiego odczytu i nie wyciągaj z niego wniosku, że zapis się nie udał - odczekaj i przeczytaj ponownie.

To także jedyny powód, dla którego istnieje opisane wyżej okno karencji. Bez niego przejście odczytywałoby formularz chwilę po udanym wysłaniu przez subskrybenta, widziało READY_FOR_SHIPMENT i „naprawiało” zamówienie, które nigdy nie było zepsute - raportując zdrowy zapis zwrotny jako awarię.

Łańcuch fakturowy

Allegro oczekuje, że fakturę do zamówienia da się pobrać z widoku zamówienia. Ta wtyczka nie wystawia faktur, więc łańcuch wygląda tak:

zamówienie opłacone  ->  moduł fakturowy wystawia fakturę
                     ->  emituje `infakt.invoice.issued`
                     ->  medusa-allegro rejestruje dokument na formularzu zakupowym
                     ->  wysyła plik PDF
                     ->  stempluje allegro_order.invoice_attached_at

Żadna z wtyczek nie importuje drugiej. Całą umową jest nazwa zdarzenia, jego zawartość i klucz kontenera (invoiceModuleKey), który ta wtyczka rozwiązuje leniwie. Sklep może fakturować, nie sprzedając na Allegro, i sprzedawać na Allegro, nie fakturując, więc twarda zależność w którąkolwiek stronę czyniłaby każdą z wtyczek bezużyteczną bez drugiej. Bez zarejestrowanego modułu fakturowego łańcuch jest po prostu bezczynny: nic nie jest logowane, nic ponawiane, a każda inna pętla zachowuje się identycznie.

Zawartość zdarzenia jest czytana defensywnie, bo przekracza granicę wersji. order_id i invoice_uuid są wymagane, cała reszta opcjonalna, nieznane pola są ignorowane, a zniekształcona zawartość jest zalogowana i porzucona, a nie rzucona wyjątkiem - rzucający subskrybent byłby ponawiany z tą samą zniekształconą zawartością aż do wyczerpania budżetu, a powód nigdy by do nikogo nie dotarł.

Dlaczego odczyt odsiewający nie jest opcjonalny

POST /order/checkout-forms/{id}/invoices nie ma klucza idempotencji. Drugie wywołanie z tym samym numerem faktury rejestruje drugi dokument, zamiast zwrócić pierwszy, a Allegro przyjmuje najwyżej dziesięć dokumentów na zamówienie. Przed każdym utworzeniem dzieją się więc dwie rzeczy:

  1. Jeśli allegro_order.allegro_invoice_id jest ustawione, ten dokument jest używany wprost.
  2. W przeciwnym razie czytany jest GET .../invoices i dopasowywany po numerze faktury. Trafienie zostaje użyte ponownie.

Identyfikator jest zapisywany w chwili, gdy utworzenie zwróci wynik, przed próbą wysłania pliku. Ten zapis jest całym powodem istnienia tej kolumny: awaria między udanym utworzeniem a wysyłką zarejestrowałaby przy ponowieniu drugi dokument dla tej samej faktury.

Sprawdzenie rozmiaru też następuje przed jakąkolwiek rejestracją. Allegro odrzuca plik większy niż 3 MB, a zarejestrowany dokument bez pliku i tak liczy się do dziesięciu, więc rejestrowanie na początku mogłoby z czasem doprowadzić do zamówienia niezdolnego przyjąć fakturę, która by się zmieściła. Plik za duży albo pusty jest odnotowywany na wierszu i nigdy nie jest wysyłany.

Dwa dalsze szczegóły warto znać. Plik jest wysyłany jako surowa treść żądania z nagłówkiem Content-Type: application/pdf, nie jako JSON i nie jako multipart - przepuszczenie Uint8Array przez JSON.stringify daje {}, co Allegro przyjmuje, zostawiając zamówienie z dokumentem faktury o dwubajtowym pliku i bez żadnego sygnału błędu. A pobranie PDF-a przestawia fakturę na printed po stronie modułu fakturowego, co jest zapisem tamtego systemu, że dokument go opuścił, a nie pomyłką - dlatego ta wtyczka rozwiązuje klienta Allegro przed pobraniem: płacenie tym skutkiem ubocznym za wysyłkę, która i tak nie może dojść do skutku, nic nie daje.

Przejście ponawiające

Zdarzenie nie jest jedyną ścieżką. Na końcu każdego drenażu zamówień, w obrębie tego samego zajęcia na wyłączność, bo pisze do tych samych wierszy, ograniczone przejście pyta moduł fakturowy o 50 ostatnio ruszanych faktur i dołącza te, których zamówienie Allegro nie ma invoice_attached_at, do 10 na takt. Pokrywa obie połowy nieudanego dołączenia: tę, która nic nie zarejestrowała, i tę, która zarejestrowała, ale nigdy nie wysłała pliku.

Kandydaci pochodzą z modułu fakturowego, a nie ze znacznika własnego tej wtyczki, i to celowo. Niepowodzenia dołączania są zapisywane w allegro_order.last_error, dzielonym z drenażem i czyszczonym przy jego następnym czystym przejściu po tym samym formularzu, więc przejście oparte na tym ciągu po cichu gubiłoby dokładnie te zamówienia, które poza tym są zdrowe. Porównania „co zostało wystawione” z invoice_attached_at nie da się w ten sposób unieważnić.

Dwa ograniczenia, wypisane zamiast zostawione do odkrycia:

  • Przejście działa tylko wtedy, gdy działa drenaż, więc ordersSyncDisabled wstrzymuje także ponawianie. Ścieżka zdarzeniowa pozostaje nietknięta, więc świeżo wystawiona faktura i tak dojdzie.
  • Niepowodzenie dołączenia może zostać nadpisane w last_error przez późniejsze zdrowe przejście drenażu po tym samym formularzu. Samego przejścia to nie dotyka, ale panel może przestać pokazywać powód; liczbę podaje wtedy linia błędu uruchomienia na wierszu stanu zamówień.

Jak czytać odrzucone podpięcie

Linia awarii nazywa które z czterech wywołań padło - pobranie PDF z modułu fakturowego, odczyt odsiewający, założenie metadanych czy wysyłkę pliku - a do tego wszystko z errors[] Allegro (code, path, message, userMessage), x-request-id do zacytowania wsparciu Allegro oraz dwie wartości, które wybiera ta wtyczka: file.name i invoiceNumber. Tylko o te dwie może chodzić przy odrzuconym założeniu, i żadna z nich nie jest daną kupującego - zasoby fakturowe przyjmują nazwę pliku i numer faktury, nic więcej. Wywołania proszą też o Accept-Language: en-US, więc userMessage przychodzi w tym samym języku co reszta dziennika.

Ma to znaczenie, bo samo AllegroApiError.message to tylko pierwszy userMessage, jaki zwróciło Allegro - a przy HTTP 400 to zwykle ogólnikowe „Bad Request", podczas gdy code i path wskazujące winne pole zostają nieprzeczytane. Czterysta bez powodu to nic, na czym operator może się oprzeć.

Jeden świadomy eksperyment przy 400. Allegro w ogóle nie dokumentuje 400 dla POST .../invoices - udokumentowane odmowy to 403 (uprawnienia), 404, 409 (zamówienie ma już fakturę), 422 (zamówienie jej nie przyjmie) i 429 (za szybko) - a jego schemat wymaga dokładnie jednego pola, file. Zatem invoiceNumber to jedyne pole, którego wartość może unieważnić skądinąd poprawne ciało żądania. Gdy założenie z numerem dostaje 400, i tylko wtedy, podpięcie ponawia raz z pominiętym numerem. Jeśli Allegro to przyjmie, kupujący dostaje fakturę, a przyczyna jest dowiedziona - jedno i drugie wprost w dzienniku; jeśli odrzuci i to, ciało żądania jest oczyszczone z zarzutów, a obie odmowy są zapisane. Żaden inny status nie jest tak ponawiany, bo każdy z nich znaczy coś, co ślepe ponowienie tylko pogorszy.

Własny wyłącznik

Dołączanie faktur ma własny przełącznik, invoice_attach_enabled, i jest to jedyny zapis, który wychodzi z pudełka włączony - bo zanim ta wtyczka usłyszy o fakturze, dokument już istnieje jako zapis prawny, więc dostarczenie go jest bezpiecznym domyślnym zachowaniem. Jest włączony, ale bezczynny, dopóki moduł fakturowy nie emituje zdarzeń.

Celowo nie jest odczytem ordersSyncDisabled. Tamten wyłącznik zatrzymuje konsumowanie dziennika przez drenaż i operator sięga po niego, żeby zatrzymać rozpędzony import; dostarczenie faktury, której wymaga zamówienie na marketplace, to inna decyzja o innych konsekwencjach. Jeden wyłącznik na oba oznaczałby, że wstrzymanie importu po cichu zatrzymuje docieranie wystawionych faktur do kupujących.

Gdy zapis jest wyłączony, powód trafia na wiersz zamówienia, a nie tylko do logu. „Nie ma faktury przy zamówieniu” wygląda z zewnątrz identycznie jak zepsuta integracja, a wyłączony zapis to jedyne wyjaśnienie, którego nikt nie zgaduje.

Narzędzia operatora

Zamówienie w kwarantannie. Lista kwarantanny na stronie zamówień niesie przy każdym wpisie jego błąd i to, jak długo się nie udaje. Usuń przyczynę i naciśnij Napraw. Sukces czyści obie mapy niepowodzeń i oddaje formularz drenażowi. Nieudana naprawa nie powiększa serii, więc ponawianie w trakcie pracy nad problemem nie może pogorszyć sprawy. Jeśli zamówienie jest starsze niż okres przechowywania zdarzeń przez Allegro, naprawa i tak działa - pobiera formularz bezpośrednio, z pominięciem dziennika.

Import zamówień, których dziennik nigdy nie wymienił. Okno importu na stronie zamówień pokrywa przypadki, których drenaż strukturalnie nie obsłuży: drenaż był wyłączony dłużej niż okres przechowywania, baza została odtworzona z kopii, kursor przepadł albo chcesz mieć historię na nowej instalacji. Nigdy nie rusza kursora zdarzeń, bo import wypełnia lukę za nim, i trzyma zajęcie zamówień przez cały czas działania, więc drenaż nie zaimportuje w tym czasie niczego nowego. Jedno uruchomienie obejmuje najwyżej 3000 zamówień; przy większym uzupełnianiu uruchom kilka okien z przesuwanym since.

Osobliwości zamówień Allegro warte poznania

Formularz zakupowy niesie nawet trzy różne osoby. buyer to dane rejestracyjne posiadacza konta, których żaden sprzedawca Allegro nie widzi we własnym interfejsie; delivery.address to wpisany przez kupującego odbiorca przesyłki i to jego sprzedawca widzi przy zamówieniu; invoice.address to odbiorca faktury, którym z pełnym prawem może być jeszcze kto inny. Czytanie wyłącznie pierwszego to sposób, w jaki integracja zaczyna wyświetlać nazwisko, któremu przeczy panel sprzedawcy Allegro. Ta wtyczka wstawia delivery.address w adres dostawy Medusy, a invoice.address w adres rozliczeniowy, z odwrotem do adresu dostawy, a nie do posiadacza konta.

Klientem Medusy jest posiadacz konta i wyłącznie posiadacz konta. Tym właśnie jest encja klienta: tożsamością stojącą za kontem, z którego przyszło zamówienie, i to jego adres pośredniczący trzyma już customer.email. Dlatego buyer.firstName / buyer.lastName - a przy koncie firmowym również buyer.companyName - trafiają na klienta, a odbiorca przesyłki nigdy nie jest pożyczany do wypełnienia pustego pola. To regularnie dwie różne osoby (prezent, dostawa do biura), a błędne nazwisko jest gorsze niż jego brak. Pola uzupełniane są tylko tam, gdzie u klienta są puste: cokolwiek ustawił ręcznie pracownik, zostaje nietknięte, więc uzupełnianie można bezpiecznie powtarzać. Zamówienia utworzone wcześniej mają klientów zupełnie bez nazwiska; przebieg uzgadniający uzupełnia je, gdy kolejno odczytuje otwarte zamówienia, i nie trzeba uruchamiać niczego ręcznie.

Blok buyer nazywa kod pocztowy postCode, a nie zipCode, jako jedyny spośród adresów w formularzu zakupowym. Otypowanie go wspólnym kształtem adresu po cichu go gubi.

NIP firmy z faktury mieszka dziś w otypowanej tablicy. Bieżącym źródłem jest company.ids z wpisem PL_NIP; płaskie company.taxId jest przestarzałe, ale wciąż wypełniane, więc zostaje jako odwrót. Pozostałe typy identyfikatorów, które Allegro potrafi zwrócić, to zagraniczne numery rejestracyjne o długości podobnej do NIP-u, więc ta wtyczka celowo ich nie czyta - złe sparowanie jest gorsze niż brak sparowania.

RETURNED jest zarządzany przez Allegro. Pojawia się, gdy każda sztuka wróci i zostanie zwrócona płatność, a endpoint realizacji go odrzuca, więc jest odczytywalny, ale nigdy ustawialny.

Spis treści