Serve materializzazione - È necessaria una materializzazione del planning
ecommerce
Carrello sessione
Carrello e-commerce persistito in sessione (guest) o DB (utente loggato) con add/update/remove riga, calcolo totali server-side, merge guest→user al login e recovery abbandonato.
Cosa fa questo modulo
Modulo «Carrello sessione»: gestione completa del carrello acquisti per e-commerce/booking/checkout digitale con doppio storage adattivo — **sessione PHP** per visitatori anonimi (guest cart) e **tabella DB persistente** per utenti loggati (user cart) — più merge automatico al login per non perdere righe aggiunte da guest. Funge da fondazione condivisa per i checkout #8319 one-shot Stripe e #8324 caparra (il carrello tiene line items, il modulo pagamento li converte in Order+PaymentIntent). Architettura storage: classe service `CartService` con interfaccia unica (`add`, `update`, `remove`, `clear`, `items`, `subtotal`, `total`, `count`, `apply_coupon`, `set_shipping`) che internamente seleziona driver — `SessionCartDriver` (storage in `session()->put('cart', $payload)` con chiave session ID, payload JSON con righe + coupon + shipping + metadata, scadenza naturale a session timeout configurabile default 120min Laravel) per guest, `DatabaseCartDriver` (storage in tabella `carts` legata a `user_id` univoco, righe in `cart_items` con riferimento polimorfico `purchasable_type`+`purchasable_id` per supportare Product/Course/Booking/Subscription/EventTicket, persistenza cross-device — cliente aggiunge da desktop poi continua da mobile) per loggati, switch automatico via middleware globale o trait `HasCart` su User. Modello dati DB: tabella `carts` (id, user_id nullable FK con index unique, currency default EUR, coupon_code nullable, shipping_method_id nullable, shipping_amount_cents, notes_customer, metadata JSON per esperimenti A/B/tracking source/UTM, abandoned_at nullable timestamp impostato da cron se inattivo X giorni, recovered_at nullable per tracciare recovery campaigns, expires_at per TTL cart inattivi, created_at/updated_at), tabella `cart_items` (id, cart_id FK, purchasable_type+purchasable_id polimorfica, sku snapshot al momento aggiunta per resilienza a cambio SKU, nome_snapshot, prezzo_unitario_cents al momento aggiunta (NB: re-validato a checkout per evitare manipolazioni client-side), quantita default 1, opzioni JSON per varianti tipo size/color/data-booking/note-personalizzata, sconti_riga_cents nullable per coupon per-riga, image_url snapshot, slug_link per back-to-product, created_at/updated_at, sort_order per drag-reorder UI), tabella `cart_abandoned_recovery` (cart_id, sent_email_count, last_email_at, recovered_at, recovery_coupon_code generato) per tracking funnel recovery. Sessione PHP guest: payload session compatibile JSON-serializzabile con stesso schema cart+items per facile migrate a DB al login (`unserialize` non usato, solo array PHP nativi+timestamp string ISO), session driver Redis raccomandato in prod per affidabilità (vs file driver con perdite su deploy/restart), session lifetime allineato a UX (es. 7gg con `cookie_lifetime` esteso per ricordare carrello tra visite anche se guest, cookie HttpOnly+Secure+SameSite=Lax). Operazioni carrello: `add($purchasable, $quantity, $options)` con validazione purchasable esiste e disponibile (stock se prodotto fisico, slot libero se booking, posti disponibili se evento), dedup riga esistente con stesse `options` (incrementa quantita invece di nuova riga, comportamento configurabile), validazione max_quantity_per_item per evitare carrelli giganti, evento `CartItemAdded` per analytics+side-effects (es. trigger upsell recommendation, log #8330); `update($itemId, $quantity)` con validazione quantità (>0 altrimenti remove, <= stock disponibile, <= max_per_item); `remove($itemId)` con conferma soft (UI mostra «Rimosso, annulla?» con possibilità undo entro 5s prima di flush definitivo); `clear()` per svuotare totalmente (es. dopo checkout success o reset manuale); `apply_coupon($code)` con validazione via #8321 modulo coupon (esiste, attivo, non scaduto, applicabile a purchasable, rispetta min_amount, non già usato da cliente), apply sconto a livello carrello (% o importo fisso o spedizione gratis) con calcolo client-side preview + ricalcolo server-side definitivo a checkout; `set_shipping($methodId, $address)` calcola costo spedizione da #8323 calendario disponibilità (se booking con slot) o tabella shipping_methods (corriere + zona + peso/volume) con preview tempo consegna. Calcolo totali server-side: `subtotal` = somma `(prezzo_unitario * quantita) - sconti_riga` per ogni item, `discount_total` = sconto coupon a livello carrello, `tax_amount` = IVA calcolata server-side (22% IT default, breakdown per aliquota se prodotti misti tipo libri 4%/alimentari 10%/standard 22%, esenzione/reverse charge B2B UE se cliente con P.IVA verificata), `shipping_amount` = costo spedizione da metodo selezionato, `total` = subtotal - discount + tax + shipping, tutti i valori in centesimi (cents) int per evitare arrotondamenti float, esposti via DTO `CartSummary` consumato da UI Blade/Livewire/API. **Anti-tampering critico**: mai accettare totali calcolati lato client — il server è source-of-truth, frontend mostra solo preview UX. Re-validazione a checkout: prima di creare PaymentIntent #8319/#8324, ricalcola tutto da DB fresco (prezzi aggiornati se cambiati da add iniziale, stock ancora disponibile, coupon ancora valido, spedizione ancora attiva), eventuale «differenza prezzo» mostrata a cliente con CTA «conferma nuovo totale» (es. prodotto in promo scaduto tra add e checkout). Merge guest→user al login: listener su evento `Auth\Events\Login` chiama `CartService::mergeGuestCartIntoUserCart($user, $sessionCartPayload)` che recupera cart utente esistente da DB (se presente), merge righe (stesso `purchasable_type+purchasable_id+options` → somma quantita rispettando max_per_item, righe diverse → append), merge coupon (warning se entrambi hanno coupon diversi, preferenza configurabile «keep user» / «keep guest» / «ask user»), flush session cart dopo merge, evento `CartMerged` per log+analytics conversion guest→user. UI patterns: **Mini-cart** (icona header con badge count totale items, dropdown hover con preview ultime 3 righe + subtotale + CTA «Visualizza carrello»+«Checkout», update real-time via Livewire poll/wire:poll o broadcast event WebSocket), **Pagina carrello** (`/cart` lista completa righe con thumbnail, nome, opzioni variante, quantita con +/- input + delete inline, sconti-riga evidenziati, subtotale per riga, sticky summary box destra con subtotal+tax+shipping+total+coupon input+CTA «Procedi al checkout» #8319, sezione «Hai dimenticato qualcosa?» con upsell prodotti correlati, save-for-later toggle per spostare riga a wishlist senza eliminare), **Empty cart state** (illustrazione + messaggio «Il tuo carrello è vuoto» + CTA «Esplora prodotti»+«Vedi le offerte»+«Continua dal tuo ultimo ordine» se cliente con storico), **Cart drawer/sidebar** (alternativa o aggiunta al mini-cart per add-to-cart con animazione slide-in laterale senza navigare via dalla product page, mantiene contesto browsing). Eventi business e hook: `CartItemAdded`/`CartItemUpdated`/`CartItemRemoved`/`CartCleared`/`CouponApplied`/`CouponRemoved`/`CartAbandoned`/`CartRecovered`/`CartConverted` (a checkout success) per analytics dashboard (funnel browsing→add→checkout→paid con drop-off per step), trigger #8331 mail recovery, log #8330 audit per debug «cliente dice mancante item che aveva aggiunto», webhook custom per integrazione marketing automation tipo Klaviyo/Mailchimp/Customer.io. Recovery carrelli abbandonati: cron `php artisan cart:detect-abandoned` quotidiano marca `abandoned_at = now()` per cart con last activity > X gg (configurabile per business model: 2gg e-commerce fast moving, 7gg booking high-consideration), cron `php artisan cart:send-abandoned-recovery` invia sequenza email #8331 a -1gg/-3gg/-7gg con copy progressivamente più urgente («hai dimenticato qualcosa nel carrello» / «i tuoi prodotti stanno aspettando — qui un -10% se torni» con coupon dinamico #8321 generato per quel cart con scadenza 48h / «ultima chiamata, scadenza coupon»), link diretto a `/cart/recover/{token}` che ripristina cart in sessione del cliente (anche se aveva svuotato per errore) — tipico 8-12% recovery rate documentato e-commerce, ROI altissimo perché email a basso costo su intento già caldo. Sicurezza e edge cases: validazione purchasable esistente ad ogni add (cliente potrebbe forge ID via DevTools), cap massimo righe carrello (es. 50 items) per evitare DoS storage, rate limit `add` endpoint (max 30/min per session/user per anti-bot), validazione quantita int positiva con cap massimo per riga (es. 999), gestione stock contestuale (riserva soft stock per X minuti dopo add per evitare oversell, rilascio se cart abbandonato), gestione concorrenza multi-tab (cliente apre 2 tab e add diverso, sync via SSE/WebSocket o reload optimistico al focus tab), gestione carrelli orfani (user eliminato → cascade delete carts), GDPR (cart con dati personali in metadata pulito su account deletion request, conservazione max 90gg dopo abandoned per ricerca prodotti). API REST/GraphQL: endpoint `GET /api/cart`, `POST /api/cart/items`, `PATCH /api/cart/items/{id}`, `DELETE /api/cart/items/{id}`, `DELETE /api/cart`, `POST /api/cart/coupon`, `DELETE /api/cart/coupon`, `POST /api/cart/shipping`, tutti con autenticazione Sanctum se user loggato o session-based per guest, response include `CartSummary` DTO completo per refresh UI atomico, supporto headless commerce (frontend React/Vue/mobile separato da backend Laravel). Pannello admin carrelli: datatable Livewire #8327 (filtri: stato `active`/`abandoned`/`recovered`/`converted`, range data ultima attività, range importo, user/guest, presenza coupon), dettaglio carrello con timeline eventi (added/updated/removed/coupon applied), azioni manuali admin (contatta cliente via mail con coupon recovery custom, forza ricalcolo totali, marca recovered manualmente, esporta carrello come quote/preventivo per cliente B2B), report (top 10 carrelli abbandonati per valore, conversion rate per source/UTM, average cart value, average items per cart, top product mai convertito da cart→order per ottimizzare pricing/copy). Comandi artisan: `php artisan cart:detect-abandoned`, `php artisan cart:send-abandoned-recovery`, `php artisan cart:purge-expired {--older-than-days=90}` (delete carrelli inattivi oltre soglia per igiene DB), `php artisan cart:reconcile-stock` (release soft-reserved stock di cart abbandonati), `php artisan cart:report-conversion {--month=}` (report conversion funnel mensile per stakeholder). Casi d'uso applicati al workspace: Footility (carrello acquisto crediti/feature con #8319 + carrelli quote multi-modulo enterprise con #8328 export), Klabhouse (carrello iscrizioni multi-evento/corso con #8324 caparra), Holiday Self Drive (carrello noleggi multi-veicolo + extra assicurazione/seggiolino bambino/GPS), gestionali (carrello acquisto licenze + add-on training + supporto extended), the-body-code/realpilates (carrello pacchetti lezioni misti con #8324 prenotazione slot), mscarichi (carrello servizi richiesti con upsell extra urgenza/orario notturno). Differenza da semplice POST diretto a checkout: qui multi-item, persistenza cross-sessione, recovery abandoned, merge guest→user, riusabile per più checkout (one-shot + caparra + abbonamento iniziale). Differenza da Magento/Shopify cart: qui leggero (no overhead full e-commerce platform), integrato nativamente Laravel/Livewire, custom-fit per esigenze workspace (booking misti + servizi + prodotti digitali). Estensioni future: wishlist separata da cart (save-for-later con notifiche prezzo/restock), shared cart B2B (team workspace condivide carrello con multiple decision maker che aggiungono/votano items prima checkout), gift cart (cliente acquista per altri con email destinatario), subscribe-and-save (toggle riga «consegna ricorrente -10%» integrato con #8320 Cashier subscription), AR/3D preview da carrello per prodotti fisici, AI cart abandonment prediction (modello su pattern browsing/dwell → score rischio abbandono → intervento real-time con chat/coupon proattivo). Costo: 0.5 giorno setup modello dati `carts` + `cart_items` + `cart_abandoned_recovery` migration, 1 giorno `CartService` + `SessionCartDriver` + `DatabaseCartDriver` con interfaccia comune + DI binding, 0.5 giorno operazioni add/update/remove/clear/apply_coupon/set_shipping con validazione, 0.5 giorno calcolo totali server-side + DTO `CartSummary` + breakdown IVA multi-aliquota, 0.5 giorno merge guest→user al login + listener evento Auth, 1 giorno UI mini-cart + pagina cart + cart drawer Livewire reattivo, 0.5 giorno integrazione #8321 coupon (apply/remove + validazione), 0.5 giorno API REST endpoint completi per headless/mobile, 0.5 giorno admin panel datatable + dettaglio + azioni manuali + report, 0.5 giorno cron detect-abandoned + send-recovery + integrazione #8331 mail + #8330 audit, 0.5 giorno testing flussi (add guest, login merge, abandoned recovery, multi-tab race, stock oversell, coupon edge cases, anti-tampering totali). Dipendenze: Laravel session driver (Redis raccomandato in prod), tabella `users` esistente per `DatabaseCartDriver`, opzionale #8321 coupon per apply sconti, opzionale #8323 calendario per gestione slot booking come purchasable, opzionale #8331 mail per recovery campaigns, opzionale #8330 audit per debug discrepanze, prerequisito per #8319 checkout one-shot e #8324 caparra (entrambi consumano `CartSummary` per creare PaymentIntent), opzionale #8327 datatable per admin panel, opzionale #8328 export per report contabilità.