- 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
220 lines
14 KiB
Markdown
220 lines
14 KiB
Markdown
# 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.
|