# DPass — Patch 61I7: verifica PayPal e riallineamento amministrativo

Data: 25 settembre 2026. Base: codice dello ZIP `dpass(2).zip`, comprensivo della patch 61I6.

## Ambito

Nuova funzione nel report amministrativo **Transazioni pagamento**, percorso `/_dpass/admin/transazioni-pagamento`. Su ogni transazione PayPal il collegamento **Verifica PayPal** apre una pagina con riferimenti, utente, tipo di operazione, stato DPass e storico delle verifiche.

Sono disponibili due azioni:

- **Verifica soltanto**: legge l'ordine e l'incasso su PayPal e registra l'esito in `payment_events`. Non modifica lo stato del pagamento, il saldo del borsellino o i titoli.
- **Verifica PayPal e riallinea**: dopo conferma dell'operatore, se l'incasso risulta completato e coerente, completa la ricarica, la prima attivazione o il rinnovo tramite i servizi applicativi esistenti.

Il report include inoltre una ricerca per nome/email dell'utente, email della transazione, ID numerico DPass, UUID, riferimento negozio, identificativi ordine/incasso PayPal e descrizione. I filtri di comune, date, profilo, provider e stato restano attivi: una ricerca corretta può quindi non trovare un pagamento escluso dagli altri filtri.

Le prenotazioni delle strutture possono essere verificate ma **non vengono riallineate** con questa patch. Gli altri provider non hanno il nuovo pulsante.

## Nessuna nuova operazione finanziaria su PayPal

La funzione effettua esclusivamente le richieste OAuth necessarie all'autenticazione e le letture:

```text
GET /v2/checkout/orders/{order_id}
GET /v2/payments/captures/{capture_id}
```

Non crea ordini, non richiede capture, non addebita il cliente, non rimborsa. Un ordine semplicemente `APPROVED` non attiva il servizio. Sono richiesti un ordine `COMPLETED` e un singolo incasso effettivo `COMPLETED`, letto nuovamente tramite il suo ID.

La procedura utilizza il profilo associato alla transazione e ne verifica tenant, comune e ambiente. Non si possono digitare liberamente credenziali, sostituire il riferimento dell'ordine o passare automaticamente da sandbox a produzione.

Le verifiche includono: UUID e riferimento fattura/negozio, importo lordo e valuta senza approssimazioni, numero di unità/capture, riferimento ordine-incasso, eventuali rimborsi/storni, riferimenti duplicati in altre transazioni, data originale e coerenza dei dati locali durante le richieste HTTP.

Un profilo eliminato, riferimenti mancanti o dati storici incoerenti richiedono una verifica dedicata: la funzione non indovina i collegamenti e non forza il pagamento.

## Recupero applicativo e duplicati

Il recupero blocca la riga del pagamento e completa gli aggiornamenti in una transazione database. Le richieste a PayPal vengono eseguite prima dell'apertura della transazione, per non tenere i lock durante l'attesa di rete.

Per una ricarica viene usato il servizio borsellini esistente. Se il movimento è già presente e inequivocabilmente collegato allo stesso pagamento, viene riutilizzato senza accreditare nuovamente il saldo. Un collegamento perso può essere ripristinato solo se metadati, importo, valuta, proprietario e riferimenti coincidono.

Per prima attivazione e rinnovo viene usato il servizio di acquisto diretto esistente: restano i controlli sull'autorizzazione, il piano acquistato, l'ultimo periodo, la validità e la possibilità di rinnovo. Il titolo resta collegato al pagamento PayPal; **non viene accreditato il borsellino**. Se nel frattempo è stato emesso un altro periodo, la procedura non lo aggira.

Il servizio usa la data originale riportata dall'incasso, non la data del clic amministrativo. Il timestamp è convertito nel fuso dell'applicazione prima del salvataggio, mentre le regole di decorrenza continuano a dipendere dal piano esistente. Non è stata introdotta una nuova regola per far iniziare il periodo al momento del recupero.

Un secondo tentativo restituisce un esito di già allineato quando i collegamenti risultano coerenti. È stato verificato anche l'arrivo successivo di un webhook completato, senza nuovi accrediti o titoli. Se il completamento applicativo fallisce, gli aggiornamenti della transazione vengono annullati; rimane lo storico del tentativo fallito.

## Caso già sistemato a mano: attenzione

Usare innanzitutto **Verifica soltanto**.

Una ricarica o un rinnovo inseriti manualmente senza un riferimento al pagamento originale non sono sempre riconoscibili in modo automatico. La funzione **non garantisce** di identificare qualsiasi intervento manuale scollegato. Prima del recupero bisogna controllare estratto conto e titoli: il modulo richiede una dichiarazione esplicita che non esistano crediti o periodi manuali ancora da collegare.

Non selezionare la conferma solo per superare un errore; non modificare importi, riferimenti, stato o autorizzazione nel database per forzare il completamento. Un conflitto viene mostrato all'operatore e registrato.

## Autorizzazioni e audit

La lettura della pagina richiede il permesso esistente `payments.view` nel comune corrente. I profili in sola lettura possono consultare ma non avviare le azioni, perché anche una verifica crea un evento di audit.

