Files
pcrt-legal-return/PLAN.md
tommaso 8ca4de6c0b MVP core recesso: App Proxy flow + ricevuta durevole + design storefront
- A1: SPEC-MVP-RECESSO.md (criteri + copy IT)
- A3: App Proxy /apps/recesso — lookup guest ordine+email (anti-leak), form 2-step,
  funzione dedicata Conferma recesso, persist WithdrawalRequest + transmittedAt + AuditLog
- A4: ricevuta durevole via nodemailer (mailer.server.ts) + receiptSentAt + AuditLog
- Design: recesso.server.ts restyle token CSS light/dark, coerente Shopify
- Fix: @shopify/shopify-api pinnato 13.1.0 (dedupe) -> tsc clean
- PLAN: A5 esteso a motore di stile a 3 livelli

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mv83a29B4eFv5ixoj6PoE1
2026-07-07 09:52:47 +02:00

220 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 01):** 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 34):** 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.