Files
pcrt-legal-return/PROTECTED-CUSTOMER-DATA.md
tommaso 787553af22 Protected Customer Data: autovalutazione + macchina sempre calda
PROTECTED-CUSTOMER-DATA.md - verdetto: la distribuzione CUSTOM ha accesso
'Always available' a Level 1 e Level 2 (email, statusPageUrl): nessuna approvazione
Shopify da attendere per gli store live. La review servira' per l'app pubblica (R7).
Il fatto che funzioni oggi su pcrt-reso-test non prova nulla: sui development store
la review non e' richiesta comunque.

I requisiti di sicurezza L1/L2 restano pero' obbligatori. Autovalutazione con
evidenze: cifratura a riposo OK (volume Fly ENCRYPTED=true), backup cifrati OK
(snapshot Fly), separazione test/prod OK. Gap: retention non documentata, privacy
policy assente, incident response assente, password Fly da cambiare, snapshot con
sola retention 5gg e nodo singolo (i record legali sono la PROVA del recesso).

fly.toml: min_machines_running 0 -> 1. Un cold start misurato ha richiesto ~38s;
l'art. 54-bis pretende una funzione sempre accessibile e facilmente utilizzabile.
2026-07-10 12:55:00 +02:00

6.6 KiB

Protected Customer Data — verdetto e autovalutazione

Stato: 2026-07-10. App: Legal Return PCRT (custom distribution) su Fly recesso-custom.


1. Serve l'approvazione di Shopify?

No, non per la distribuzione custom. Dalla documentazione ufficiale (shopify.dev/docs/apps/launch/protected-customer-data):

Livello Public app Custom app Admin-created custom app
Level 1 Requires review Always available Always available
Level 2 (nome, indirizzo, email, telefono) Requires review Always available Varies by plan

Conseguenze:

  • Oggi (custom, store live): nessuna approvazione da attendere. order.email e order.statusPageUrl sono accessibili.
  • Domani (R7, app pubblica): la review sarà obbligatoria. Questo documento e' la base della futura richiesta.
  • ⚠ Il fatto che oggi funzioni su pcrt-reso-test non prova nulla: sui development store la review non e' richiesta comunque. La prova e' la tabella qui sopra, non il test.

Da fare comunque nel Dev Dashboard: dichiarare quali dati e campi protetti si usano (App → API access requests → Protected customer data). Testi pronti al §3.

⚠ Attenzione: order.statusPageUrl richiede Level 2 dal 15 marzo 2024 (changelog). Lo usiamo solo per il bottone "Vedi il tuo ordine" nella ricevuta al cliente: e' una comodita', non un dato necessario. Per la data minimization della futura app pubblica valutare di rimuoverlo.


2. Dati protetti effettivamente trattati

