diff --git a/ANALISI-REQUISITI-LEGALI.md b/ANALISI-REQUISITI-LEGALI.md index aeb8823..0f6c8ce 100644 --- a/ANALISI-REQUISITI-LEGALI.md +++ b/ANALISI-REQUISITI-LEGALI.md @@ -56,3 +56,52 @@ 2. ⚠ Inquadramento sanzionatorio definitivo (Art. 27 vs Artt. 62-66 Cod. Cons.) e importi post-Omnibus. 3. Testo integrale Art. 54-bis da Normattiva (non solo Brocardi) per etichette e dati esatti. 4. Verifica cosa Shopify copre nativamente (regole reso/cancellazione UE) vs gap reali. + +--- + +## 5. Recesso parziale — punto CHIARITO (2026-07-10) + +**Domanda:** il recesso si applica all'intero ordine o si puo' spacchettare per singolo bene? + +**Risposta su due piani distinti.** + +### 5.1 Diritto sostanziale: il recesso parziale e' AMMESSO +Il recesso puo' avere ad oggetto tutti i beni (totale) o soltanto parte di essi (parziale). +Coerente col **modulo tipo** (Allegato I, parte B), che prevede di indicare "i seguenti beni". +Nota: sul parziale le **spese di consegna non si rimborsano** (importo fisso, indipendente dal +numero di pezzi). + +### 5.2 La FUNZIONE di recesso (art. 54-bis) NON deve offrire la selezione dei beni +Fonte primaria — **Considerando 37, Direttiva (UE) 2023/2673**: + +> "If the consumer has ordered multiple goods or services within the same distance contract, +> the trader **can** provide the consumer with the possibility to withdraw from a part rather +> than the whole of the contract." + +**"can", non "must"**: offrire il recesso parziale nella funzione e' una **facolta' del +professionista**, non un obbligo. Conformita' minima = il consumatore deve poter identificare +**il contratto** da cui recede. + +Sei fonti italiane indipendenti convergono sull'elenco dei dati richiesti dall'art. 54-bis: +nome + **elementi identificativi del contratto** + mezzo elettronico per la conferma. Nessuna +menziona la selezione dei beni. (Stefanelli, CMS, Studio Legale MP, LegalBlink, Alexander Greco, +Rescindly.) + +⚠ Un commento anglosassone (NatLawReview) sostiene il contrario ("the interface must allow the +customer to select the particular product(s)"): e' una **sovra-lettura del considerando 37**. + +### 5.3 Conseguenza per l'app +Una funzione di recesso **a livello di ordine e' conforme**. Il consumatore conserva comunque il +diritto al recesso parziale attraverso gli **altri canali** (modulo tipo / email), che per legge +restano validi e non vanno disabilitati. +Il recesso parziale nel pulsante e' quindi una scelta di **prodotto**, non un obbligo di legge. + +### 5.4 Panorama concorrenti (2026-07-10) +- **Revize** (revize.app): implementa la selezione articoli ("the customer selects which items to withdraw"); pre-compila e blocca nome/contratto/contatto dall'ordine. +- **Rescindly** (rescindly.eu): form = numero ordine, nome, email, motivo. **Nessuna selezione articoli** -> livello ordine. +- **Guida IFG eCommerce**: raccomanda esplicitamente l'implementazione **a livello di ordine**. +- **LegalBlink**: NON e' un concorrente — e' un generatore di documenti legali (condizioni di vendita, privacy, condizioni di recesso), non un widget di recesso. +- **Shopify**: **nessun pulsante di recesso nativo** (a maggio 2026). Gli strumenti annulla/rimborsa sono lato merchant e non soddisfano i requisiti art. 11a (funzione rivolta al consumatore, 2 step, etichettata, con ricevuta). + +### 5.5 Sanzione confermata +Mancata conformita' -> il termine di recesso si estende automaticamente di **12 mesi**. diff --git a/PLAN.md b/PLAN.md index ab53fa5..f115ed3 100644 --- a/PLAN.md +++ b/PLAN.md @@ -181,6 +181,15 @@ input / deliverable / criteri di uscita espliciti. **Nessun avvio automatico** 3. **Istruzioni di reso configurabili**: `returnAddress` (già in schema), `returnAtCustomerExpense` (default ON — Art. 57: informa + rende il cliente responsabile delle spese), `returnInstructions` (testo opzionale). **Resta MANUALE per scelta Pizeta** (nessuna automazione richiesta): resi parziali + rimborso proporzionale (pannello Shopify), emissione rimborso (Shopify manda la sua mail), verifica data consegna se il corriere non passa l'evento a Shopify. **Uscita:** merchant attiva/disattiva ciascun comportamento; il flusso si adatta allo stato dell'ordine. +- **A6-ter recesso parziale** *(OPZIONALE — non è un obbligo di legge)* — Base legale in `ANALISI-REQUISITI-LEGALI.md` §5: il **considerando 37 Dir. (UE) 2023/2673** dice che il professionista **"can"** offrire il recesso su parte del contratto, non che **deve**. Un pulsante a livello di ordine è conforme; il parziale è **differenziazione di prodotto** (lo fa Revize, non lo fa Rescindly) e copre il caso reale Pizeta (3 confezioni, ne rende 2). + - Toggle per-shop **`partialWithdrawalEnabled`** (default **OFF** = comportamento attuale, un clic = recesso totale). + - Passo 2: elenco articoli dell'ordine con quantità; **default tutti selezionati** (nessun attrito per chi vuole il totale). + - Dichiarazione **generata sugli articoli scelti** (oggi dice "intero ordine": non si può lasciare così se il cliente seleziona un sottoinsieme). + - `createShopifyReturn`: reso limitato alle **righe/quantità selezionate**. + - `autoCancelUnfulfilled`: si attiva **solo** se la selezione copre l'intero ordine. Parziale + non evaso → order edit / rimborso parziale, che resta manuale. + - **Idempotenza da rivedere**: oggi la chiave è `(shop, orderId)` (commit `fa8ee9c`), corretta solo nel mondo total-only. Col parziale deve diventare `(shop, orderId, articoli)`: il cliente può recedere per l'articolo A oggi e per il B domani, entrambi legittimi nella finestra. + - Dati da aggiungere: `lookupOrder` deve restituire titoli, quantità e ID riga; mappatura `lineItem ↔ fulfillmentLineItem` per il reso; nuova colonna (es. `items Json`) su `WithdrawalRequest`. + - **Uscita:** merchant può abilitare il parziale; con toggle OFF il comportamento resta identico a oggi. ### Fase 4 — Robustezza *(A7→A8 sequenziali, poi A9 audit)* - **A7 i18n** — localizzazione IT/EN/DE/FR/ES (R14). @@ -218,6 +227,7 @@ integrati. Dettaglio stato/commit nella memoria di progetto + git. - **R1 — A6-bis operatività per stato ordine (Pizeta-confirmed)** — 3 toggle per-shop: `stateAwareEmail` (ricevuta differenziata non-evaso/spedito-consegnato), `autoCancelUnfulfilled` (annullo automatico ordini non evasi), `returnAtCustomerExpense`+`returnInstructions`+`returnAddress` (istruzioni reso, Art. 57). Dettaglio: sezione A6-bis. - **R2 — A6 residuo copy compliance** — G7: promemoria rimborso 14gg + facoltà di trattenuta (Art. 56) nella notifica al merchant. *(G6 spese-reso assorbito da R1.)* +- **R2-bis — A6-ter recesso parziale** *(opzionale, toggle `partialWithdrawalEnabled`, default OFF)* — non è compliance ma prodotto: selezione articoli/quantità, dichiarazione sugli articoli scelti, reso limitato alle righe scelte, auto-annullo solo se selezione = intero ordine, idempotenza su `(ordine, articoli)`. Vedi A6-ter e `ANALISI-REQUISITI-LEGALI.md` §5. - **R3 — Deploy custom (go-live)** — Fly.io + Postgres prod + URL stabile + tunnel Cloudflare nominato + link install custom sui 2-3 store live. *(Serve per usarlo davvero; occhio deadline transfer pizeta-pharma-2 ~metà luglio 2026.)* - **R4 — A9 hardening / QA-security** — rate-limit robusto, retry ricevuta fallita, idempotenza webhook, review avversariale (enumeration lookup, HMAC, PII), Protected Customer Data (approvazione Shopify per leggere `order.email` in prod). - **R5 — A7 i18n** — IT/EN/DE/FR/ES. diff --git a/app/app/lib/recesso.copy.ts b/app/app/lib/recesso.copy.ts index cfa948d..08a4a50 100644 --- a/app/app/lib/recesso.copy.ts +++ b/app/app/lib/recesso.copy.ts @@ -23,9 +23,20 @@ export const FIELD = { // Dichiarazione precompilata (editabile), deriva dall'Allegato I-B. export function statementTemplate(orderName: string): string { - return `Con la presente comunico il recesso dal contratto di vendita relativo all'ordine ${orderName}.`; + return `Con la presente comunico il recesso dal contratto di vendita relativo all'intero ordine ${orderName}.`; } +/** + * Ambito del recesso esercitato tramite questa funzione. La funzione dell'art. + * 54-bis puo' legittimamente operare a livello di CONTRATTO (ordine): offrire + * il recesso parziale e' una facolta' del professionista (considerando 37 Dir. + * UE 2023/2673, "the trader CAN provide..."), non un obbligo. Il diritto al + * recesso parziale resta comunque esercitabile dagli altri canali (modulo tipo, + * email), che non vanno mai disabilitati: per questo li indichiamo. + */ +export const SCOPE_HINT = + "Questo recesso riguarda l'intero ordine. Per restituire solo alcuni articoli, contatta il negozio."; + // Informazioni sul diritto di recesso (Art. 49) + alternative (coesistenza). // Mostrate su richiesta dal pulsante info "i", per non appesantire il flusso. export const INFO_TITLE = "Il tuo diritto di recesso"; diff --git a/app/app/lib/recesso.server.ts b/app/app/lib/recesso.server.ts index d52a7a9..7e85307 100644 --- a/app/app/lib/recesso.server.ts +++ b/app/app/lib/recesso.server.ts @@ -16,7 +16,14 @@ import { createHash } from "node:crypto"; import type { AdminApiContext } from "@shopify/shopify-app-remix/server"; import type { ExclusionRule } from "@prisma/client"; -import { FIELD, INFO_TITLE, INFO_BODY, CONFIRM_LABEL, PAGE_TITLE } from "./recesso.copy"; +import { + FIELD, + INFO_TITLE, + INFO_BODY, + CONFIRM_LABEL, + PAGE_TITLE, + SCOPE_HINT, +} from "./recesso.copy"; // --------------------------------------------------------------------------- // Costanti path storefront (prefix "apps" + subpath "recesso" da shopify.app.toml). @@ -1107,6 +1114,7 @@ export function renderStep2(data: { +

${escapeHtml(SCOPE_HINT)}

diff --git a/app/app/routes/app.settings.tsx b/app/app/routes/app.settings.tsx index 0996054..b638de7 100644 --- a/app/app/routes/app.settings.tsx +++ b/app/app/routes/app.settings.tsx @@ -536,6 +536,17 @@ export default function SettingsPage() { onChange={setAutoCancelUnfulfilled} helpText="Al recesso, se l'ordine non è evaso: annullo + rimborso automatici. Irreversibile." /> + {autoCancelUnfulfilled ? ( + + + Il recesso da questa funzione riguarda tutto l'ordine. + Se il cliente voleva restituire solo alcuni articoli, + l'annullo automatico gli cancella e rimborsa l'intero + ordine, e non è reversibile. Con ordini multi-articolo + valuta di tenerlo spento e decidere caso per caso. + + + ) : null}