«Non ci sono vettori disponibili per il tuo indirizzo di consegna»: la causa su un carrello vecchio recuperato
Il sintomo
Un cliente riceve o recupera il link di un vecchio carrello (dal back office, oppure da un'email di recupero) e arriva regolarmente fino allo step "Metodo di spedizione". Lì legge: "Sfortunatamente, non ci sono vettori disponibili per il tuo indirizzo di consegna." Nessun altro errore, nessun avviso prima. Se lo stesso cliente, con lo stesso indirizzo, crea un carrello nuovo da zero, il checkout funziona normalmente e i vettori compaiono. Il carrello coinvolto, di solito, non è recente: può avere mesi o anni, ed è stato riaperto ora tramite un link di recupero.
Cosa sembrava e non era
Il sospetto naturale, davanti a un messaggio che parla esplicitamente di "indirizzo di consegna", è che l'indirizzo sia il problema: cancellato, non più associato al cliente, oppure semplicemente non coperto da nessuna zona di spedizione configurata. Nessuna di queste tre piste regge a una verifica.
- L'indirizzo del carrello (a livello di testata) è valido. Non è cancellato, è quello attuale del cliente, e corrisponde a una zona geografica per cui esistono vettori attivi.
- I vettori esistono e sono attivi per quella zona. Controllati manualmente da back office (Trasporto -> Vettori): più opzioni configurate e attive per la zona dell'indirizzo, compreso un vettore a pagamento e un ritiro gratuito.
- Il gruppo cliente ha accesso a quei vettori. Nessuna restrizione per gruppo che escluda l'account del cliente coinvolto.
Tre piste ragionevoli, tutte confermate corrette, e il messaggio di errore compariva comunque. A quel punto la domanda giusta non era "quali vettori esistono per questo indirizzo", ma "PrestaShop sta davvero calcolando i vettori per questo indirizzo, o per un altro".
La causa vera
PrestaShop tiene, per ogni carrello, due riferimenti a un indirizzo di consegna: uno a livello di testata del carrello (ps_cart.id_address_delivery) e uno per ogni singola riga prodotto (ps_cart_product.id_address_delivery). Questa doppia struttura esiste per supportare la spedizione multipla, cioè un solo carrello con pacchi diversi spediti a indirizzi diversi. Su un negozio che non usa la spedizione multipla, i due valori dovrebbero sempre coincidere.
Il punto è che PrestaShop non aggiorna mai un indirizzo esistente quando il cliente lo modifica dal proprio account: ne crea uno nuovo e marca il vecchio come cancellato (soft delete, deleted = 1), pur mantenendolo nello storico per non rompere gli ordini già fatti su quell'indirizzo. Su un carrello attivo questo non si nota: la prossima volta che il cliente passa dal checkout, la testata del carrello viene aggiornata al nuovo indirizzo. Ma se quel carrello viene abbandonato prima di completare il checkout, la riga prodotto salvata resta agganciata all'ID del vecchio indirizzo, quello che il cliente poi cancella modificandolo. La testata può anche venire aggiornata in un secondo momento (per esempio da un accesso successivo che tocca solo l'indirizzo di fatturazione), lasciando un disallineamento silenzioso tra testata e riga prodotto che nessuna delle due parti segnala da sola.
Il metodo che calcola le opzioni di consegna, Cart::getDeliveryOptionList(), raggruppa i pacchi da spedire per l'indirizzo a livello di riga prodotto, non per quello di testata: è così che gestisce correttamente la spedizione multipla quando serve davvero. Con un indirizzo di riga cancellato, calcola comunque un elenco di vettori (per l'ID cancellato, che esiste ancora come record, solo marcato come non attivo), ma il checkout confronta quell'elenco con l'indirizzo di testata, che è diverso. Nessuna corrispondenza, nessun vettore mostrato, anche se i vettori per l'indirizzo vero esistono e sono perfettamente validi.
Perché capita soprattutto su carrelli vecchi: PrestaShop non cancella mai un carrello abbandonato, resta nel database a tempo indeterminato, e un link di recupero riapre esattamente lo stesso record, con lo stesso storico. Più tempo passa tra la creazione del carrello e il suo recupero, più è probabile che nel frattempo l'indirizzo salvato in quella riga sia stato modificato almeno una volta dal cliente.
La correzione
Serve un intervento su due livelli distinti: i carrelli già rotti in questo momento, e quelli che si romperanno allo stesso modo in futuro.
Correzione una tantum, per i carrelli già disallineati: una query che riallinea ogni riga prodotto con indirizzo cancellato all'indirizzo di testata, ma solo quando quest'ultimo è valido e diverso da quello di riga.
UPDATE ps_cart_product cp
JOIN ps_cart c ON c.id_cart = cp.id_cart
JOIN ps_address ap ON ap.id_address = cp.id_address_delivery
JOIN ps_address ah ON ah.id_address = c.id_address_delivery
SET cp.id_address_delivery = c.id_address_delivery
WHERE cp.id_address_delivery <> 0
AND ap.deleted = 1 AND ah.deleted = 0
AND cp.id_address_delivery <> c.id_address_delivery;
Su un catalogo carrelli con anni di storia, questa query può toccare diverse centinaia di righe in un colpo solo: nel caso che ha fatto scoprire il problema erano 565 righe, su 221 carrelli diversi.
Correzione strutturale, per i carrelli futuri: un override del metodo Cart::getDeliveryOptionList() che esegue lo stesso riallineamento, ma solo per il singolo carrello che si sta aprendo, prima di calcolare le opzioni di consegna. In pratica, ogni carrello vecchio "si ripara da solo" nel momento in cui arriva al checkout, senza bisogno di ripassare periodicamente su tutto il database.
Un dettaglio che vale la pena scrivere esplicitamente: questo riallineamento presuppone che indirizzi diversi tra riga e testata siano sempre un errore da correggere, il che è vero solo se il negozio non usa la spedizione multipla. Se un giorno la spedizione multipla venisse attivata, indirizzi diversi per riga diventano legittimi, e questa correzione andrebbe disattivata o ripensata: romperebbe esattamente il caso che dovrebbe funzionare.
Un'ultima categoria è stata lasciata volutamente non toccata: carrelli dove sia l'indirizzo di riga sia quello di testata risultano cancellati. Non c'è nessun indirizzo valido a cui riallinearsi, quindi non sono sistemabili con questa query. Sono carrelli vecchi mai più riaperti: se un giorno lo fossero, il flusso normale del checkout chiede comunque al cliente di scegliere o confermare un indirizzo valido prima di procedere, quindi si sistemano da soli al primo utilizzo reale.
Come verificare che tenga
- Riaprire un carrello con il disallineamento noto (o riprodurne uno: indirizzo di riga cancellato, testata su un indirizzo valido) e controllare che allo step spedizione compaiano i vettori attesi, non il messaggio di errore.
- Dopo la correzione una tantum, ricontare quante righe restano con indirizzo di riga cancellato e diverso dalla testata: deve tornare a zero.
- Verificare a codice che l'override sia effettivamente quello caricato (
get_class()sull'istanza diCartnel checkout, o un controllo sumethod_exists), non solo che il file esista sul server: un override può essere presente e non attivo se la cache delle classi non è stata rigenerata. - Ripetere il controllo periodicamente se il negozio ha molti carrelli vecchi che vengono recuperati via email o campagne di remarketing: è lì che il problema si ripresenta più spesso, perché è esattamente il tipo di carrello che accumula questo disallineamento nel tempo.
Il negozio su cui è stato diagnosticato questo caso resta anonimo di proposito. Se in un checkout PrestaShop un carrello recuperato mostra "non ci sono vettori disponibili" mentre uno nuovo con lo stesso indirizzo funziona, il posto giusto dove guardare non è la configurazione dei vettori: è se l'indirizzo salvato sulla riga del prodotto e quello salvato sulla testata del carrello sono davvero lo stesso record. Chi si occupa di manutenzione ricorrente su un negozio PrestaShop, inclusi questo tipo di casi, trova un riepilogo dei servizi su questa pagina.