Un amministratore di tenant abilitato alla scrittura può utilizzare entrambe le azioni. Per gli operatori comunali, la verifica richiede `payments.view` e capacità di scrittura; il riallineamento richiede anche il permesso esistente `subscriptions.manual_payments`.

Il POST è protetto da autenticazione, middleware amministrativi, isolamento tenant/comune, CSRF, validazione e limite di frequenza (12 richieste al minuto). La conferma del recupero è verificata sul server e non soltanto in JavaScript.

Ogni tentativo viene registrato in `payment_events` prima delle richieste HTTP, con ID univoco, operatore, modalità, stato locale precedente, riepilogo remoto, risultato e stato successivo. Il dettaglio mostra gli ultimi 20 tentativi amministrativi. La firma webhook è lasciata `NULL`: si tratta di una risposta REST autenticata, non di un webhook firmato. Non vengono memorizzati token OAuth, segreti o interi oggetti richiesta nell'audit amministrativo.

Esiti: verificato senza modifiche, non incassato, riallineato, già allineato oppure bloccato/errore. Il sistema distingue un incasso confermato con completamento DPass fallito da un incasso non confermato. Un tentativo rimasto in elaborazione dopo un'interruzione richiede di ricaricare la pagina e verificarne lo stato prima di riprovare.

## Installazione e pubblicazione

Nessuna migrazione, query phpMyAdmin, modifica `.env`, aggiornamento Composer/NPM o variazione del cron. Nessuna nuova credenziale o permesso da creare: sono usati quelli già presenti.

La patch sostituisce 5 file applicativi e aggiunge 6 file applicativi, 2 script di test e questa nota. Lo script di installazione controlla gli hash della base prevista, crea un backup dei soli file interessati e ripristina i file in caso di fallimento delle verifiche. Non carica il vero `.env` per eseguire i test e non avvia migrazioni o comandi applicativi sul database di lavoro.

L'installer invalida soltanto le cache standard di rotte e viste; non modifica la cache di configurazione. Su percorsi cache personalizzati o server con OPcache non autoricaricante occorre usare la procedura ordinaria del proprio ambiente.

Il ripristino dei file non annulla eventuali pagamenti riallineati in seguito tramite l'interfaccia: si tratta di operazioni applicative distinte, da non correggere cancellando movimenti o periodi alla cieca.

**La copia locale e la produzione hanno database separati.** Un riallineamento eseguito sul sito locale modifica soltanto il database locale. Inoltre un profilo PayPal di produzione copiato nel locale interroga comunque PayPal reale: preferire dati sintetici e profili sandbox per le prove, e usare il recupero del caso reale nel gestionale di produzione soltanto dopo il deploy verificato.

## Verifiche eseguite e limiti

137 controlli della nuova suite superati (il totale include i 48 controlli senza database). Sono stati esercitati i veri servizi Laravel di credito, emissione iniziale e rinnovo su dati sintetici, con le risposte HTTP di PayPal simulate e le altre richieste di rete bloccate.

Casi inclusi: prova valida; solo approvazione; capture assente; importi/valute/riferimenti discordanti; rimborsi; storni; profilo/ambiente modificato; mancata risposta o ordine assente; riferimenti duplicati; tenant/comune diversi; utenti senza permesso; verifica senza recupero; doppio tentativo; collegamenti persi; movimento già presente; modifica locale durante la chiamata; ripristino transazionale dopo errore; prima attivazione; rinnovo; validità originale; conflitto con periodo successivo; webhook tardivo; ricerca e rendering delle due pagine; conferma richiesta dal controller.

Regressioni esistenti superate: `FabrickPaymentTokenSmoke --database` (89 controlli), `SubscriptionFreeSmoke --database` (132), `FacilityBookingsSmoke --unit` (26), `ParkingSubscriptionValiditySmoke --unit` (24).

Ambiente delle prove: PHP 8.4.23; SQLite reale in memoria, collegato tramite un adattatore PDO di test basato sulla libreria SQLite locale, perché nel runtime di sviluppo del pacchetto manca l'estensione nativa `pdo_sqlite`. L'adattatore non è incluso nella patch. I test forniti usano normalmente `pdo_sqlite` nella macchina locale. La base schema dei test è sintetica e utilizza anche migrazioni esistenti: non equivale a un test delle migrazioni storiche MySQL.

Non sono stati eseguiti: incassi PayPal live/sandbox reali, verifica crittografica della firma webhook reale, test di concorrenza con processi multipli su MySQL, collaudo del database dell'utente o deploy sul suo server. Nel test del webhook tardivo soltanto la verifica della firma è simulata; il completamento applicativo è reale.

La patch offre lo strumento di riconciliazione. **Non identifica né risolve automaticamente la causa del mancato aggiornamento originario**: per quella diagnosi serviranno eventi, log e riferimenti del caso concreto.

## Riferimenti tecnici

Documentazione ufficiale PayPal consultata per i controlli di ordine/incasso:

- https://developer.paypal.com/api/orders/v2/orders-get
- https://developer.paypal.com/api/payments/v2/
- https://developer.paypal.com/api/orders/v2/definitions/order_status

Un ordine `COMPLETED` non sostituisce la verifica dello stato dell'incasso: per questo l'azione amministrativa non usa la scorciatoia esistente del solo stato ordine.
