{"id":40,"date":"2026-09-08T10:00:00","date_gmt":"2026-09-08T08:00:00","guid":{"rendered":"https:\/\/www.phabdev.it\/blog\/?p=40"},"modified":"2026-08-30T02:34:23","modified_gmt":"2026-08-30T00:34:23","slug":"casi-maledetti-diari-di-uninfrastruttura-ecommerce","status":"publish","type":"post","link":"https:\/\/www.phabdev.it\/blog\/2026\/09\/08\/casi-maledetti-diari-di-uninfrastruttura-ecommerce\/","title":{"rendered":"Casi Maledetti: Diari di un&#8217;Infrastruttura eCommerce"},"content":{"rendered":"<p><em>Tre storie reali di bug invisibili, perdite finanziarie silenziose e ingegneria deterministica nel backend operativo.<\/em><\/p>\n<hr \/>\n<h2>1. Il bug invisibile di WooCommerce: quando i webhook smettono di parlare (e come evitarlo)<\/h2>\n<p>Luned\u00ec mattina, ore 11:30. Il reparto logistico telefona in amministrazione: &#8220;Come mai oggi non ci sono ordini da preparare? \u00c8 tutto fermo da stamattina&#8221;.<br \/>\nL&#8217;eCommerce Manager apre la dashboard dello store WooCommerce: 23 ordini ricevuti e pagati regolarmente nelle ultime tre ore. Eppure, sul gestionale l&#8217;elenco \u00e8 vuoto. Nessun server \u00e8 caduto, non ci sono errori nei log di sistema, la CPU \u00e8 al 5% e il database risponde in millisecondi. Tutto sembra apparentemente normale.<\/p>\n<p>Questo \u00e8 il guasto silenzioso classico delle integrazioni WooCommerce trascurate.<\/p>\n<h3>La Causa: La disattivazione silenziosa di WooCommerce<\/h3>\n<p>Quando un ordine viene completato, WooCommerce invia una notifica HTTPS (webhook) al sistema di destinazione. Se il server dell&#8217;ERP o della logistica ha un micro-ritardo temporaneo (ad esempio un timeout di rete di 5 secondi dovuto a un sovraccarico momentaneo), la chiamata fallisce. <br \/>\nWooCommerce riprova a consegnare il webhook. Se riscontra pochi fallimenti consecutivi, attiva una funzione nativa devastante: <strong>imposta lo stato del webhook su &#8220;disabled&#8221; nel database, in silenzio, senza avvisare nessuno.<\/strong><\/p>\n<p>Da quel secondo in poi, l&#8217;integrazione smette di funzionare. Il merchant lavora alla cieca finch\u00e9 un magazziniere non si accorge del vuoto anomalo sulle linee di spedizione.<\/p>\n<h3>La Soluzione: Riconciliazione asincrona + Watchdog attivo<\/h3>\n<p>Un&#8217;integrazione professionale non si basa sulla speranza che la rete e i webhook funzionino sempre. Si progetta partendo dal presupposto che prima o poi falliranno.<\/p>\n<p>Per risolvere questo problema alla radice, la nostra infrastruttura PHABDEV applica tre livelli di sicurezza:<\/p>\n<ol>\n<li><strong>Il Poller di Sicurezza<\/strong>: Accanto al webhook in tempo reale, facciamo girare un processo di polling periodico controllato da un cursore temporale (<code>WOO_LOOKBACK_MIN<\/code>). Se il webhook salta, il poller recupera l&#8217;ordine mancante al ciclo successivo.<\/li>\n<li><strong>La query corretta su <code>modified_after<\/code><\/strong>: Molti sviluppatori usano il parametro <code>after<\/code> (data di creazione dell&#8217;ordine) per interrogare le API di WooCommerce. Questo \u00e8 un errore grave: se un ordine nasce in stato &#8220;pending&#8221; (non pagato) e viene pagato dopo tre ore, il poller con filtro sulla data di creazione lo salter\u00e0 per sempre. Usiamo rigorosamente <code>modified_after<\/code>, cos\u00ec ogni cambio di stato riporta l&#8217;ordine nel mirino della sincronizzazione.<\/li>\n<li><strong>Il Watchdog delle API<\/strong>: Un monitor interno controlla ogni minuto il timestamp dell&#8217;ultimo ciclo riuscito di ciascun poller (<code>ultimoOkMs<\/code>) e lo stato del webhook WooCommerce. Se il webhook passa da <code>active<\/code> a qualsiasi altro stato, il Watchdog notifica immediatamente il team tecnico su Telegram, indicando la scheda di ripristino del Runbook prima che il cliente finale o la logistica avvertano il disservizio.<\/li>\n<\/ol>\n<hr \/>\n<h2>2. GID vs ID: Come Shopify ha quasi sdoppiato la contabilit\u00e0 di un merchant<\/h2>\n<p>Nel collaudo end-to-end di una nuova integrazione custom per un importante merchant Shopify, l&#8217;ordine di test #1003 \u00e8 apparso due volte nell&#8217;anagrafica del gestionale aziendale. Due righe identiche, due fatture emesse con protocolli consecutivi, magazzino scalato del doppio rispetto alla merce realmente venduta.<\/p>\n<p>L&#8217;integrazione possedeva un registro di idempotenza robusto per impedire le doppie scritture. Eppure, la barriera di sicurezza era stata aggirata.<\/p>\n<h3>La Causa: Il doppio formato degli identificativi di Shopify<\/h3>\n<p>Andando a ispezionare i log dei payload in ingresso, abbiamo scoperto l&#8217;origine del conflitto. L&#8217;architettura utilizzava due vie di ricezione per garantire l&#8217;alta affidabilit\u00e0: i webhook in tempo reale (REST) e un poller GraphQL Admin API per la riconciliazione notturna.<\/p>\n<ul>\n<li>Il webhook REST trasmetteva l&#8217;identificativo dell&#8217;ordine come stringa numerica classica: <code>\"1003\"<\/code>.<\/li>\n<li>Il poller GraphQL rispondeva usando il formato globale di Shopify (GID): <code>\"gid:\/\/shopify\/Order\/1003\"<\/code>.<\/li>\n<\/ul>\n<p>Per la pipeline di ricezione naive, si trattava di due entit\u00e0 distinte. Il registro di idempotenza ha creato due chiavi differenti nel database e ha fatto passare l&#8217;ordine due volte, sdoppiando la transazione contabile.<\/p>\n<h3>La Soluzione: Normalizzazione stringente al confine dell&#8217;adapter<\/h3>\n<p>La Clean Architecture impone una regola ferrea: <strong>le differenze specifiche delle piattaforme non devono mai contaminare il nucleo dell&#8217;applicazione.<\/strong><\/p>\n<p>Abbiamo risolto estraendo la logica di normalizzazione degli ID all&#8217;estremo confine dell&#8217;adapter Shopify, prima che l&#8217;ordine venga convertito nel modello canonico interno:<\/p>\n<pre><code class=\"language-typescript\">export function shopifyOrderExternalId(rawId: string): string {\n  if (rawId.startsWith('gid:\/\/')) {\n    const parti = rawId.split('\/');\n    return parti[parti.length - 1];\n  }\n  return rawId;\n}<\/code><\/pre>\n<p>Che arrivi un GID GraphQL o un intero REST, l&#8217;adapter lo normalizza in un&#8217;unica stringa numerica pura. Il nucleo del middleware riceve esclusivamente l&#8217;ID pulito, garantendo l&#8217;idempotenza assoluta.<\/p>\n<p>Inoltre, abbiamo implementato un guardrail logico essenziale: <strong>la chiave di idempotenza viene registrata sul database persistente solo dopo che la riserva di magazzino \u00e8 andata a buon fine.<\/strong> Se un ordine viene rifiutato a monte per stock insufficiente, la chiave non viene bruciata. Questo permette all&#8217;ordine di essere riconsegnato e accettato automaticamente non appena lo stock viene rifornito, senza essere erroneamente scartato come duplicato.<\/p>\n<hr \/>\n<h2>3. La trappola del magazzino condiviso: quando lo SKU ripetuto genera oversell<\/h2>\n<p>Un merchant che vende abbigliamento di fascia alta su pi\u00f9 canali (eCommerce proprietario, Amazon e Zalando) si ritrova improvvisamente con una giacenza negativa sul gestionale. Ha venduto due volte l&#8217;ultima felpa taglia L rimasta a scaffale. Entrambi i clienti hanno pagato e si aspettano la consegna il giorno successivo. Uno dei due ordini dovr\u00e0 essere annullato manualmente, con conseguente danno d&#8217;immagine e recensione negativa.<\/p>\n<p>La sincronizzazione stock era attiva ogni 15 minuti. Come \u00e8 stato possibile l&#8217;oversell?<\/p>\n<h3>La Causa: Il controllo di giacenza sequenziale (naive)<\/h3>\n<p>Il payload arrivato dal marketplace presentava un&#8217;anomalia strutturale: lo stesso identico articolo (stesso SKU) era presente su due righe d&#8217;ordine distinte (ad esempio, per via di una promozione speciale e di un acquisto standard accorpati dal carrello del cliente).<\/p>\n<p>Il sistema di controllo stock naive dell&#8217;eCommerce scorreva le righe dell&#8217;ordine una alla volta ed eseguiva la verifica:<\/p>\n<ol>\n<li><strong>Riga 1<\/strong>: Richiesto 1 pezzo dello SKU <code>NRD-TSH-01<\/code>. Stock disponibile: 1. Esito: Disponibile (accettato).<\/li>\n<li><strong>Riga 2<\/strong>: Richiesto 1 pezzo dello SKU <code>NRD-TSH-01<\/code>. Stock disponibile: 1. Esito: Disponibile (accettato).<\/li>\n<\/ol>\n<p>Il codice valutava la disponibilit\u00e0 riga per riga senza aggiornare lo stato temporaneo in memoria prima del commit finale dell&#8217;ordine. Ha fatto passare un ordine che richiedeva 2 pezzi totali a fronte di 1 solo pezzo realmente disponibile, mandando il magazzino in negativo.<\/p>\n<h3>La Soluzione: Aggregazione preventiva e riserva atomica &#8220;Tutto o Niente&#8221;<\/h3>\n<p>La nostra pipeline d&#8217;integrazione previene questa collisione aggregando preventivamente il fabbisogno per SKU prima di interrogare il magazzino, trattando l&#8217;intera riserva come una transazione atomica (&#8220;tutto o niente&#8221;):<\/p>\n<pre><code class=\"language-typescript\">\/\/ Sotto-carrello aggregato in memoria prima della riserva stock\nconst fabbisogno = nuoveRighe.reduce((acc, riga) =&gt; {\n  acc[riga.sku] = (acc[riga.sku] || 0) + riga.qty;\n  return acc;\n}, {} as Record&lt;string, number&gt;);<\/code><\/pre>\n<p>La funzione di riserva valuta lo stock sull&#8217;intero fabbisogno aggregato dell&#8217;ordine (2 pezzi richiesti a fronte di 1 disponibile). Se anche una sola riga o un solo articolo aggregato supera la disponibilit\u00e0 reale, l&#8217;intero ordine viene bloccato sollevando un <code>InsufficientStockError<\/code> ed emettendo un evento <code>stock.rejected<\/code> nel feed della dashboard.<\/p>\n<p>L&#8217;ordine viene respinto in totale sicurezza prima che possa corrompere i dati contabili o generare spedizioni fantasma. Questa \u00e8 ingegneria dei flussi, non copia manuale dei dati.<\/p>\n<hr \/>\n<h2>Da dove partire per mettere in ordine l&#8217;infrastruttura<\/h2>\n<p>PHABDEV lavora proprio su questo livello operativo degli eCommerce, con integrazioni tra shop e gestionale, automazioni del backoffice e portali B2B su misura. \u00c8 una scelta sensata quando il problema non \u00e8 la singola schermata, ma il flusso completo dei dati.<\/p>\n<p>Se vuoi partire da un controllo concreto, puoi usare la valutazione online dell&#8217;eCommerce su <a href=\"https:\/\/my.kesbatty.app\/c\/valuta-il-tuo-ecommerce\" target=\"_blank\" rel=\"noopener\">questo percorso guidato<\/a>, con <strong>ventotto domande su sette aree operative<\/strong>, un tempo di compilazione di <strong>cinque minuti<\/strong> e un punteggio dettagliato per area. Se il tema ti tocca da vicino, puoi anche vedere come lavoro su <a href=\"https:\/\/www.phabdev.it\">PHABDEV<\/a>, perch\u00e9 qui la differenza non la fa il software in vetrina, ma lo strato che tiene in piedi ordini, magazzino e gestionale ogni giorno.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tre storie reali di bug invisibili, perdite finanziarie silenziose e ingegneria deterministica nel backend operativo. 1. Il bug invisibile di WooCommerce: quando i webhook smettono di parlare (e come evitarlo) Luned\u00ec mattina, ore 11:30. Il reparto logistico telefona in amministrazione: &#8220;Come mai oggi non ci sono ordini da preparare? \u00c8 tutto fermo da stamattina&#8221;. L&#8217;eCommerce [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_yoast_wpseo_title":"","_yoast_wpseo_metadesc":"","_yoast_wpseo_focuskw":"","rank_math_title":"","rank_math_description":"","rank_math_focus_keyword":""},"categories":[1],"tags":[],"class_list":["post-40","post","type-post","status-publish","format-standard","hentry","category-senza-categoria"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/posts\/40","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/comments?post=40"}],"version-history":[{"count":1,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/posts\/40\/revisions"}],"predecessor-version":[{"id":41,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/posts\/40\/revisions\/41"}],"wp:attachment":[{"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/media?parent=40"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/categories?post=40"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.phabdev.it\/blog\/wp-json\/wp\/v2\/tags?post=40"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}