Dato Da dove Perche' e' il minimo necessario
order.email Admin API (lookup ordine) Verifica d'identita' del consumatore guest (senza login, come impone l'art. 54-bis) e destinatario della ricevuta su supporto durevole (obbligo di legge)
Nome del consumatore Inserito dal consumatore nel form Elemento richiesto dall'art. 54-bis nella dichiarazione
Testo della dichiarazione Inserito dal consumatore E' l'atto giuridico stesso; va conservato come prova
order.statusPageUrl Admin API Solo comodita' (link "Vedi il tuo ordine"). Non necessario

NON trattiamo: indirizzi, telefono, dati di pagamento, profilazione. Nessuna vendita o condivisione a terzi. Unico sub-processor: il provider SMTP scelto dal merchant.


3. Testi per la dichiarazione nel Dev Dashboard

Protected customer data — motivazione:

L'app implementa la funzione di recesso obbligatoria ex art. 54-bis del Codice del Consumo italiano (D.Lgs 209/2025, Dir. UE 2023/2673). Per legge la funzione deve essere utilizzabile senza login: l'app deve quindi verificare l'identita' del consumatore confrontando il numero d'ordine con l'email associata all'ordine, e inviare a quell'indirizzo la ricevuta su supporto durevole con il timestamp di trasmissione. Nessun altro dato del cliente viene letto.

Protected customer field — email:

Necessaria per due obblighi di legge: (1) verificare che chi esercita il recesso sia il titolare dell'ordine (accesso guest, senza autenticazione); (2) recapitare la ricevuta su supporto durevole, che l'art. 54-bis impone di inviare senza indebito ritardo.

Protected customer field — name: non richiesto via API (lo inserisce il consumatore).


4. Requisiti Level 1 / Level 2 — stato reale

# Requisito Stato Evidenza / Gap
L1 Minimizzazione dei dati 🟡 Ok tranne statusPageUrl (non necessario)
L1 Informare il merchant su dati e finalita' 🟡 CHECKLIST-COMPLIANCE-MERCHANT.md c'e'; manca una privacy policy dell'app
L1 Uso limitato alle finalita' dichiarate Nessun uso secondario
L1 Periodi di retention definiti Non documentati. shop/redact fa purge totale; customers/redact pseudonimizza. Manca la policy scritta
L1 Cifratura in transito HTTPS ovunque; SMTP con STARTTLS/TLS
L1 Cifratura a riposo Volume Fly pg_data ENCRYPTED = true. Password SMTP per-shop cifrate AES-256-GCM (crypto.server.ts)
L2 Backup cifrati Snapshot automatici Fly (cifrati perche' il volume lo e')
L2 Separazione test / produzione Dev = Postgres in Docker locale; prod = Fly Postgres. DB e app distinti
L2 Accesso staff limitato 🟡 Un solo titolare. Ma la password master Fly era in chiaro nel file Cred Fly: da cambiare
L2 Password robuste Vedi sopra: cambio password Fly non confermato
L2 Access log 🟡 Log applicativi Fly (effimeri) + AuditLog append-only con payload hashati. Nessun log di accesso al DB
L2 Incident response policy Non esiste. Va scritta
L2 Data loss prevention Snapshot con retention 5 giorni, nodo singolo, nessuna copia off-site. Vedi §5

5. Due rischi che non sono formalita'

5.1 Durabilita' dei record legali

Gli snapshot Fly hanno retention 5 giorni e vivono nella stessa infrastruttura. Sono disaster recovery, non archivio. Ma le WithdrawalRequest e l'AuditLog sono la prova del recesso: se il termine si estende a 12 mesi per mancata informativa, o se sorge una controversia, quei record devono esistere ben oltre 5 giorni. Un solo nodo, nessuna copia off-site: se l'app Postgres viene cancellata, la prova sparisce.

Fix: dump logico periodico verso storage esterno (o Fly Managed Postgres / Supabase, che hanno backup gestiti con retention lunga). Non e' un requisito Shopify — e' il motivo per cui l'app esiste.

5.2 Cold start sulla funzione di recesso

min_machines_running = 0: la macchina si spegne. Una richiesta a freddo ha impiegato 38 secondi. L'art. 54-bis pretende una funzione "sempre accessibile" e "facilmente utilizzabile": 38 secondi di attesa dopo il clic sono un ostacolo, e con ogni probabilita' il consumatore abbandona.

Fix: min_machines_running = 1 (una macchina sempre calda).


6. Azioni, in ordine

  1. Cambiare la password Fly (era in chiaro). — utente
  2. min_machines_running = 1 nel fly.toml. — codice, banale
  3. Backup off-site dei record legali (dump periodico). — da progettare
  4. Privacy policy dell'app + retention policy scritte. — documenti
  5. Incident response policy (chi, cosa, entro quanto). — documento
  6. Dichiarare dati e campi nel Dev Dashboard (testi al §3). — utente
  7. (Per R7, app pubblica) valutare la rimozione di statusPageUrl per minimizzazione.