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.
120 lines
6.6 KiB
Markdown
120 lines
6.6 KiB
Markdown
# 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](https://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](https://shopify.dev/changelog/level-2-protected-customer-data-requirements-are-now-needed-to-access-the-order-statuspageurl-field)).
|
|
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.
|