# Pizeta Recesso — App Shopify per la funzione di recesso ### Piano di sviluppo multi-agente > **Obiettivo:** App Shopify riutilizzabile che rende qualsiasi negozio conforme alla funzione > elettronica di recesso ("pulsante di recesso") obbligatoria — **Art. 54-bis Codice del Consumo** > (D.Lgs 209/2025), recepimento Dir. (UE) 2023/2673. In vigore per contratti conclusi **dal 19 > giugno 2026**. I merchant sono esposti *da adesso*. > > **Base legale verificata:** vedi `ANALISI-REQUISITI-LEGALI.md` (fonti primarie/secondarie). > Le voci ⚠ restano da confermare su EUR-Lex prima del go-live. --- ## 0. Decisioni bloccate | Tema | Decisione | Nota | |---|---|---| | Distribuzione | **Custom distribution ora (2-3 store live) → Public molto più avanti** | Custom = no review, ok per pochi store. Account separati → 1 registrazione custom per store. Codice riusato per il futuro public | | Backend | **Template Shopify Remix (Node) + Polaris** | Strada ufficiale Shopify | | Hosting | **Fly.io** | Riuso setup esistente | | Database | **Postgres + Prisma** | Fly Postgres (default) o Supabase (ho le creds) — decido in Fase 0 | | Email (ricevuta durevole) | **Stub in MVP**, provider (Resend/Postmark) da decidere prima del go-live | Ricevuta con timestamp | | Store di test | **SOLO `pcrt-reso-test`** (usa e getta) | ⚠ MAI pizeta-pharma-2 o store da trasferire: `shopify app dev`/install app DISABILITA il transfer in modo irreversibile | | Consegna storefront | **Theme App Extension + App Proxy** | installazione senza toccare il tema, guest-capable | **La cartella tema (`pizeta-pharma/`) NON è l'app** — è un tema Liquid, solo target di test. L'app è questo repo separato. --- ## 0-bis. ⚠️ VINCOLO CRITICO — trasferimento store (IRREVERSIBILE) **Il landmine è STRETTO: solo le DRAFT app / `shopify app dev` disabilitano il transfer.** Lanciare `shopify app dev` (draft app) contro un development store — o abilitare developer preview — **disabilita il trasferimento in modo permanente e irreversibile** (CLI issue #3946). Uno store transfer-disabled non si trasferisce più: andrebbe ricostruito da zero. **Cosa NON disabilita il transfer** (verificato): - **Custom distribution via install link** (la nostra app recesso finita): si installa come una normale app, **non** applica "Transfer Disabled". Installabile anche su store del cliente (via link store-specifico, scadenza 7 giorni; ownership non richiesta). - **Legacy/admin custom app** (es. "Import Negozio"): non disabilita il transfer. **Regole operative:** 1. Sviluppo/test (**`shopify app dev` = draft**) **solo su `pcrt-reso-test`** (usa e getta). ← unica vera protezione. 2. **Mai** `shopify app dev` / draft contro **pizeta-pharma-2** o store destinati al cliente. 3. **App finita (custom link):** installabile su pizeta + altri store, **prima o dopo** il transfer, senza rompere niente. Per zero-rischi assoluti, installa **dopo** il transfer (banale, via link). 4. ⚠ Nuance da confermare empiricamente: install custom su un client-transfer store ancora "in your org" (pre-transfer) — fonti in conflitto. Se vuoi installare pre-transfer, testa prima. --- ## 0-ter. Strategia distribuzione & isolamento ambienti **Principio:** *una build public-grade, distribuita Custom ora.* Non due sviluppi. Vincolo di piattaforma (verificato): una app **Public — anche unlisted — richiede review Shopify** per installarsi su store **live**. Solo **Custom** installa su live senza review. Quindi: - **ORA:** distribuzione **Custom** (unico canale per il live senza review) su 2-3 store clienti. - **DOMANI:** **nuova registrazione Public** + review, stesso codice. La distribuzione non è convertibile, ma non si converte: si aggiunge una seconda registrazione. **Codice public-grade dal giorno 1** (così il public futuro = re-registrazione, non rebuild): 1. Template Shopify CLI/Remix standard — OAuth multi-tenant, session token (no scorciatoia single-store). 2. Dati **multi-tenant per-shop** dal giorno 1, anche con soli 2-3 negozi. 3. **Webhook GDPR obbligatori** subito. 4. **Billing API dietro flag** (`isPublic`): off in custom, on in public. Unico ramo di codice divergente. 5. Performance/sicurezza a livello review **man mano**, non retrofit. **Isolamento ambienti (anti-rottura) — creato separato dal giorno 1 in A0:** ``` pcrt-reso-test → staging (Fly) → recesso-custom (Fly, CLIENTI LIVE) sviluppo collaudo stabile — deploy solo testati — DB proprio recesso-public (Fly, DOMANI) → registrazione Public + DB proprio stesso codice, deployment SEPARATO → non tocca mai recesso-custom ``` Registrazioni Partner distinte + Fly app distinti + DB distinti → il deploy della public **non può** impattare gli store live sulla custom (isolamento strutturale, non per disciplina). **Regole anti-rottura sulla custom-live:** test su `pcrt-reso-test` prima; migrazioni DB solo **additive/backward-compatible**; theme extension **versionate** (rollback); **API version pinnata**. --- ## 1. Architettura ``` STOREFRONT (acquirente) ├─ Theme App Extension (app embed) → pulsante "recedere dal contratto qui" └─ App Proxy /apps/recesso → flusso recesso guest-capable sul dominio dello shop 1. lookup: Order ID + email (guest — buona prassi per "facilmente accessibile") 2. form: nome, dichiarazione, email (precompilato se loggato) 3. STEP 2 conferma: "conferma recesso" (funzione dedicata, anti-accidentale) 4. successo + ricevuta durevole inviata BACKEND (Fly.io) ├─ App Remix (admin embedded, Polaris) ├─ Postgres (Prisma): richieste, regole, impostazioni, audit ├─ Motore compliance: calcolo finestra, esclusioni Art. 59, tipo-prodotto, scadenza ├─ Ricevuta durevole: email con testo dichiarazione + timestamp di TRASMISSIONE ├─ Webhook: fulfillment (avvio finestra) + 3 GDPR obbligatori └─ Billing (aggiunto in fase App Store) ``` **Perché Theme App Extension + App Proxy:** il pulsante acquirente + form guest devono vivere sul dominio del negozio e sopravvivere agli aggiornamenti del tema senza che il merchant tocchi il Liquid. App Proxy consente a un URL storefront (`/apps/recesso`) di raggiungere il backend restando sul dominio dello shop — necessario per l'accesso guest e per "facile come acquistare". --- ## 2. Matrice requisiti (il contratto su cui ogni agente costruisce) Corretta e integrata dopo `ANALISI-REQUISITI-LEGALI.md`. | # | Requisito legale | Funzionalità | Criteri di accettazione | |---|---|---|---| | R1 | Funzione visibile, facilmente accessibile, **continuativamente disponibile** per tutta la finestra | Pulsante extension + pagina proxy sempre attiva, legata al singolo ordine | Label **"recedere dal contratto qui"**; funzione attiva tutta la finestra di quell'ordine | | R2 | Accesso facile (guest = buona prassi, non obbligo) | Lookup Order ID + email | Guest completa il recesso con solo n° ordine + email; nessun login forzato | | R3 | Raccolta nome, id contratto, mezzo elettronico, dichiarazione | Form recesso | I 4 dati raccolti + persistiti | | R4 | Conferma a 2 step, funzione dedicata | Funzione **"conferma recesso"** | Invio impossibile senza 2ª azione esplicita; **no dark pattern** | | R5 | Ricevuta su supporto durevole **senza ritardo**, con dichiarazione + **timestamp di trasmissione** | Email transazionale | Email < 1 min; contiene testo dichiarazione + data/ora di trasmissione | | R6 | Onere probatorio del professionista | Persistenza DB + **audit log immutabile** | Record immutabile richiesta + timestamp + invio ricevuta | | R7 | Funzione **non più onerosa della conclusione** (anti dark pattern) | UX ≤ passaggi del checkout | ≤ passaggi/attriti del checkout; no pop-up dissuasivi, pre-flag, retention nudge | | R8 | Termine 14gg, decorrenza per tipo | Motore compliance | Scadenza corretta per beni (consegna/ultimo lotto) / servizi (conclusione) / digitale | | R9 | Esclusioni Art. 59 (su misura, deperibili, sigillati igiene) | Regole esclusione (prodotto/collezione/tag) | Item escluso segnalato con motivo; no recesso "cieco" | | R10 | Non conformità → 14gg → 12 mesi+14gg (Art. 53) | Warning misconfig + calcolo corretto | Admin avvisa su misconfig; scadenza riflette proroga quando applicabile | | R11 | Pulsante **AGGIUNTIVO**, non sostitutivo | Coesistenza modulo tipo Allegato I-B + email | App non disabilita/nasconde gli altri canali | | R12 | Obblighi info **Art. 49** (ubicazione funzione) | Testo info recesso + link ubicazione | Info diritto di recesso + posizione funzione mostrate | | R13 | Rimborso: spese consegna incluse; reso a carico consumatore se informato; diminuzione valore | Logica rimborso + informativa | Schermata informa su chi paga reso; calcolo include spedizione standard | | R14 | Lingua del contratto/offerta | i18n (IT/EN/DE/FR/ES…) | Flusso acquirente + ricevuta localizzati | | R15 | Solo **B2C** | Filtro ordini | Ordini B2B esclusi dal flusso obbligatorio | | R16 | Webhook GDPR obbligatori Shopify | `customers/data_request`,`customers/redact`,`shop/redact` | Tutti e 3 implementati + HMAC verificato | | R17 | Sanzione pulsante non conforme = pratica scorretta Art. 27 (AGCM, fino €10M/4%) | (rischio, non feature) | Documentato; guida merchant alla conformità | --- ## 3. Modello dati (bozza Prisma) - **Shop** — dominio, accessToken(cifrato), piano, installedAt - **Settings** — labelPulsante, brandColors, indirizzoReso, giorniFinestraDefault, overrideMercati, testoInfoRecesso - **ExclusionRule** — scope(prodotto|collezione|tag|tutto), targetId, motivo(sumisura|deperibile|igiene), attiva - **WithdrawalRequest** — shopId, orderId, orderName, nomeCliente, email, testoDichiarazione, **trasmessoAt (ts)**, locale, canale(guest|account), tipoProdotto, stato, ricevutaInviataAt, scadenzaCalcolata - **AuditLog** — shopId, evento, hashPayload, ts (append-only, immutabile) - **WebhookEvent** — topic, ricevutoAt, processato (idempotenza) --- ## 4. Piano multi-agente (agenti SPECIALIZZATI, esecuzione SEQUENZIALE) Preferenza utente: agenti **super-specializzati**, eseguiti **uno alla volta** (parallelo solo dove strettamente utile), **minimo consumo di token**. Ogni agente = un brief focalizzato con input / deliverable / criteri di uscita espliciti. **Nessun avvio automatico** — parto al tuo ok. ### Fase 0 — Fondamenta *(gate: tutto il resto dipende da qui)* - **A0 architect** — init app Remix, schema Prisma, config Fly, igiene segreti, collegamento ai 2 dev store, CI. **Uscita:** app installata su entrambi i dev store, `/health` verde su Fly, DB migrato. ### Fase 1 — Specifica compliance *(gate: il contratto di accettazione)* - **A1 compliance-mapper** — trasforma `ANALISI-REQUISITI-LEGALI.md` + §2 in criteri di accettazione testabili + copy deck (etichette IT esatte, template ricevuta, testo info recesso), risolve i punti ⚠. **Uscita:** file criteri approvato contro cui ogni agente testa. ### Fase 2 — MVP core (minimo legalmente conforme) *(sequenziale)* - **A2 storefront-extension** — Theme App Extension + App Proxy lookup guest (R1,R2,R7,R11). - **A3 backend-flow** — endpoint recesso, conferma 2 step, persistenza + timestamp (R3,R4,R6). - **A4 receipt** — email durevole con dichiarazione + timestamp trasmissione (provider stub) (R5). - **Uscita:** guest su dev store immacolato completa il recesso → record + ricevuta. **Questo è già conforme.** ### Fase 3 — Admin merchant *(sequenziale; A5→A6)* - **A5 admin-UI + motore di stile estensibile** — Polaris: dashboard richieste, impostazioni, testo info (R12). **Theming a 3 livelli** (requisito utente: "super estendibile, personalizzabile da template"): 1. **Token da Settings** (no-code) — colore brand, font, raggio, logo, larghezza → override delle CSS variables del form (base già pronta: il form del design è tutto a token `:root`). 2. **Eredita tema** — opzione App Proxy in **Liquid** → il form è reso dentro il tema del negozio (look nativo, "template del merchant"). 3. **CSS custom** — escape hatch per personalizzazione totale. Merchant sceglie il livello; default = card a token controllata. Può diventare un agente dedicato (theming) se troppo grande per A5. - **A6 compliance-engine** — esclusioni Art. 59 + calcolo finestra/scadenza per tipo prodotto + warning misconfig + logica rimborso (R8,R9,R10,R13,R15). **Uscita:** merchant configura esclusioni/finestre; item escluso si comporta secondo R9. ### Fase 4 — Robustezza *(A7→A8 sequenziali, poi A9 audit)* - **A7 i18n** — localizzazione IT/EN/DE/FR/ES (R14). - **A8 compliance-webhooks** — 3 webhook GDPR + verifica HMAC + idempotenza (R16). - **A9 qa-security-auditor** — avversariale: HMAC, abuso lookup guest (enumeration/rate-limit), gestione PII, edge case (fulfillment parziale, beni digitali, calcolo scadenza). **Uscita:** report audit, zero criticità. ### Fase 5 — App Store pubblica *(quando custom → public)* - **A10 billing** — Shopify Billing API + piani. - **A11 app-store-submission** — listing, screenshot, privacy, checklist review. **Uscita:** pacchetto pronto per submission. ### Grafo dipendenze ``` A0 → A1 → A2 → A3 → A4 → A5 → A6 → A7 → A8 → A9 → A10 → A11 (sequenziale per default; A7/A8 parallelizzabili solo se serve accelerare) ``` --- ## 5. Milestone - **M1 (Fase 0–1):** scaffold + contratto compliance. *Nulla di visibile, ma toglie rischio a tutto.* - **M2 (Fase 2):** **MVP conforme live su pizeta.** ← primo valore reale, azzera esposizione legale. - **M3 (Fase 3–4):** production-grade, configurabile, auditato. - **M4 (Fase 5):** submission App Store. --- ## 6. Rischi / punti di attenzione - **Sovrapposizione nativo Shopify** — Shopify ha regole reso/cancellazione UE; non ricostruire i resi, avvolgerli. Verificare in A1 cosa è nativo vs gap. - **Competitor esistenti** (Rescindly, REVER) — differenziare su conformità IT esatta + semplicità + prezzo. - **Lookup guest = superficie di abuso** — enumeration ordini; serve rate-limit + match email + nessun leak su miss (A9). - **Prova supporto durevole** — ricevuta riproducibile/loggata, non fire-and-forget (A4/A6). - **Calcolo scadenza** — consegna vs conclusione, beni digitali, fulfillment parziale; fonte unica di verità nel motore (A6). - **PII** — dichiarazione + email = dati personali; cifratura at rest + webhook GDPR non negoziabili (A8). - **Base legale ⚠** — confermare numeri articoli UE e inquadramento Art. 27 su fonte primaria (A1). --- ## 7. Punti aperti (non bloccanti) 1. DB: Fly Postgres vs Supabase Postgres — decido in A0. 2. Provider email — decido prima di M2. 3. Automazione rimborso — fuori scope MVP; confermare che il tracking manuale basta al lancio.