Checkout zawieszał się na kroku wyboru dostawy – spinner kręcił się w nieskończoność. Klienci nie mogli przejść dalej. Problem dotyczył 100% użytkowników na wszystkich przeglądarkach. Czas diagnozy: 2 godziny, naprawy: 20 minut.
Objawy
- Krok „Shipping” w checkout nigdy nie kończy ładowania
- W DevTools Network: żądanie GET
/rest/V1/carts/mine/shipping-methodszwraca 500 - W
var/log/exception.log: błąd PHP w custom carrier module - Problem zaczął się po deployu nowego modułu dostawy
Diagnoza
# Sprawdź co zwraca endpoint shipping methods curl -s -H "Authorization: Bearer TOKEN" \ https://sklep.pl/rest/V1/carts/mine/shipping-methods \ | python3 -m json.tool # Sprawdź logi tail -50 var/log/exception.log | grep -A 10 "shipping" # Przetestuj każdy carrier osobno - wyłącz wszystkie poza jednym bin/magento config:set carriers/custom_carrier/active 0 bin/magento cache:flush # Sprawdź czy checkout działa - jeśli tak, problem w tym carrierze
Przyczyna
Nowy custom carrier w metodzie collectRates() wykonywał zapytanie do zewnętrznego API kuriera bez timeout. API było niedostępne – PHP czekał 30 sekund aż nginx zakończył połączenie. Błąd nie był logowany bo wyjątek był rzucany po timeoucie nginx.
Rozwiązanie
<?php
// Carrier z poprawną obsługą błędów i timeout
class CustomCarrier extends \Magento\Shipping\Model\Carrier\AbstractCarrier
implements \Magento\Shipping\Model\Carrier\CarrierInterface
{
protected $_code = 'custom_carrier';
public function collectRates(
\Magento\Quote\Model\Quote\Address\RateRequest $request
): ?\Magento\Shipping\Model\Rate\Result {
if (!$this->getConfigFlag('active')) {
return null;
}
$result = $this->_rateResultFactory->create();
try {
// Zawsze ustaw timeout dla zewnętrznych API
$this->_httpClient->setConfig(['timeout' => 5]);
$rates = $this->fetchRatesFromApi($request);
foreach ($rates as $rate) {
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod($rate['code']);
$method->setMethodTitle($rate['name']);
$method->setPrice($rate['price']);
$method->setCost($rate['price']);
$result->append($method);
}
} catch (\Exception $e) {
// Log błąd ale nie rzucaj wyjątku - checkout musi działać
$this->_logger->error('Custom carrier API error', [
'exception' => $e->getMessage(),
]);
// Opcjonalnie: pokaż fallback ratę zamiast nic
if ($this->getConfigData('show_fallback')) {
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setMethod('fallback');
$method->setMethodTitle('Dostawa standardowa');
$method->setPrice((float)$this->getConfigData('fallback_price'));
$result->append($method);
}
}
return $result;
}
}
Wnioski
Metoda collectRates() musi być odporna na błędy zewnętrznych API. Timeout jest obowiązkowy – bez niego jeden niedostępny carrier blokuje cały checkout. Loguj błędy ale nie rzucaj wyjątków – customer nie powinien widzieć błędu technicznego tylko brak opcji dostawy. Rozważ fallback ratę dla krytycznych carrierów.
