Only this pageAll pages
Powered by GitBook
1 of 57

Sviluppatori e partner tecnologici

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Integrazioni

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Sviluppo

Loading...

Loading...

Standard e convenzioni

Introduzione

Questo documento è rivolto a sviluppatori, tecnici e Partner Tecnologici ed è volto a fornire informazioni tecniche sull'architettura, le integrazioni, le interfacce disponibili nella piattaforma.

La piattaforma OpenCity Italia semplifica la realizzazione e la gestione di servizi digitali per cittadini e imprese, anche da smartphone (art. 7 CAD).

OpenCity Italia è open source, scaricabile da Developers Italia o disponibile come servizio pronto all'uso (Software as a Service).

La piattaforma è sviluppata con l'obiettivo di erogare servizi per centinaia di Enti contemporaneamente da una singola installazione, per questo motivo il codice della piattaforma è organizzato in microservizi indipendenti tra loro e il dialogo tra gli stessi è realizzato mediante stream di eventi piuttosto che mediante chiamate dirette alle API.

Per queste scelte l'installazione e la gestione della piattaforma in un ambiente di produzione richiede un ampio ventaglio di conoscenze:

  • Sistema Operativo GNU/Linux e uso della command line (CLI)

  • database PostgresQL, MongoDB, Redis

  • Docker e sistemi di orchestrazione di container

  • Kafka e KsqlDB

Per contribuire alla piattaforma, a seconda dell'area su cui si desidera sviluppare sono necessarie cometenze nei seguenti linguaggi di programmazione:

  • PHP

  • NodeJS

  • Python

  • Golang

Infine la piattaforma è in costante evoluzione e nuovi componenti vengono aggiunti o prendono il posto di componenti esistenti divenuti obsoleti.

Architettura

In questa area della documentazione si fornisce una descrizione mediante diagrammi dell'architettura logica della piattaforma.

Il Pagamento

In questa sezione viene descritto in dettaglio il json del pagamento e la definizione dettagliata dei campi

Pattern: microservizi

Un'architettura di microservizi è costituita da un insieme di servizi ridotti autonomi.

Ogni servizio è autonomo e deve implementa una singola funzionalità all'interno di un contesto delimitato. Un contesto delimitato è una divisione naturale all'interno di un Ente e fornisce un limite esplicito all'interno del quale esiste un modello di dominio.

Ogni microservizio della piattaforma è distribuito come docker container, ha un proprio versionamento ed espone una o più porte per comunicare con l'esterno.

I microservizi sono esposti all'esterno da un web router che assolve solitamente anche il ruolo di terminatore di SSL.

Ad ogni release della piattaforma vengono aggiornati uno o più microservizi.

Roadmap

Per facilitare il lavoro di chi lavora su integrazioni e sulle API della piattaforma, forniamo una visione generale della roadmap.

Per ulteriori dettagli si veda https://gitlab.com/groups/opencity-labs/-/milestones

Integrazioni con il flusso delle pratiche

La piattaforma può offrire sia una esperienza completa al cittadino o all'impresa, che consente l'invio di pratiche e la loro gestione nel backend, sia farsi carico di uno solo di questi aspetti, lasciando ad altri sistemi il ruolo di interfaccia con il cittadino o a sistemi verticali il compito di gestire pratiche (pratiche edilizie o in ambito sociale)

In presenza di questa esigenza è importante che la piattaforma e il sistema terzo siano in grado di instaurare e mantenere nel tempo la relazione tra le pratiche, mantenere aggiornati lo stato della pratica o di un pagamento pendente per la stessa.

Vari strumenti nella piattaforma consentono di mantenere aggiornate le informazioni e ricevere aggiornamenti:

  1. API ReST

  2. Webhook

Prima di entrare nei dettagli di questi strumenti merita avere uno sguardo di insieme del flusso della pratica e di tutti gli stati che può attraversare. Nel grafico che segue il tratto continuo più forte rappresenta il flusso più frequente di una pratica, il tratto punteggiato rappresenta i flussi alternativi. I cerchi costituiscono gli stati finali di una pratica

Per una visione generale di tutti gli stati che una pratica può assumere e quali sono i cambi di stato ammessi segue un grafico più generale. Nel grafico sono evidenziati in azzurro gli stati fondamentali della pratica, mentre in grigio gli stati deprecati.

Vista generale

Il sistema è costituito da un core, che assolve ai compiti principali e da alcuni sottosistemi dedicati a compiti specifici: gestione dei pagamenti, protocollazione, sistema di analytics.

Core

Il componente principale, il core, è ovviamente il frutto del lavoro dei primi anni di sviluppo della piattaforma, ed assolve ai compiti principali della stessa:

  1. Definizione e organizzazione dei Servizi Digitali

  2. Definizione dei Moduli on-line

  3. Gestione delle Pratiche inviate dai cittadini e dei messaggi

  4. Gestione degli utenti

  5. Gestione Uffici e appuntamenti on-line su slot di tempo e orari di apertura.

  6. Gestione Sale pubbliche e altre risorse a prenotazione flessibile

Il core assolve anche al compito di interfaccia utente, sia per i cittadini che per gli operatori e gli amministratori degli Enti.

All'interno del core sono presenti alcune integrazioni con sistemi di protocollo e di intermediari di pagamento PagoPA: si tratta delle prime integrazioni realizzate quando lo sviluppo era di tipo monolitico, successivamente sono stati creati i sottosistemi dedicati alle integrazioni con Protocolli e Pagamenti. In futuro probabilmente queste integrazioni verranno riscritte come microservizi indipendenti, alleggerendo le responsabilità del core e guadagnandone anche la semplicità del codice e la sua manutenibilità.

Il core è costituito dai seguenti microservizi:

  • Caddy / Symfony App (dal 2023 i due servizi sono distribuiti in una immagine unica: registry.gitlab.com/opencontent/stanza-del-cittadino/core/app) Nota

  • Form Server, implementazione del server di Form.IO: registry.gitlab.com/opencontent/stanza-del-cittadino/form-server

  • Form Builder, un editor multi-tenant con accesso riservato agli amministratori della piattaforma per l'editing dei componenti condivisi dei form degli Enti: registry.gitlab.com/opencontent/stanza-del-cittadino/formbuilderjs

  • Varnish, un cache proxy server che incrementa l'affidabilità e la scalabilità del formserver e del catalogo dei servizi: registry.gitlab.com/opencontent/varnish:latest

  • Gotenberg, a Docker-powered stateless API for PDF files: gotenberg/gotenberg:7

  • Payment Updates, ascolta gli eventi relativi ai pagamenti e aggiorna le pratiche di conseguenza: registry.gitlab.com/opencontent/stanza-del-cittadino/payment-updater

  • Signature Check, api usata dal componente sdc_upload di form.io per validate le firme dei documenti firmati .p7m: geopartner/signcheckwebapi:latest

Per un esempio completo di deploy dei microservizi si veda il file nel repositoy del core, che fornisce una configurazione funzionante della piattaforma.

L'immagine Caddy / Symfony App viene usata sia per esporre la UI e le API, sia per l'esecuzione dei cron jobs, per un esempio si veda il file docker-compose usato in ambiente di sviluppo..

Utilizza inoltre i seguenti servizi di persistenza:

  • PostgreSQL, per la gestione di tutti i dati e delle configurazioni della piattaforma, ad eccezione delle form. La versione usata in sviluppo è la 11 e comprende anche l'estensione PostGIS: postgis/postgis:11-3.3-alpine

  • MongoDB, per la persistenza dei moduli realizzati con l'editor di Form.IO: mongo:4.2

  • Redis, per la gestione delle sessioni degli utenti e della cache applicativa: redis:5-alpine.

docker-compose.yml
Organizzazoine generale del sistema
Servizi che costituiscono il core

Nuova Area Personale del cittadino, per le pratiche generate dalla piattaforma e per le pratiche generate in altre piattaforme.

Erogazione dei servizi digitali mediante Widget React

Gestione di Documenti in Area Personale

Estensione del sistema di notifica al di fuori del flusso delle pratiche.

Pagamenti in Area Personale e integrazione con Checkout

https://gitlab.com/groups/opencity-labs/-/epics/29
https://gitlab.com/groups/opencity-labs/-/epics/18
https://gitlab.com/groups/opencity-labs/-/epics/38
https://gitlab.com/groups/opencity-labs/-/epics/37
Cover
Cover
Cover
Cover
Cover
Cover

Requisiti per l'integrazione

Introduzione

L’area personale di OpenCity Italia è progettata per integrarsi con vari intermediari di pagamento utilizzati da Comuni ed enti pubblici. Questa sezione descrive i requisiti tecnici necessari per sviluppare e implementare l'integrazione tra la piattaforma e l'intermediario di pagamento PagoPA.

Le interazioni avvengono in seguito ad alcune azioni da parte dei cittadini e degli operatori:

  • Invio di una pratica da parte del cittadino

  • Approvazione di una pratica da parte di un operatore nel caso di un pagamento posticipato

  • Importazione di dovuti da parte dell'operatore mediante API file csv

  • Pagamento di un dovuto da parte del cittadino a seguito dell'importazione di quest'ultimo

Requisiti Generali

Per sviluppare l'integrazione con l'intermediario di pagamento PagoPA, sono richiesti i seguenti elementi:

  • Documentazione Tecnica: Manuali, guide e riferimenti API.

  • Endpoint/Ambiente di Test: URL degli endpoint e accesso a un ambiente di test per effettuare chiamate API.

  • Modalità di Verifica dello Stato del Pagamento: Specifiche delle modalità di verifica dello stato del pagamento, tramite polling o notifica.

Dettagli Tecnici dell'Integrazione

Per integrare OpenCity Italia Area Personale con l’intermediario di PagoPA, sono necessari i seguenti parametri minimi:

Creazione di un Pagamento

  • Parametri per la creazione di un pagamento

  • Parametri per la creazione di una Marca da Bollo Digitale

  • Restituzione del numero IUV e/o del numero avviso in fase di creazione del pagamento

Scaricamento Documenti

  • Parametri per scaricare l’avviso di pagamento cartaceo

  • Parametri per scaricare la ricevuta telematica una volta effettuato il pagamento

Dovuto

  • API per il download degli importi dovuti (se disponibile)

Specifiche delle API

L’intermediario di pagamento deve fornire documentazione dettagliata delle API, comprensiva di:

  • Endpoint

  • Metodi supportati (GET, POST, etc.)

  • Parametri richiesti e facoltativi

  • Formato dei dati (JSON, XML)

  • Esempi di richieste e risposte

Configurazione di Rete

Per garantire le comunicazioni fra la piattaforma e l’intermediario di pagamento, è necessario sbloccare gli IP delle chiamate entranti verso gli endpoint dell’intermediario di pagamento. Gli IP di OpenCity da sbloccare sono:

  • 93.41.234.251/32

  • 78.47.94.152/32

  • 54.220.150.231/32

  • 63.33.155.248/32

  • 37.186.144.203/32

Supporto Tecnico

Per risolvere eventuali complicazioni, errori o bug durante l'integrazione, è fondamentale avere un contatto tecnico dedicato. Si prega di fornire i seguenti dettagli del contatto tecnico:

  • Nome

  • Email

  • Telefono

  • Disponibilità (orari di lavoro)

Integrazione con Protocollo Informatico

La piattaforma è responsabile della gestione della protocollazione dei documenti derivanti dai diversi processi burocratici che essa offre. Il documento generato è conforme alle linee guida stabilite dall'AGID(si veda la sezione Documento digitale). Esso è soggetto a protocollazione in determinati casi. Tale processo viene eseguito attraverso l’integrazione con un sistema di protocollazione esterno. Nella seguente sezione verrà esaminato in dettaglio come tale integrazione deve essere fatta, descrivendo gli attori, i componenti coinvolti e le loro interazioni.

Usecase: Il Documento originato dalle pratiche dai servizi digitali

La generazione di documenti varia a seconda del tipo di pratica che deve essere registrata o gestita. Ci sono diverse tipologie di pratica, ognuna delle quali corrisponde a uno stato specifico della procedura:

  1. Creazione di una pratica da parte o per conto di un cittadino (application-request): questo si riferisce alla creazione iniziale di una pratica da parte di un cittadino o da parte di qualcuno agendo per conto del cittadino.

  2. Invio di una richiesta di integrazioni da parte di un operatore (integration-request): questo si verifica quando un operatore richiede ulteriori informazioni o documenti relativi a una pratica.

  3. Invio di un'integrazione da parte del cittadino (integration-response): In questo caso, il cittadino risponde alle richieste di integrazione inviando ulteriori documenti o informazioni.

  4. Approvazione/rifiuto di una pratica da parte di un operatore (application-outcome, application-revocation): questo rappresenta l'atto di approvare o rifiutare una pratica da parte di un operatore.

  5. Ritiro di una pratica da parte di un cittadino (application-withdraw): un cittadino può ritirare una pratica in qualsiasi momento.

Queste tipologie rappresentano diversi stati o fasi attraverso cui una pratica può passare durante il suo ciclo di vita. Poiché una pratica può attraversare più di uno di questi stati, ciò significa che saranno generati diversi documenti associati alla stessa pratica, ognuno dei quali corrisponderà a una fase specifica del processo.

Pattern: event sourcing

Un'architettura guidata dagli eventi è costituita da producer eventi che generano un flusso di eventi e consumer eventi che sono in ascolto degli eventi.

Streaming eventi: gli eventi vengono scritti in un log. Gli eventi sono rigorosamente ordinati (in una partizione) e durevoli. I client non sottoscrivono il flusso, ma un client può invece leggere da qualsiasi parte del flusso. Il client è responsabile di far avanzare la propria posizione nel flusso. Questo significa che un client può aggiungersi in qualsiasi momento e può riprodurre gli eventi.

Elaborazione del flusso di eventi. Usare una piattaforma di flussi di dati come Apache Kafka, come pipeline per inserire gli eventi e fornirli agli elaboratori di flussi. Gli elaboratori di flussi intervengono per elaborare o trasformare il flusso. Possono essere presenti più elaboratori di flussi per sottosistemi diversi nell'applicazione.

API ReST

Le API consentono di leggere e modificare informazioni sulla piattaforma in modo programmatico.

Una API ReST, nota anche come API RESTful, è un'interfaccia di programmazione delle applicazioni (API o API web) conforme ai vincoli dello stile architetturale ReST, che consente l'interazione con servizi web RESTful. Il termine REST, coniato dall'informatico Roy Fielding, è l'acronimo di REpresentational State Transfer.

La documentazione delle API ReST della piattaforma è pubblicamente disponibile in ogni Ente all'indirizzo:

Ad esempio per il comune demo della piattaforma è possibile consultarle alla pagina:

Autenticazione

Alcune API richiedono un'autenticazione prima di essere chiamate, in questo caso un cittadino o un operatore possono usare le proprie credenziali per ottenere un token di autenticazione come segue:

Richiesta token di autenticazione

POST https://servizi.comune.bugliano.pi.it/lang/api/auth

Con questa chiamata si riceve un token di autenticazione in formato JWT, che dovrà essere inviato per tutte le successive chiamate che richiedono autenticazione come bearer token nell'header Autorization

Request Body

Name
Type
Description

username*

String

nome utente

password*

String

password

Alcuni esempi di chiamate

Esempio di chiamata con Postman

Esempio di chiamata da linea di comando con il client Httpie:

echo '{ "username": "my-api-user", "password": "xxxx" }' | \
http post https://servizi.comune.bugliano.pi.it/lang/api/auth

Esempio di chiamata da linea di comando con il client curl:

curl \
--header 'Content-Type: application/json' \
--data $'{\n "username": "my-api-user",\n "password": "xxxx"\n}' \
https://servizi.comune.bugliano.pi.it/lang/api/auth 

Versioni delle API

Le API sono disponibili in due versioni, la "1" e la "2". Le capacità delle API sono le stesse per le due versioni, nella versione 2 c'e' una migliore gestione del formato JSON, quindi si consiglia l'uso della versione più recente.

È possibile specificare la versione delle Api che si vanno ad interrogare, questo è possibile in 2 modi differenti:

  • tramite parametro version da passare come parametro delle GET

  • tramite parametro X-Accept-Version da specificare nell' header della richiesta

La versione di default è per retrocompatibilità la 1

Dalla versione 2 le chiavi dei valori del campo data delle applications non sono più restituite come un insieme di strighe piatto separato dal punto (.) ma come campo json con le chiavi esplose.

Nota bene: si raccomanda di specificare sempre la versione di API utilizzata, anche se è il default (1), perche' in futuro il default potrebbe cambiare.

Webhooks

Sono un meccanismo di comunicazione tra sistemi che consentono una comunicazione in tempo reale tra due API

Le API non sono sufficienti per realizzare una buona integrazione tra due sistemi. Se due o più sistemi si devono aggiornare reciprocamente in base agli eventi che avvengono al proprio interno, è necessario che periodicamente ogni sistema interroghi gli altri per sapere se il loro stato è cambiato: questo approccio si chiama anche polling. Questo è molto oneroso e introduce un ritardo nell'aggiornamento che è tanto maggiore quanto maggiore è l'intervallo con cui un sistema interroga gli altri. D'altra parte se di intensifica la frequenza del polling si avranno maggiori costi a fronte di aggiornamenti magari poco frequenti.

Un modo più efficiente di far dialogare due sistemi è avviare una comunicazione in occasione di cambiamenti rilevanti per il sistema in ascolto e per ottenere questo, eliminando del tutto la necessità di fare polling, è possibile usare i Webhook.

Ogni webhook consente di inviare un aggiornamento a una API di un altro sistema che sta ascolto, in base agli eventi che si desidera comunicare.

Per fare test sui webhook si consiglia di utilizzare un servizio come Request Inspector.

Integrazione Widget servizio (FormIO)

Widget Formio – Servizio permette di integrare i form registrati all’interno del portale servizi del Comune. Di seguito la guida all’installazione e configurazione.

1. Installazione

Aggiungi dentro il tag <head> della tua pagina questi riferimenti:

2. Utilizzo del tag <widget-formio>

Posiziona <widget-formio> nel punto della pagina in cui vuoi mostrare il form:

Attributi

Nome
Obbligatorio
Valori / Descrizione

3. Storybook & Demo

Tutte gli esempi dei servizi sono documentati in Storybook:

4. Configurazione Servizi Built-in Personalizzati

Per aggiungere o personalizzare un servizio built-in esistente (Richiedi assistenza, Prenota appuntamento, ecc.), è necessario configurare il campo identifier come amministratore nella sezione dettaglio del servizio.

  • Il valore di identifier deve seguire il formato: <nome-servizio>-custom dove:

    • <nome-servizio> è obbligatorio e corrisponde al servizio di base (helpdesk, bookings, ecc.)

    • -custom è suffisso libero a scelta per distinguere la versione personalizzata

Esempi:

  • helpdesk-custom per una versione custom di Richiedi assistenza

  • bookings-custom per una versione custom di Prenota appuntamento

Questa configurazione permette al widget di riconoscere e caricare il servizio personalizzato attraverso il tag <widget-formio>.

Processo di sviluppo

Test delle chiamate SOAP/REST dell'intermediario

Le API fornite nella documentazione dell'intermediario vengono generalmente testate in prima battuta via Postman/Insomnia nel caso di chiamate REST o via SoapUI nel caso di chiamate SOAP

Test in ambiente locale

A seguito dei test delle chiamate, si procede con l'implementazione del microservizio. Per l'ambiente di sviluppo viene utilizzato Docker e Docker compose come da esempio nel seguente repository :

Stati del documento

<!-- JavaScript (deferred) -->
<script
  defer
  src="https://static.opencityitalia.it/widgets/formio/latest/js/web-formio.js"
></script>

<!-- CSS -->
<link
  rel="stylesheet"
  href="https://static.opencityitalia.it/widgets/formio/latest/css/web-formio.css"
/>
<widget-formio
  service-id="0121edc9-57d8-45f2-ae92-260f45aec6d5"
  base-url="https://servizi.comune-qa.bugliano.pi.it/lang"
  formserver-url="https://form-qa.stanzadelcittadino.it"
  pdnd-url="https://api.qa.stanzadelcittadino.it/pdnd/v1"
  enable-draft="true"
></widget-formio>

service-id

Sì

UUID del servizio/Form registrato sulla “Stanza del Cittadino”

base-url

Sì

URL della “Stanza del Cittadino” (autenticazione, privacy e routing del widget).

formserver-url

Sì

URL del Form Server Form.io (endpoint per recupero e invio dei form).

pdnd-url

Sì

URL del servizio PDND (Piattaforma Digitale Nazionale Dati) per il caricamento delle anagrafiche.

enable-draft

No

true / false – abilita il salvataggio in bozza delle pratiche. Default: false.

https://widget-formio.comune-qa.bugliano.pi.it/
https://gitlab.com/opencity-labs/area-personale/protocol-proxy-sipal

Configurazione dei pagamenti

Panoramica

La configurazione dei pagamenti avviene interrogando delle API apposite fornite dal proxy di riferimento. Quest'ultimo fornisce inoltre un form come json schema sviluppato con form.io mediante il quale l'utente inserisce tali configurazioni.

Per creare un form utilizzare il seguente builder: https://formio.github.io/formio.js/app/builder

Single Sign-On

Per SSO si intende la possibilità per il cittadino di fare login sull'area personale o su un servizio digitale e non dovrelo ripetere se si sposta su un altro servizio digitale dello stesso Ente.

Multilingua

La piattaforma supporta il multilinguismo dal punto di vista tecnico. Le due lingue supportate sono Italiano e Tedesco

Schema di Funzionamento

Panoramica dei ciclo di funzionamento dei pagamenti di Opencity Italia - Area Personale.

Creazione di un pagamento

Un pagamento nasce da una interazione con il cittadino: il cittadino presenta una pratica per la quale l'Ente richiede un pagamento.

Rispetto al Core della piattaforma, i pagamenti sono gestiti mediante un insieme dedicato di microservizi

Il cuore dell'integrazione con un intermediario di pagamento è un microservizio apposito che dialoga con l'intermediario da un lato, implementando le chiamate specifiche in protocollo ReST o SOAP, e con il core della piattaforma dall'altro lato, mediante la lettura e la scrittura su un topic di Kafka.

Componenti coinvolte

Componenti del core della piattaforma

In questo momento i pagamenti hanno una relazione 1 a 1 con le pratiche, di conseguenza ci sono due servizi che fanno da intermediari tra le pratiche e i pagamenti, ovvero il payment dispatcher e il payment updater.

Core

È l’interfaccia dalla quale l’utente usufruisce di un servizio digitale, inserendo i dati di una pratica attraverso un modulo: se il servizio prevede un pagamento, i dati di questo sono presenti nella descrizione JSON della pratica e saranno utilizzati dal dispatcher per creare il pagamento.

Payment dispatcher

È il microservizio che, a partire dall'evento di una pratica nel topic applications, crea l'evento del pagamento nel topic payments.

Payment dispatcher API

È il microservizio che sostituirà il Payment Dispatcher, esso, anziche leggere l'evento di una pratica dal topic applications, esporrà un API che, quando chiamata, crea l'evento del pagamento nel topic payments. Questo permette di avere una soluzione unica per gestire tutti i flussi di pagamento della piattaforma, quindi sia pagamenti creati a partire dalle pratiche, che i pagamenti importati tramite csv o API esterne.

Payment updater

Il microservizio legge gli eventi dal topic payments e contestualmente aggiorna lo stato della pratica cambiando lo stato da Payment pending a Inviata.

Sottosistema dei pagamenti

Payment proxy

E' il microservizio che gestisce la comunicazione e l’aggiornamento della posizione debitoria con l'Intermediario di pagamento di riferimento. Esso gestisce inoltre il salvataggio delle configurazioni tramite le quali può interagire con l'intermediario di pagamento di riferimento. Queste configurazioni sono inserite dall'operatore mediante l'interfaccia del Core, il quale comunica con il payment proxy mediante delle apposite API che quest'ultimo serve.

Ksql

E' un microservizio che ascolta dal topic payments e crea un database interrogabile con SQL contenente tutti i pagamenti in stati non definitivi.

Payments poller

E' il microservizio che monitora lo stato dei pagamenti: periodicamente legge su KSQL l'elenco dei pagamenti aperti e invia per ognuno di questi una chiamata alla "update.url" specificata nel pagamento stesso: questa url è una url del proxy stesso che consente al proxy di interrogare l'Intermediario per avere lo stato aggiornato del pagamento.

Checkout pagoPA API

Questo microservizio si occupa di centralizzare il pagamento online dei dovuti in un'unica API, ovvero quella del checkout di pagoPA, in questo modo si astrae dall'intermediario con cui ci si deve integrare facilitando quindi lo sviluppo dell'integrazione e omogeneizzando l'esperienza di pagamento del cittadino, che diventa quindi indipendente dall'intermediario tramite qui andrà a pagare.

Gli stati di un pagamento

Dettaglio degli stati che attraversa un pagamento nel suo ciclo di vita

STATO PAGAMENTO
DESCRIZIONE
NOTE

Architettura del sistema di protocollazione

Questa pagina descrive l'architettura impiegata per attuare il processo di protocollazione di una pratica

Seguendo l'approccio del viene descritta l'architettura utilizzata per attuare il processo di protocollazione. Partendo da una panoramica ad alto livello si va sempre più in dettaglio nell'architettura fino a raggiungere il secondo livello del C4 model.

1 System Context

Per poter attuare il processo di protocollazione, l'area personale del cittadino si avvale dei sistemi di protocollazione esterni utilizzati dai diversi tenant. È fondamentale notare che la piattaforma è un servizio SaaS di tipo multi-tenant.

L’area personale del cittadino effettua diverse interazioni con il sistema di protocollo, ma tutte seguono una logica comune. Le interazioni avvengono in seguito ad alcune azioni da parte cittadini e degli operatori:

  1. Invio di una pratica da parte del cittadino

  2. Invio di una richiesta di integrazioni da parte di un operatore

  3. Invio di una integrazione da parte del cittadino

  4. Approvazione/rifiuto di una pratica

Altre azioni possibili, mediante configurazioni avanzate del servizio:

  1. Creazione di una pratica da parte di un operatore per conto di un cittadino

  2. Ritiro di una pratica da parte del cittadino

  3. Annullamento di una pratica da parte di un operatore

Per ognuna di queste operazioni viene prodotto un documento. A partire dall'entità documento si costruiscono le richieste per i sistemi di protocollazioni esterni.

2 Container Diagram

Il processo di protocollazione è stato realizzato seguendo i principi del Domain Driven Design utilizzando il pattern Event Sourcing.

Di conseguenza si è scelto di separare il dominio dei servizi dal dominio dei documenti. In questo modo, i documenti sono indipendenti dalla loro fonte di origine e possono essere generati sia per esempio dalle pratiche, sia da altre strutture dati diverse senza che il sistema di protocollazione debba subire modifiche o integrazioni.

La piattaforma è multi tenant e multi servizio: ogni tenant può selezionare un diverso sistema di protocollazione per ogni diverso servizio che offre. Questo significa che da qualche parte la piattaforma deve tenere traccia delle configurazioni di protocollo scelte per un determinato tenant e i servizi abilitati per il sistema scelto. Inoltre va tenuta traccia anche delle configurazioni specifiche di ogni sistema di protocollazione.

Per tenere traccia di tali configurazioni è necessario un meccanismo di storage persistente(il cui tipo è da definire a seconda dei casi), mentre la scelta su chi detiene la responsabilità di gestire tali configurazioni è stata attribuita al Protocol Proxy.

Questa decisione è stata dettata dal fatto che ogni sistema di protocollazione funziona in maniera diversa, e quindi ognuno di essi necessita di una configurazione specifica. Affidando la responsabilità di gestire le configurazioni al Protocol Proxy, si evita di dover modificare, per ogni nuova integrazione, il Core Application, aggirando inoltre il problema di uno sviluppo coordinato con un terzo sistema come il Protocol Proxy.

Si può dedurre quindi che esiste un mapping 1:1 tra i Protocol proxy e i sistemi di protocollazione esterni.

C4Model
C4_layer_1 arichitettura ad alto livello del sistema di protocollazione

Standard della piattaforma

Riguardo a coding, contribuzioni, eventi, API

Struttura Eventi

Producers

Nella piattaforma si fa ampio uso del pattern Event Sourcing, perciò è fondamentale che il formato degli eventi sia stabile, versionato e documentato.

Tutti i producer DEVONO includere obbligatoriamente i seguenti campi in ogni evento:

Campo
Valore
Formato
Annotazioni

id

identificativo unico dell'entità oggetto dell'evento

UUID

Es: in un pagamento è l'ID del pagamento, in una pratica è l'application_id etc...

event_id

identificativo unico dell'evento

UUID

Non deve esserci per nessun motivo due volte lo stesso ID

event_version

versione dell'evento

Integer or Float

E' un intero (Es: 1, 2, etc..) cambia ad ogni cambiamento NON retrocompatibile.

event_created_at

data dell'evento

ISO8601

Ricordarsi di usare il formato della timezone Europe/Rome (Es: 2022-06-22T15:11:20+02:00)

app_id

Nome e versione dell'applicativo che ha generato l'evento

$application:$version

Utile per motivi di debug (Es: payment-dispatcher:1.0.15).

Nota importante: Se si genera un nuovo evento a partire da uno esistente, NON vanno ricopiati i campi event_id, event_created_at, event_version, app_id. Si tratta di una nuova entità, quindi deve avere nuovi valori.

Consumers

Chi consuma eventi deve:

  • Gestire esplicitamente le versioni supportate.

  • Scartare eventi con versioni non supportate.

  • Non generare errori per versioni non gestite:

    • È corretto loggare in DEBUG la ricezione di una versione non supportata.

    • In condizioni normali, un consumer emette un log a livello INFO solo se:

      • ha processato con successo l’evento.

      • ha eseguito un'azione concreta.

Questo approccio evita rumore nei log e rende evidenti solo le operazioni significative.

Sistema di Retry

Obiettivo

La piattaforma è dotata di un componente, detto RetryOrchestrator, responsabile della gestione automatica dei retry per eventi temporaneamente non processabili. In caso di errore temporaneo, consente di riprogrammare la consegna dell’evento al consumer in base a una politica configurabile, evitando la perdita di messaggi e riducendo l'intervento manuale.

Funzionamento

  1. Quando un servizio X fallisce nel processare un messaggio, questo viene inviato nel Failed Message Topic (FMT).

  2. Il RetryOrchestrator legge i messaggi dal FMT e, sulla base della politica di retry, li ripropone al servizio X.

  3. Se il messaggio esaurisce i tentativi previsti, viene inviato in una Dead Letter Queue (DLQ).

Politica di Retry

La politica di retry viene definita tramite due variabili d’ambiente principali:

Variabile
Descrizione

KAFKA_DISPATCHER_POLICY

Specifica la sequenza dei retry come array di coppie NxTm, dove N è il numero di tentativi e Tm è il tempo di attesa (es. 2x1m,1x2m,3x3m).

KAFKA_RETRY_QUEUE_PREFIX

Prefisso dei topic di attesa su Kafka (es. retryQueue_).

Esempio

Con la configurazione:

  • KAFKA_DISPATCHER_POLICY=2x1m,1x2m,3x3m

  • KAFKA_RETRY_QUEUE_PREFIX=retryQueue_

Il servizio si aspetta i seguenti topic di retry:

  • retryQueue_1m

  • retryQueue_2m

  • retryQueue_3m

Un messaggio attraverserà quindi una sequenza di 6 tentativi: 2 tentativi a distanza di 1 minuto, 1 tentativo dopo 2 minuti, 3 tentativi dopo 3 minuti ciascuno.

Standard Date

Formato delle date

Tutte le date all’interno della piattaforma devono essere rappresentate nel formato ISO 8601, con la seguente struttura:

YYYY-MM-DDTHH:MM:SS+HH:MM

Esempio:

2025-03-12T00:00:00+01:00

Questo formato garantisce coerenza, leggibilità e interoperabilità tra i diversi sistemi e microservizi, soprattutto in contesti distribuiti.

Fuso orario

Tutte le date devono essere espresse nel fuso orario Europe/Rome, che gestisce automaticamente l’ora solare (+01:00) e l’ora legale (+02:00).

Non utilizzare UTC o altri fusi orari, salvo esplicita eccezione documentata.

Linee guida

  • Le date devono includere l’offset del fuso orario (+01:00 o +02:00) come parte della stringa.

  • Le librerie utilizzate per serializzazione/deserializzazione devono essere configurate per usare Europe/Rome come timezone di default.

  • Il formato deve essere rispettato sia in input che in output per tutte le API e gli eventi.

Applicazioni

Contesto
Applicazione dello standard

API REST

Tutte le date nei payload (richieste e risposte)

Messaggi su Kafka

Timestamp negli eventi pubblicati

Validazioni e test

  • Ogni data deve essere validata secondo il pattern ISO8601 con timezone Europe/Rome.

  • I test automatici devono includere:

    • Verifica del formato in fase di creazione e lettura degli eventi.

    • Verifica dell’offset corretto (+01:00 o +02:00) in base alla data.

    • Gestione corretta dell’ora legale.

API Standards

Principi di riferimento

Le API della piattaforma seguono i principi di:

  • Zalando RESTful API Guidelines

  • Linee guida di interoperabilità della PA (AgID)

Comportamento dei servizi

  • I servizi DEVONO esporre solo i verbi HTTP previsti e restituire errore per metodi non supportati.

  • I metodi seguenti devono essere idempotenti, come da RFC 7231:

    • DELETE, GET, HEAD, OPTIONS, PUT, TRACE

  • I percorsi (path) delle risorse devono:

    • Essere al plurale per rappresentare collezioni

    • Usare kebab-case (es. user-preferences)

Esempi di endpoint

POST    /tenants
GET     /tenants/{tenant_id}
PUT     /tenants/{tenant_id}
PATCH   /tenants/{tenant_id}
DELETE  /tenants/{tenant_id}

Le URL nelle risposte devono essere sempre espresse in forma assoluta.

GET e Navigazione

Per le risorse collezione, le risposte devono includere una struttura di meta-informazioni e link di navigazione:

{
  "meta": {
    "page": {
      "offset": 5000,
      "limit": 20,
      "sort": "creationTime"
    },
    "total": 46561
  },
  "links": {
    "self": "https://api.example.it/items?offset=5000&limit=20&sort=creationTime",
    "prev": "https://api.example.it/items?offset=4980&limit=20&sort=creationTime",
    "next": "https://api.example.it/items?offset=5020&limit=20&sort=creationTime"
  },
  "data": [
    { "id": "..." },
    { "id": "..." },
    { "id": "..." }
  ]
}

Paginazione

I parametri di paginazione da usare nei GET, ove applicabile, sono:

Parametro
Descrizione

offset

Offset dell’elemento iniziale

limit

Numero massimo di elementi da restituire

sort

Ordinamento (campo o criterio)

cursor

Puntatore per navigazione cursor-based

embed

Specifica risorse correlate da includere

q

Query full-text o filtro libero

fields

Specifica dei campi da includere nella risposta

Per endpoint con elevata numerosità di dati, si può usare la cursor-based pagination, ad esempio:

GET /payments?created_since=2024-12-01T00:00:00+01:00

Gestione degli errori

Tutti gli errori devono seguire lo standard RFC 7807 - Problem Details for HTTP APIs:

{
  "type": "/errors/incorrect-user-pass",
  "title": "Incorrect username or password.",
  "status": 401,
  "detail": "Authentication failed due to incorrect username or password.",
  "instance": "/login/log/abc123"
}

Ogni errore può includere:

  • type: URL che identifica il tipo di errore

  • title: breve descrizione dell’errore

  • status: HTTP status code (opzionale)

  • detail: descrizione dettagliata (opzionale)

  • instance: identificatore specifico dell’occorrenza (opzionale)

Esempio documento con allegati non protocollato

{
  "title": "Risposta integrazione: Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "id": "a83ead00-2a62-404c-a416-b965a400bcd6",
  "app_id": "document-dispatcher:1.3.2",
  "event_created_at": "2023-12-21T16:58:49+00:00",
  "event_id": "367fd4d2-4c8a-4124-a853-70c67bea0b03",
  "event_version": 1,
  "registration_data": {
    "transmission_type": "Inbound",
    "date": "",
    "document_number": ""
  },
  "folder": {
    "title": "Protocollazione esterna [FAKE REGISTRY PROXY]-Michelangelo Buonarroti BNRMHL75C06G702B",
    "id": ""
  },
  "status": "DOCUMENT_CREATED",
  "type": "integration-response",
  "remote_id": "1a30529e-9c41-4352-9198-1fac44c5e911",
  "remote_collection": {
    "id": "bdefd713-feb9-4bbe-9308-1afe4dcbd858",
    "type": "service"
  },
  "topics": null,
  "short_description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "main_document": {
    "name": "risposta-a-richiesta-integrazione-cccda494-9d02-4a92-9e05-cbad01096818-202311230314.pdf",
    "description": "Risposta a richiesta integrazione: cccda494-9d02-4a92-9e05-cbad01096818 2023-11-23 03:14",
    "mime_type": "application/pdf",
    "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/241879ee-a133-4456-8473-811781daeb67?version=1",
    "md5": "",
    "filename": "655f5e343daa3.pdf"
  },
  "image_gallery": null,
  "attachments": [
    {
      "name": "dummy.pdf",
      "description": "Allegato - dummy.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/a4c81464-ee91-449a-b2a3-7010c59fdee7?version=1",
      "md5": "",
      "filename": "dummy.pdf"
    },
    {
      "name": "dummy2.pdf",
      "description": "Allegato - dummy2.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/d284be8a-2700-48a9-aa4b-fc0136674548?version=1",
      "md5": "",
      "filename": "dummy2.pdf"
    }
  ],
  "related_public_services": null,
  "normative_requirements": null,
  "related_documents": null,
  "life_events": null,
  "business_events": null,
  "allowed_readers": null,
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "owner_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "created_at": "2023-11-23T14:14:23+00:00",
  "updated_at": "",
  "author": [
    {
      "type": "human",
      "tax_identification_number": "BNRMHL75C06G702B",
      "name": "Michelangelo",
      "family_name": "Buonarroti",
      "street_name": "Via Antica Aia",
      "postal_code": "67049",
      "email": "[email protected]",
      "role": "sender"
    }
  ],
  "source_type": "user",
  "recipient_type": "tenant"
}

Esempio documento con allegati protocollato

{
  "title": "Risposta integrazione: Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "id": "a83ead00-2a62-404c-a416-b965a400bcd6",
  "event_created_at": "2023-12-21T16:58:49+00:00",
  "event_id": "367fd4d2-4c8a-4124-a853-70c67bea0b03",
  "event_version": 1,
  "registration_data": {
    "transmission_type": "Inbound",
    "date": "2023-11-23T14:14:24+00:00",
    "document_number": "123456789"
  },
  "folder": {
    "title": "Protocollazione esterna [FAKE REGISTRY PROXY]-Michelangelo Buonarroti BNRMHL75C06G702B",
    "id": "n/a"
  },
  "status": "REGISTRATION_COMPLETE",
  "type": "integration-response",
  "remote_id": "1a30529e-9c41-4352-9198-1fac44c5e911",
  "remote_collection": {
    "id": "bdefd713-feb9-4bbe-9308-1afe4dcbd858",
    "type": "service"
  },
  "topics": null,
  "short_description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "main_document": {
    "name": "risposta-a-richiesta-integrazione-cccda494-9d02-4a92-9e05-cbad01096818-202311230314.pdf",
    "description": "Risposta a richiesta integrazione: cccda494-9d02-4a92-9e05-cbad01096818 2023-11-23 03:14",
    "mime_type": "application/pdf",
    "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/241879ee-a133-4456-8473-811781daeb67?version=1",
    "md5": "",
    "filename": "655f5e343daa3.pdf"
  },
  "image_gallery": null,
  "attachments": [
    {
      "name": "dummy.pdf",
      "description": "Allegato - dummy.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/a4c81464-ee91-449a-b2a3-7010c59fdee7?version=1",
      "md5": "",
      "filename": "dummy.pdf"
    },
    {
      "name": "dummy2.pdf",
      "description": "Allegato - dummy2.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/d284be8a-2700-48a9-aa4b-fc0136674548?version=1",
      "md5": "",
      "filename": "dummy2.pdf"
    }
  ],
  "related_public_services": null,
  "normative_requirements": null,
  "related_documents": null,
  "life_events": null,
  "business_events": null,
  "allowed_readers": null,
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "owner_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "created_at": "2023-11-23T14:14:23+00:00",
  "updated_at": "",
  "author": [
    {
      "type": "human",
      "tax_identification_number": "BNRMHL75C06G702B",
      "name": "Michelangelo",
      "family_name": "Buonarroti",
      "street_name": "Via Antica Aia",
      "postal_code": "67049",
      "email": "[email protected]",
      "role": "sender"
    }
  ],
  "source_type": "user",
  "recipient_type": "tenant"
}

Esempio documento con campo retry_meta prodotto dal protocol proxy

Il campo "retry_meta" è stato creato dal protocol proxy e contiene il topic di origine ed eventuali campi utili al protocol proxy per tenere lo stato della protocollazione

{
  "title": "Risposta integrazione: Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "id": "a83ead00-2a62-404c-a416-b965a400bcd6",
  "event_created_at": "2023-12-21T16:58:49+00:00",
  "event_id": "367fd4d2-4c8a-4124-a853-70c67bea0b03",
  "event_version": 1,
  "registration_data": {
    "transmission_type": "Inbound",
    "date": "",
    "document_number": ""
  },
  "folder": {
    "title": "Protocollazione esterna [FAKE REGISTRY PROXY]-Michelangelo Buonarroti BNRMHL75C06G702B",
    "id": ""
  },
  "status": "REGISTRATION_FAILED",
  "type": "integration-response",
  "remote_id": "1a30529e-9c41-4352-9198-1fac44c5e911",
  "remote_collection": {
    "id": "bdefd713-feb9-4bbe-9308-1afe4dcbd858",
    "type": "service"
  },
  "topics": null,
  "short_description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
  "main_document": {
    "name": "risposta-a-richiesta-integrazione-cccda494-9d02-4a92-9e05-cbad01096818-202311230314.pdf",
    "description": "Risposta a richiesta integrazione: cccda494-9d02-4a92-9e05-cbad01096818 2023-11-23 03:14",
    "mime_type": "application/pdf",
    "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/241879ee-a133-4456-8473-811781daeb67?version=1",
    "md5": "",
    "filename": "655f5e343daa3.pdf"
  },
  "image_gallery": null,
  "attachments": [
    {
      "name": "dummy.pdf",
      "description": "Allegato - dummy.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/a4c81464-ee91-449a-b2a3-7010c59fdee7?version=1",
      "md5": "",
      "filename": "dummy.pdf"
    },
    {
      "name": "dummy2.pdf",
      "description": "Allegato - dummy2.pdf",
      "mime_type": "application/pdf",
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/d284be8a-2700-48a9-aa4b-fc0136674548?version=1",
      "md5": "",
      "filename": "dummy2.pdf"
    }
  ],
  "related_public_services": null,
  "normative_requirements": null,
  "related_documents": null,
  "life_events": null,
  "business_events": null,
  "allowed_readers": null,
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "owner_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "created_at": "2023-11-23T14:14:23+00:00",
  "updated_at": "",
  "author": [
    {
      "type": "human",
      "tax_identification_number": "BNRMHL75C06G702B",
      "name": "Michelangelo",
      "family_name": "Buonarroti",
      "street_name": "Via Antica Aia",
      "postal_code": "67049",
      "email": "[email protected]",
      "role": "sender"
    }
  ],
  "source_type": "user",
  "recipient_type": "tenant",
  "retry_meta": {
    "original_topic": "documents"
  }
}

Integrazione con PDND

Flusso di Integrazione e Fruizione

Processo di Adesione e Configurazione (Lato Ente/PDND e OpenCity):

  • L'ente (es. Comune) deve prima abilitarsi sulla piattaforma PDND (procedura di PagoPA tramite SPID).

  • Sulla piattaforma PDND (spesso guidati o fatti da noi per l'ente):

    • Si crea un Client.

    • Si richiede la Fruizione degli e-service desiderati (es. ANPR stato famiglia). Questa richiesta deve essere approvata dall'ente erogatore.

    • Si crea una Finalità per ogni e-service richiesto, specificando le motivazioni e l'analisi del rischio. Anche questa finalità deve essere approvata.

    • Si associa la Finalità approvata e la Chiave Pubblica al Client sulla PDND.

  • Sulla Piattaforma OpenCity:

    • Si crea un Client corrispondente a quello creato sulla PDND.

    • Si genera una Chiave Pubblica (tramite un'API del PDND Connector) e la si associa al Client sulla PDND.

    • Si inseriscono gli identificativi ottenuti dalla PDND: Client ID, KID e Purpose ID (ParID).

    • Si abilitano gli E-service (configurati con il Client e la Finalità) a livello di "tenant" (per tutti i servizi compatibili) o di "singolo servizio".

    • Si configura il Nested Form sul frontend per ricevere i dati da quello specifico e-service.

Ulteriori Dettagli e Supporto

Documentazione

  • Le slide OpenCity_Richiesta di fruizione dati via PDND_public link.pdf sono ottime per gli step lato Ente/PDND.

  • Le registrazioni video sono essenziali per vedere il flusso pratico:

    • La "Videolezione 1: Formazione Pdnd" per il contesto generale, gli attori, e la demo di configurazione/fruizione;

    • La "Videolezione 2: Revisione codice PDND" per il funzionamento interno del Connector (flusso GET/POST, struttura codice).

Ambiente di Lavoro

Serve l'accesso alla piattaforma PDND (test/collaudo) con permessi adeguati (amministratore è meglio). Richiedere i permessi per comuni di test come Asolo o Vicopisano. Configurare un ambiente locale per testare il PDND Connector, magari copiando le configurazioni esistenti da S3 (richiedere credenziali).

Processo di Lavoro

Seguire la Guida Passo Passo per l'Integrazione della PDND: identificare l'e-service (con PMs), studiarlo (dati vs form), e creare una issue dettagliata che includa lo studio, la mappatura, e la definizione della risposta del Connector.

Problemi Noti

Ci sono aree in miglioramento: lo Swagger del Connector potrebbe non funzionare nell'ambiente QA, la gestione degli ambienti collaudo/produzione sulla UI della Stanza ha delle criticità (è stata aperta una issue per questo), la strategia di caching nel Connector ha delle criticità.

Requisiti per l'integrazione

L’area personale del cittadino effettua diverse interazioni con il sistema di protocollo, ma tutte seguono una logica comune. Le interazioni avvengono in seguito ad alcune azioni da parte cittadini e degli operatori:

  1. invio di una pratica da parte del cittadino

  2. invio di una richiesta di integrazioni da parte di un operatore

  3. invio di una integrazione da parte del cittadino

  4. approvazione/rifiuto di una pratica

Altre azioni possibili, mediante configurazioni avanzate del servizio:

  1. creazione di una pratica da parte di un operatore per conto di un cittadino

  2. ritiro di una pratica da parte del cittadino

  3. annullamento di una pratica da parte di un operatore

Metadati utilizzati

Per ognuna di queste operazioni viene prodotto un documento principale dotato di eventuali allegati.

Per ogni documento ci aspettiamo di poter sempre specificare:

  • Un oggetto

  • Un mittente (il soggetto che ha formato il documento)

  • Un destinatario (solitamente l’ufficio che deve prendere in carico la richiesta)

  • Data di registrazione della pratica

  • Tipo di trasmissione (in entrata, in uscita, interno)

  • Classificazione (solitamente sotto forma di indice di classificazione nell’albero del titolario, es 6.3.4)

Per ogni documento protocollato ci aspettiamo di ricevere i seguenti dati:

  • Il Numero di protocollo (un identificativo unico e persistente)

  • Data di registrazione nel protocollo

  • (opzionale) Una impronta digitale del documento protocollato

Tutti i documenti di una pratica sono raccolti in un fascicolo per il quale ci aspettiamo di poter specificare

  • L’oggetto

  • (opzionale) La classificazione

Flusso di interazione tra Area Personale e sistema di protocollo

Per ogni integrazione sono necessarie solitamente le seguenti chiamate API o webservice:

  1. Ricerca di un documento per Numero di protocollo

  2. Invio di un Documento principale

  3. Invio di allegati al documento principale

  4. Protocollazione di un documento e dei suoi allegati: se disponibile, preferiamo avere una chiamata atomica, che protocolla il documento principale e i suoi allegati.

  5. Ricerca di un fascicolo per Numero di fascicolo

  6. Creazione di un fascicolo

  7. Aggiunta di un documento ad un fascicolo esistente

Processo di integrazione

  1. Condivisione documentazione:

  • Documentazione delle API o dei WebService

  • ambiente di test (endpoint, credenziali di accesso)

  • Esempio di chiamate necessarie per fare una protocollazione di un documento e dei suoi allegati

  1. Test delle chiamate al sistema di protocollo effettuate in modo manuale

  2. Validazione della protocollazione manuale da parte dell’Ente

  3. Implementazione e rilascio dell’integrazione in ambiente di Quality Assurance

  4. Validazione su un servizio di test

  5. Rilascio in ambiente di produzione

  6. (opzionale) Validazione su un servizio di produzione e annullamento della protocollazione effettuata

L’ultimo test effettuato in ambiente di produzione non è strettamente necessario, ma nella nostra esperienza può rivelare differenze nella configurazione del sistema di protocollo che possono diventare bloccanti.

CREATION_PENDING

pagamento in attesa di essere creato sull'IdP

CREATION_FAILED

pagamento di cui è fallita la creazione sull'IdP

PAYMENT_PENDING

pagamento creato sull'IdP e in attesa di essere eseguito dall'utente

PAYMENT_STARTED

procedura di pagamento iniziata dall'utente

COMPLETE

pagamento completato a seguito di conferma dalI'dP

PAYMENT_FAILED

pagamento fallito a causa di scadenza del termine ultimo entro cui doveva essere eseguito

nei proxy sviluppati questo stato non è quasi mai stato

utilizzato

CANCELED

pagamento annullato

EXPIRED

pagamento scaduto

Introduzione

Contesto Generale e Obiettivi

Cos'è la PDND e a Cosa Serve:

  • E’ l'infrastruttura digitale italiana che facilita l'interoperabilità tra sistemi informativi delle Pubbliche Amministrazioni.

  • Il suo scopo primario è condividere dati in modo sicuro, standard e trasparente.

  • E' uno dei due approcci adottati da Opencity Italia per implementare il principio del (o one-time entry), evitando di chiedere al cittadino dati già in possesso di altre PA (es. residenza, stato famiglia da ANPR).

Attori Principali Coinvolti:

  • PDND: La piattaforma centrale che gestisce le interazioni e la sicurezza.

  • Erogatori: Gli enti che mettono a disposizione i dati tramite e-service (es. ANPR, INPS).

  • Fruitori: Gli enti (come i Comuni, che usano OpenCity) che hanno bisogno di accedere a questi dati.

  • La Piattaforma (OpenCity): L'interfaccia lato ente e cittadino dove i dati vengono visualizzati e usati.

  • PDND Connector: Il microservizio chiave che funge da ponte tra OpenCity e l’ecosistema PDND (compresi gli enti erogatori). Ha il compito di:

    • Gestire le configurazioni e il materiale crittografico degli enti fruitori;

    • Interagire con la PDND, per conto dei fruitori, al fine di ottenere i voucher di sicurezza;

    • Utilizzare i voucher per accedere in modo sicuro agli e-service esposti via API dagli enti erogatori, garantendo che ogni cittadino acceda a tutti e soli i dati di sua pertinenza.

    • Garantire la conservazione dei voucher secondo le linee guida.

    In sintesi, il PDND Connector centralizza la logica di integrazione e sicurezza, permettendo a OpenCity di comunicare con la PDND in modo trasparente, conforme e scalabile.

Requisiti per l'integrazione

E-service

Non sono generici servizi API, ma specifici servizi erogati dagli enti che forniscono dati ben definiti (es. "C021 - Accertamento Stato Famiglia" da ANPR). Possono avere diverse versioni, per poter essere utilizzati bisogna farne richiesta e in seguito associarli ad una finalità.

Client

Un oggetto di configurazione fondamentale sulla PDND e su OpenCity. Raggruppa le informazioni necessarie per gestire e amministrare gli e-service che un ente (fruitore) vuole usare. L'ente, configurando il client di Opencity Italia, può accedere a tutti gli e-service che si vogliono integrare nei servizi digitale. Per poter utilizzare gli e-service, il client deve essere associato alle relative finalità.

Finalità

Il motivo (anche legale) per cui un ente richiede l'accesso a un determinato e-service. È strettamente legata a uno o più e-service e richiede una specifica "Analisi del rischio". Per ampliare l'adozione della PDND, la piattaforma consente di creare tanti servizi digitali utilizzando una singola finalità.

Voucher

Un token di sicurezza temporaneo che il PDND Connector ottiene dalla PDND. Questo voucher è essenziale perché funge da credenziale per autenticarsi e autorizzarsi direttamente presso l'ente erogatore quando si chiede il dato tramite l'e-service. Il voucher è relativo ad uno specifico client, una specifica finalità e una specifica coppia chiavi di crittografia.

Nested Form

Il componente sulla nostra piattaforma che riceve e visualizza i dati ottenuti tramite la PDND. Alcune proprietà nel suo codice definiscono quale e-service utilizzare e quali campi specifici riceveranno i dati PDND certificati.

ID Chiave (KID), ID Client (Client ID), ID Finalità (Purpose ID / ParID)

Sono gli identificativi cruciali ottenuti dalla piattaforma PDND dopo aver configurato Client, Chiave Pubblica e Finalità. Vanno copiati dalla PDND e inseriti nella configurazione del Client corrispondente sulla nostra piattaforma.

SSO mediante oAuth2

Opencity Italia integra l'autenticazione Spid/CIE mediante il componente dedicato OpenLogin, dialogando con esso con protocollo oAuth2. Lo stesso componente può essere usato da altri applicativi.

Una autenticazione con SSO tra Opencity Italia e un applicativo in uso allo stesso Ente può essere agevolmente realizzata se può essere garantito il supporto per il protocollo oAuth2.

Vediamo uno schema generale degli attori in gioco:

Vediamo un flusso di autenticazione di un cittadino che fa login prima sull'area personale e successivamente sull'applicativo di terze parti:

L'applicativo di terze parti dovrà essere dotato di un opportuno client oAuth2. Ipotiziamo che le url coinvolte siano le seguenti e mostriamo una tipica configurazione del client.

URL dell'area personale: https://servizi.comune.bugliano.pi.it

URL della pagina di login (OpenLogin): http://login.comune.bugliano.pi.it

URL dell'applicativo di terze parti: http://myapp.company.com

Per attivare un nuovo client è necessario fornire:

  • La URL da autorizzare come redirct uri del client

  • La URL di logout

L'attivazione comprende i seguenti dati

redirect_url: http://servizi.comune.bugliano.pi.it/lang/auth/login-oauth
url_authorize: http://login.comune.bugliano.pi.it/authorize
url_access_token: http://login.comune.bugliano.pi.it/token
url_resource_owner_details: http://login.comune.bugliano.pi.it/api/profile
url_logout: http://login.comune.bugliano.pi.it/logout?redirect=http://servizi.comune.bugliano.pi.it/lang
client_id: {client_id} fornito da Opencity Labs
client_secret: {client_secret} fornito da Opencity Labs

Temi grafici

L'aggiunta di un tema grafico richiede una modifica al Core della piattaforma, in queste pagine si da indicazione dei temi presenti e di come predisporre una Merge-Request per aggiungerne uno nuovo.

Come creare un MR per aggiungere un tema

Step preparativi alla modifica:

  • Clonare il repository https://gitlab.com/opencity-labs/area-personale/core

  • Posizionarsi sul branch master

  • Creare un nuovo branch di nome add-theme-$themename e spostarsi sul nuovo branch con il comando git checkout -b "add-theme-$themename" sostituendo con il nome del nuovo tema che si vuole creare (per le indicazioni sui nomi dei temi vedere sotto)

  • Creare una MR sul branch che effettui squash di tutti i commit del branch ed elimini il branch una volta mergiato come di seguito:

Successivamente creare un nuovo file in ./assets/styles copiando il contenuto di seguito che dovrebbe coincidere con il file default.scss. Il nome del file deve coincidere con il nome che avrà il tema e avere l'estensione .scss (l nome del tema può contenere solo lettere e il carattere `-`, deve rispondere cioè alla espressione regolare [a-z][a-z0-9-]+. Es: se si vuole creare il tema "amaranto" aggiungiamo un file amaranto.scss):

@charset "UTF-8";

$primary: #3478bd;

$primary-a5: $primary;
$primary-a6: $primary;
$primary-a7: $primary;

// Header
$header-slim-bg-color: #2b649e;     // Colore di background del top header
$header-slim-button-color: #2b649e; // Colore di background del bottone di accesso all'area personale
$header-slim-button-hover-color: $primary; // Colore di background del bottone di accesso all'area personale su hover

$header-center-bg-color: #3478bd;       // Colore di background del header centrale
//$header-center-text-color: #fff; // Colore del testo del header centrale

// Footer
$bg-footer: #16334f; // Colore di background del footer

// Content
$card-link-color: $primary; // Colore link all'interno delle card

@import "core";
  • Nel file cambiare le opzioni ed i colori a proprio piacimento

  • Si compila il nuovo tema su webpack aggiungendo nel file webpack.config.js StyleEntry come di seguito: .addStyleEntry('amaranto', './assets/styles/amaranto.scss') (Il nome deve coincidere con il nome del file creato in precedenza). I colori devono tenere conto della linea guida Accessibilità.

  • Si aggiunge alla piattaforma il nuovo tema aggiornando nel file src/Model/Tenant/ThemeOptions.php la costante THEMES e inserendo il nuovo tema come di seguito: 'Amaranto' => 'amaranto'

Esempio con GovWay

Esempio dettagliato di una integrazione di un modulo di Opencity Italia che attinge a una API protetta mediante l'API gateway opensource GovWay di Link.it

API privata, che si intende esporre mediante GovWay:

https://192.168.1.111:8000/conti-correnti

L'API risponde un dato privato se viene correttamente inserito un parametro in get tax_code che identifica il codice fiscale del cittadino che esegue la richiesta:

https://192.168.1.111:8000/items/conti-correnti?tax_code=AAABBBXX...

L'API non ha un controllo interno che permette di rispondere ad ogni cittadino solo con i suoi dati, l'API è aperta e risponde a qualunque chiamata.

In questo esempio si desidera esporre l'API privata su un indirizzo pubblico e filtrare le chiamate in modo che ogni cittadino possa consultare l'API solo per i propri dati.

L'API esposta è la seguente:

https://govway-qa-out.boat.opencontent.io/govway/Ente/directus/v1

Quando viene chiamata quest'ultima url si desidera imporre un controllo sulla la presenza del token JWT e sul parametro della chiamta. La chiamata deve raggiungere l'API privata SOLO se:

  1. il token è presente

  2. il token è validato mediante la chiave pubblica esposta in JWKS

  3. viene passato il parametro tax_code in GET

  4. il parametro tax_code corrisponde al valore username del token

Quest'ultimo punto è il più importante perché ci da la certezza che il cittadino che sta facendo la richiesta è lo stesso che si è autenticato.

Configurazione

Selezionare il profilo API Gateway
Aggiungere la definizione dell'API: per farlo sarà necessario caricare il file Open API 3 della definizione dell'API stessa.
Configurazione del connettore
Creare una API in Erogazione che esponga l'API che si desidera proteggere: si usa la definizione dell'API configurata allo step precedente e si impostano la URL di invocazione e la url del servizio interno che espone l'API (connettore)

E' necessario creare una token policy, che poi verrà assegnata all'erogazione delle API

Creazione token policy: fare attenzione a tutti i campi evidenziati. Lo username verrà usato per fare il controllo di accesso.

Il file jwks è stato scaricato da https://servizi.comune-qa.bugliano.pi.it/.well-known/jwks.json e inserito in un path raggiungibile da GovWay: nel nostro caso essendo GovWay installato con Docker abbiamo fatto uso della direttiva volumes per condividere il file con il container di GovWay.

Fare attenzione inoltre che il kid della chiave pubblica è specifico per ogni tenant e deve essere quello corretto.

Predisposta la policy deve essere applicata all'Erogazione API

Configurazione dell'Erogazione creata in precedenza
Si accede alle configurazioni necessarie dalla sezione "Controllo Accessi" dell'Erogazione API
Il controllo prevede la Policy predisposta agli step precedenti, la validazione del token e la presenza del claim username. Infine viene inserito il controllo sul contenuto come indicato nell'immagine.

Il controllo del contenuto viene fatto imponendo che l'informazione riportata nel token (${tokenInfo:username}) e quella inserita come parametro GET della chiamata (${query:filter[tax_code]}) abbiano lo stesso valore. Resta inteso che il valore nel token è vincolato dalla piattaforma, mentre il parametro tax_code è scelto da chi ha progettato l'API che viene protetta.

A questo punto la configurazione è completa.

Importazione dovuti

In questa pagina viene fornito un esempio di chiamata API per l'importazione dovuti

Come descritto nella sezione precedente tramite le API dei jobs è possibile creare un processo di importazione dovuti.

Per poter procedere correttamente all'importazione bisogna prima creare un servizio a cui i dovuti devono essere collegati.

Questa operazione può essere fatta tramite interfaccia nella sezione admin, il servizio creato dovrà essere di tipo "Servizio di pagamento dovuti"

Per il servizio appena creato dovrà essere configurato anche il gateway di pagamento

Creazione di un Job per l'importazione dovuti

Per poter creare un Job di importazione dovuti bisogna eseguire una chiamata POST all' endpoint api/jobs

Il payload per la creazione di un Job è così composto

{
  "attachment": {
    "name": "dovuti.csv",
    "mime_type": "text/csv",
    "file": "bm90aWNlX251bWJlsYW1vdW50LHJlYXNvbixjcmVhdGVkX2F0LGV4cGlyZV9hdCxjcmV..."
  },
  "args": {
    "service_id": "a5a9bf9f-7b5c-4aed-b020-5bf2d1ca75ab"
  },
  "type": "import_dovuti"
}

Dove:

attachments -> è il file csv contenente i dovuti da importare

Il file csv deve acere un template specifico di questo tipo:

390B
template-csv-import-dovuti.csv

service_id -> è un parametro specifico del tipo di job import_dovuti ed indica su quale servizio devono essere agganciati i dovuti importati

SSO mediante JWT

La piattaforma Opencity Italia supporta questa possibilità mediante una tecnica che permette di convertire una sessione basata su cookie in un token JWT.

Questo consente di usare l'autenticazione senza necessariamente usare lo stesso cookie:

  1. integrarsi con altri applicativi di terze parti che possono usare un client JWT;

  2. fare chiamate cross-origin (CORS) autenticate dove non è semplice o possibile sfruttare lo stesso cookie di autenticazione;

Attenzione: il requisito perché questa tecnica funzioni è che i servizi si trovino tutti sotto lo stesso dominio di secondo livello. Ad esempio: - servizi.comune.bugliano.pi.it - www.comune.bugliano.pi.it - altro.bugliano.pi.it

Il Cookie di Sessione

Come molti applicativi web anche Opencity Italia usa i cookie per implementare la sessione utente, il cookie si ottiene semplicemente l'indirizzo di accesso degli utenti: ad esempio per il nostro Ente di demo è

In base alla , si viene rediretti su un provider di autenticazione (tipicamente un sistema che permette il login con Spid/CIE), si eseguono tutti gli step necessari e infine si torna sull'area personale con una sessione valida.

A questo punto nel proprio brower è possibile rintracciare facilmente il cookie della piattaforma.

Il token JWT

Il cookie di sessione viene inviato in modo trasparente all'utente dal browser al sito che lo ha generato, e questo garantisce che l'utente abbia sempre una sessione valida quando naviga nella sua area personale.

Per fare chiamate alle API però è necessario convertire il Cookie in Token JWT, per farlo esiste una apposita API, nel nostro esempio:

Per simulare la chiamata con Postman ad esempio:

Il token JWT può essere usato adesso per fare chiamate alle API che richiedono una autenticazione, ad esempio posso usare l'interfaccia Swagger delle API come segue.

Faccio click in alto a destra sul comando Authorize

A questo punto posso chiamare le API che richiedono autenticazione come la /applications

Anatomia di un Token

All'interno del token sono presenti le seguenti informazioni:

Header

Payload

Esempio documento con campo retry_meta modificato dal sistema di retry

Il campo "retry_meta" contiente le informazioni inserite dal sistema di retry. Queste dovrebbero essere ignorate da protocol proxy

{
	"title": "Risposta integrazione: Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
	"id": "a83ead00-2a62-404c-a416-b965a400bcd6",
	"event_created_at": "2023-12-21T16:58:49+00:00",
	"event_id": "367fd4d2-4c8a-4124-a853-70c67bea0b03",
	"event_version": 1,
		"registration_data": {
		"transmission_type": "Inbound",
		"date": "",
		"document_number": ""
	},
	"folder": {
		"title": "Protocollazione esterna [FAKE REGISTRY PROXY]-Michelangelo Buonarroti BNRMHL75C06G702B",
		"id": ""
	},
	"status": "REGISTRATION_FAILED",
	"type": "integration-response",
	"remote_id": "1a30529e-9c41-4352-9198-1fac44c5e911",
	"remote_collection": {
		"id": "bdefd713-feb9-4bbe-9308-1afe4dcbd858",
		"type": "service"
	},
	"topics": null,
	"short_description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
	"description": "Protocollazione esterna [FAKE REGISTRY PROXY] Michelangelo Buonarroti BNRMHL75C06G702B - 1a30529e-9c41-4352-9198-1fac44c5e911",
	"main_document": {
		"name": "risposta-a-richiesta-integrazione-cccda494-9d02-4a92-9e05-cbad01096818-202311230314.pdf",
		"description": "Risposta a richiesta integrazione: cccda494-9d02-4a92-9e05-cbad01096818 2023-11-23 03:14",
		"mime_type": "application/pdf",
		"url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/241879ee-a133-4456-8473-811781daeb67?version=1",
		"md5": "",
		"filename": "655f5e343daa3.pdf"
	},
	"image_gallery": null,
	"attachments": [
		{
			"name": "dummy.pdf",
			"description": "Allegato - dummy.pdf",
			"mime_type": "application/pdf",
			"url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/a4c81464-ee91-449a-b2a3-7010c59fdee7?version=1",
			"md5": "",
			"filename": "dummy.pdf"
		},
		{
			"name": "dummy2.pdf",
			"description": "Allegato - dummy2.pdf",
			"mime_type": "application/pdf",
			"url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/d284be8a-2700-48a9-aa4b-fc0136674548?version=1",
			"md5": "",
			"filename": "dummy2.pdf"
		}
	],
	"related_public_services": null,
	"normative_requirements": null,
	"related_documents": null,
	"life_events": null,
	"business_events": null,
	"allowed_readers": null,
	"tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
	"owner_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
	"created_at": "2023-11-23T14:14:23+00:00",
	"updated_at": "",
	"author": [
		{
			"type": "human",
			"tax_identification_number": "BNRMHL75C06G702B",
			"name": "Michelangelo",
			"family_name": "Buonarroti",
			"street_name": "Via Antica Aia",
			"postal_code": "67049",
			"email": "[email protected]",
			"role": "sender"
		}
	],
	"source_type": "user",
	"recipient_type": "tenant",
	"retry_meta": {
		"next_attempt_at": "2023-11-23 14:04:25 +0000 UTC",
		"original_topic": "documents",
		"retry_counter": 1
	}
}
{
  "typ": "JWT",
  "alg": "RS256"
}
{
  "iat": 1711639327,
  "exp": 1712503327,
  "roles": [
    "ROLE_CPS_USER",
    "ROLE_USER"
  ],
  "username": "AAAAAANNANNAMNNNA",
  "id": "ba23337b-7b9e-4e5a-9566-e3f0583822f7",
  "tenant_id": "5962qqfb-15b7-4407-b203-47ee3456f6c3"
}
Esempio di cookie di sessione
esemio di chiamata per recupeare il token JWT usando il cookie e l'API /session-auth
https://servizi.comune.bugliano.pi.it/lang/api/doc
Inserisco il valore del token ottenuto con la chiamata /session-auth

Processi asincroni (job)

In questa pagina viene fatta una breve descrizione delle API per la gestione dei Jobs (processi asincroni)

La piattaforma consente la possibilità da parte di sistemi esterni di creare/modificare/visualizzare ed eliminare oggetti Job

Ad esempio nel nostro ambiente di demo è possibile consultare le API dedicate a questo scopo:

https://servizi.comune-qa.bugliano.pi.it/lang/api/doc#jobs

Questi speciali oggetti sono dei processi onerosi che vengono creati e poi eseguiti in modo asincrono dalla piattaforma.

Esempio di interazione tra un sistema esterno e le API dei jobs:

In fase di creazione del Job viene specificato tramite il parametro type il tipo di processo (type) che verrà eseguito dal sistema.

In base al tipo di processo creato i parametri attachment e args possono cambiare. Attualmente l'unico processo disponibile è import_dovuti

Creazione di un Job

Per poter creare un Job bisogna eseguire una chiamata POST all' endpoint api/jobs

Il payload per la creazione di un Job è così composto

{
  "attachment": {
    "name": "string",
    "mime_type": "text/csv",
    "file": "ZXNlbXBpbw== OR www.example.com"
  },
  "type": "string",
  "args": {
    "additionalProp1": "string",
    "additionalProp2": "string",
    "additionalProp3": "string"
  }
}

Dove:

attachment -> è il file in base64 che verrà processato

type -> è il tipo di processo che deve essere eseguito

args -> sono dei parametri necessari al tipo di processo

L' API rispondere con un codice HTTP 201 e con l'id del Job creato

Recupero delle informazioni di un Job

Per poter recuperare informazioni su Job bisogna eseguire una chiamata GET all' endpoint api/jobs/{id}

Il payload di risposta delle API è così composto:

{
    "attachment": {
      "name": "string",
      "mime_type": "text/csv"
    },
    "id": "string",
    "type": "string",
    "status": "string",
    "args": {
      "additionalProp1": "string",
      "additionalProp2": "string",
      "additionalProp3": "string"
    },
    "output": "string",
    "running_output": "string",
    "started_at": "2024-05-31T09:19:18.779Z",
    "terminated_at": "2024-05-31T09:19:18.779Z",
    "created_at": "2024-05-31T09:19:18.779Z",
    "updated_at": "2024-05-31T09:19:18.779Z"
}

Dove:

status -> è lo stato attuale del job e può assumere i seguenti valori pending, cancelled, running, finished, failed

output -> l'output finale del processo, può contenere un errore in caso di processo failed

running_output -> l'output corrente (Es. la riga del file che si sta processando)

Uno degli utilizzi più significativi che si può fare con i Job è l'importazione di contenuti come ad esempio l'importazione dei dovuti

Integrazione di un servizio di terze parti protetto da autenticazione

In questa sezione viene descritta la modalità di integrazione con un servizio di terze parti protetto da autenticazione tramite il token JWT dell'utente.

Dataset e micro servizi esterni alla piattaforma possono essere facilmente integrati tramite il flusso di autenticazione basato su JWT (JSON Web Token) e JWKS (JSON Web Key Set)

Questo flusso di autenticazione prevede alcuni passaggi chiave e interazioni tra:

  • client (browser dell'utente)

  • area personale

  • micro servizio esterno

Di seguito il processo punto per punto:

1. Il client richiede il token

Il client invia una richiesta all'area personale per ottenere un JWT, spesso includendo le credenziali dell'utente o altre forme di autorizzazione.

Come descritto nella sezione è possibile per un utente autenticato richiedere un token di autenticazione per la propria sessione.

Questa operazione può essere effettuata facendo una chiamata alle API:

oppure come utente non autenticato effettuando una chiamata POST alle API

2. L'area personale rilascia il token

L'area personale autentica il client, genera un JWT e lo firma con una chiave privata.

Il token viene quindi restituito al client.

3. Il client richiede la risorsa al micro servizio esterno:

Il client invia una richiesta al micro servizio esterno per accedere a una risorsa protetta, includendo il JWT nell'intestazione Authorization.

4. Il micro servizio esterno convalida il token

Per verificare la validità del token il micro servizio esterno deve:

  • Estrarre il JWT dalla richiesta

  • Recuperare il JWKS dall'area personale, il JWKS contiene le chiavi pubbliche necessarie per verificare la firma del JWT.

  • Verificare la firma del JWT e convalidare le sue richieste

L'area personale espone il JWKS all'indirizzo:

5. Accesso concesso/negato:

In base alla validità del JWT il micro servizio esterno concede o nega l'accesso alla risorsa richiesta.

Oltre alla validità del token si possono verificare le affermazioni in esso contenute.

Il payload del token prodotto dall'area personale presenta delle informazioni utili a capire se quello specifico utente oltre ad avere un token valido ha i permessi per ottenere la risorsa richiesta.

Di seguito un esempio di payload del token

curl --request POST \
  --url https://servizi.comune.bugliano.pi.it/lang/api/auth \
  --header 'Content-Type: application/json' \
  --data '{
	"username": "username",
	"password": "password"
}'
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InNkYy1xYSJ9.eyJpYXQiO.c2VybmFtZSI6ImFkbWluIiwiaWQiOiI2ZmZkZWRkZi04NmZhLTRjYmEtODUzNC05YTBlNjZmZTNlYmYiLCJ0ZW[...]5hbnRfaWQiOiI2MGUzNWYwMi0xNTA5LTQwOGMtYjEwMS0zYjFhMjgxMDkzMjkifQ.lFe0MR-LAj[...]RjoVvqwk3OX23T642ETu8PUy8sFaVRf1oM1qAnPhLEpnOcrIQY65mpKw6mrJ1rzZM5OVvEeOc4xxmtgcBOfMEBJo_Dw1pMfZHOv2S1S50Zr9XNxk0LcfWjXGdC7wy81eF7UuF-3cX9W"
}
curl --request GET \
  --url '{Url API micro servizio esterno}' \
  --header 'Authorization: Bearer {JWT Token}'
{
  "iat": 1718278670,
  "exp": 1719142670,
  "roles": [
    "ROLE_CPS_USER",
    "ROLE_USER"
  ],
  "username": "BNRMHL75C06G702B",
  "id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329"
}
Single Sign-On
https://api.stanzadelcittadino.it/.well-known/jwks.json
Diagramma del flusso di autenticazione con JWT e JWKS

Integrazione con Intermediari di pagamento PagoPA

Cosa permette di fare la piattaforma con l’integrazione in questione

La piattaforma consente la creazione di servizi digitali che prevedono l'associazione di uno o più pagamenti, gestiti in modo integrato con il sistema pagoPA o tramite registrazione manuale di marche da bollo.

I pagamenti possono avvenire in due modalità:

Pagamento tramite marche da bollo

In questo caso non è prevista alcuna integrazione con sistemi esterni. Il cittadino registra i dati delle marche da bollo acquistate autonomamente (numero identificativo e data), a corredo della pratica che sta presentando.

Pagamento tramite circuito pagoPA

La piattaforma gestisce in modo integrato i pagamenti elettronici tramite l’infrastruttura pagoPA, attraverso l’interazione con uno degli intermediari accreditati.

La creazione del pagamento avviene automaticamente durante l’invio della pratica o a seguito di un'azione dell’operatore, che può:

  • generare posizioni debitorie direttamente tramite configurazione della pratica;

  • importare manualmente pagamenti pendenti da sistemi esterni tramite file CSV, rendendoli disponibili al cittadino nell’Area Personale.

L’integrazione consente quindi:

  • la creazione, la gestione e l’aggiornamento dei pagamenti;

  • la ricezione automatica delle notifiche di esito da parte del nodo pagoPA;

  • l’allineamento in tempo reale dello stato della pratica all'interno dell’Area Personale.

Nota: È attualmente in fase di sperimentazione l’introduzione della Marca da Bollo Digitale, che permetterà il pagamento del bollo direttamente tramite pagoPA, superando la modalità manuale tradizionale.

Benefici dell’integrazione

L’integrazione dei pagamenti nella piattaforma porta numerosi vantaggi operativi e strategici per gli enti:

  • Automazione dei processi di incasso: La generazione automatica delle posizioni debitorie e la ricezione dell’esito del pagamento riducono il carico di lavoro manuale per gli operatori.

  • Migliore esperienza per il cittadino: Il cittadino può completare i pagamenti online in autonomia, in un contesto centralizzato (Area Personale), senza dover interagire con sistemi esterni o recarsi fisicamente presso l’ente.

  • Tracciabilità e trasparenza amministrativa: Tutti i pagamenti vengono registrati con ID univoci, ricevute elettroniche e cronologia completa, garantendo sicurezza, conformità normativa e facilità di riconciliazione.

Esempi di utilizzo dell’integrazione per i pagamenti

Di seguito alcuni casi d’uso reali in cui l’integrazione dei pagamenti viene utilizzata all’interno della piattaforma:

Pagamenti pagoPA generati automaticamente dalla piattaforma

In questi scenari, la piattaforma crea autonomamente una posizione debitoria pagoPA in seguito a un’azione del cittadino o alla configurazione di un servizio digitale:

Domanda di occupazione suolo pubblico

Al termine della compilazione dell’istanza, un pagamento di €20 per i diritti di segreteria. Il cittadino può pagare direttamente dall’Area Personale.

Pagamenti pagoPA importati da file CSV

In alcuni casi, l’ente può caricare manualmente un file contenente posizioni debitorie provenienti da altri sistemi (es. tributi, multe, TARI):

Pagamenti Canone CUP

Il comune importa le posizioni già determinate nel gestionale tributi. Il cittadino visualizza e paga queste richieste direttamente dalla propria Area Personale.

Pagamento tramite marche da bollo cartacee

Alcuni servizi non richiedono l’integrazione con pagoPA, ma prevedono il pagamento tramite marche da bollo acquistate dal cittadino:

Istanza di residenza

Il modulo richiede l’inserimento del numero identificativo e della data di una marca da bollo da €16 acquistata autonomamente. Il cittadino la registra all’interno della pratica.

Processo di integrazione

L’integrazione con il sistema di pagamento si sviluppa in diverse fasi operative. Di seguito una descrizione dettagliata del processo.

Fasi Principali

Raccolta e validazione della documentazione e dell’ambiente di test

In questa fase iniziale viene contattato il fornitore dell’intermediario di pagamento per ottenere:

  • La documentazione tecnica delle API, contenente le specifiche delle operazioni fondamentali:

    • creazione del pagamento;

    • generazione dell’avviso cartaceo;

    • ottenimento del link per il pagamento online (checkout);

    • recupero dello stato del pagamento;

    • download della ricevuta telematica (RT).

  • Le informazioni sull’ambiente di test, in particolare:

    • l’URL del web service (WSDL per chiamate SOAP o documentazione Postman/Swagger per REST);

    • le credenziali di accesso e i dati fittizi da usare per i test (codici tipo dovuto, codice esercente, identificativi utente, ecc.).

Sviluppo dell’integrazione

Questa fase prevede lo sviluppo vero e proprio dell’integrazione, seguendo:

  • la documentazione fornita dall’intermediario;

  • la documentazione tecnica della piattaforma Opencity;

  • gli standard architetturali e funzionali adottati internamente.

L’integrazione viene generalmente realizzata tramite un proxy che standardizza la comunicazione tra la piattaforma e l’intermediario.

Test dell’integrazione in ambiente di collaudo

Completato lo sviluppo, si procede alla validazione funzionale nell’ambiente di collaudo.

I test previsti includono:

  • Test automatici delle API di configurazione del pagamento;

  • Test manuale della configurazione del pagamento tramite form (interfaccia admin);

  • Test di creazione del pagamento a partire da una pratica compilata da un cittadino;

  • Test del completamento del pagamento attraverso il checkout pagoPA;

  • Test di scaricamento della ricevuta telematica a pagamento avvenuto.

  • Test di importazione mediante file csv di un pagamento pendente creato esternamente alla piattaforma

Test dell’integrazione in ambiente di produzione

Una volta superata la fase di collaudo, si procede con il test in ambiente di produzione, in collaborazione con un cliente pilota.

Questa fase è fondamentale perché:

  • emergono spesso comportamenti specifici o difformità rispetto all’ambiente di test;

  • si rilevano dettagli contestuali noti solo al cliente, che può accedere al backoffice dell’intermediario e fornire informazioni utili;

  • si verificano disallineamenti tra test e produzione dovuti a configurazioni, dati o logiche specifiche dell’ente.

Prima di procedere, è necessario concordare la tempistica e la modalità di test con il cliente coinvolto.

Criticità comuni nelle integrazioni di pagamento

Durante lo sviluppo e la messa in produzione dell’integrazione con i sistemi di pagamento, possono emergere alcune criticità ricorrenti. Di seguito le principali:

Connettività in ambienti segregati (firewall, VPN, ecc.)

In alcuni casi, le API degli intermediari sono accessibili solo tramite VPN o da indirizzi IP autorizzati, sia in ambiente di test che in ambiente di produzione.

Per usufruire delle API, è necessario comunicare al fornitore gli indirizzi IP fissi da autorizzare. Questo vincolo può essere particolarmente rilevante quando lo sviluppo dell’integrazione è affidato a un’azienda esterna.

In questi casi si possono adottare due strategie:

  • Configurare l’accesso alla VPN di Opencity Labs per consentire lo sviluppo in modo controllato e sicuro.

  • Richiedere lo sblocco dell’IP dell’azienda esterna (se disponibile) da parte del fornitore dell’integrazione, insieme a quelli di Opencity Labs.

Documentazione tecnica errata o incompleta

La documentazione fornita dai fornitori del sistema di pagamento può presentare, in alcuni casi, ambiguità, imprecisioni o mancanze. Questo può ostacolare:

  • la corretta progettazione dell’integrazione;

  • lo sviluppo in ambiente di test;

  • la validazione finale in produzione.

In presenza di dubbi o problemi tecnici, è fondamentale contattare tempestivamente il supporto tecnico del fornitore e fornire esempi concreti del comportamento anomalo riscontrato.

Ambiente di test instabile o non funzionante

Gli ambienti di test – sia del fornitore intermediario, sia di pagoPA – possono presentare frequenti disservizi. Le criticità più comuni si verificano nei seguenti punti del flusso:

1. Ambiente di test dell’intermediario di pagamento I problemi più frequenti includono:

  • errori nella creazione del pagamento;

  • mancato download dell’avviso o della ricevuta telematica;

  • errori di aggiornamento dello stato del pagamento;

  • malfunzionamenti nel redirect verso il portale di pagamento.

In questi casi è necessario contattare il supporto tecnico dell’intermediario, fornendo tutti i dettagli tecnici (endpoint, parametri inviati, timestamp, codice esercente, ecc.).

2. Ambiente di test di pagoPA I problemi si manifestano generalmente dopo il redirect al portale pagoPA, in particolare:

  • nella schermata di inserimento dei dati della carta;

  • nel flusso di conferma o ricezione dell’esito.

Per queste problematiche è necessario contattare il supporto tecnico di pagoPA via email all’indirizzo: [email protected]

https://dominio/PATH/api/docsdominio
Indirizzo della documentazione delle API
Documentazione API

Implementazione di un proxy

Molti aspetti implementativi dipendono più dalla normativa che da nostre scelte progettuali, in questa pagina si evidenziano gli uni e le altre.

Requisiti funzionali

Sono requisiti che dipendono da nostre scelte implementative, possono cambiare man mano che evolviamo la piattaforma, per supportare nuovi casi d'uso (pagamento dei dovuti) oppure perché ci accorgiamo che abbiamo commesso qualche errore nella progettazione.

  1. Gestione di pagamenti spontanei

  2. Gestione di pagamenti con bilancio

  3. Gestione della notifica dello stato del pagamento da parte dell'IdP - se disponibile - o del polling se non è disponibile la notifica

  4. Se è implementato il polling, questo deve avvenire con un esponenziale che assicuri alcuni check nel giro di pochi minuti e poi si diradi fino a un massimo di un check al giorno fino alla scadenza del pagamento o 1 anno se la scadenza è inferiore.

  5. Gestione delle configurazioni a livello di Tenant e di Servizio

Requisiti normativi

  • Avere una specifica in formato OpenAPI v3

  • Rispettare la : in particolare per quanto riguarda

    • (4.2)

    • (4.3) optando per la scelta di snake_case per i nomi degli attributi

    • logging (4.4)

    • risultare valido nel

Requisiti per l'ambiente di produzione

  • Sappiamo che in molti topic ci sono diversi eventi mal formati, per vari errori e assenza di uno schema registry, quindi è necessario validare gli eventi in input e opzionalmente in output (lo fanno le nostre prime implementazioni in python, ma è un requisito?)

  • Ci aspettiamo che se di dovesse aggiornare il formato dell'evento nel topic payments gestiremo eventi anche in parallelo con versioni distinte, quindi ogni proxy deve Interpretare solo gli eventi che riportano il formato più recente dell'evento nel topic payments: event_version: 2.0

  • Integrarsi con per la gestione degli errori

  • Esporre metriche di monitoraggio in , in particolare:

    • counter sul numero di pagamenti creati

    • counter sul numero di errori durante il processamento di pagamenti, differenziando gli errori interni (errori di formato dati in ingresso o validazione) da errori esterni (errori di dialogo con l'IdP)

    • latenza delle chiamate fatte all'IdP

    Di seguito la tabella contenente le specifiche di ogni metrica

Metrica
Labels
Descrizione

Inoltre il servizio deve rispettare gli in particolare per quanto riguarda la gestione dello storage: non possiamo avere vendor-lockin sulle tecnologie, quindi dobbiamo essere compatibili con file-system posix tradizionale, NFS share e almeno i due cloud-storage più diffusi S3 e Azure Blob.

Documentazione API di Esempio - Swagger

Esempi di proxy da cui si può prendere spunto

EFIL:

IRIS:

MYPAY:

PMPAY:

Questi proxy condividono alcune scelte implementative grazie a un apposito python-sdk sviluppato in parallelo ai proxy stessi:

  • le configurazioni sono salvate su disco locale o s3 secondo un albero ben definito tenant->servizio

  • i singoli pagamenti vengono salvati su storage fino a che il pagamento è pendente, così da poter fare il polling in autonomia.

Altri riferimenti utili

oc_payment_validation_errors_total

cluster, env, app_name

la metrica deve misurare gli errori di validazione sull'evento letto (es. l'importo è una stringa invece che un float)

oc_api_requests_total

cluster, env, method, app_name, status_code

la metrica deve monitorare le chiamate http indicandone lo status code

oc_payment_success_events_total

cluster, env, app_name

la metrica deve misurare solo gli eventi per cui il proxy ha una configurazione e che stati processati con successo

oc_payment_failed_events_total

cluster, env, app_name

la metrica deve misurare gli eventi di pagamento validi di cui però è fallito il processing per qualsiasi motivo (escluso il caso in cui non esiste una configurazione per esso)

oc_payment_provider_errors_total

cluster, env, app_name

la metrica deve misurare gli eventi di pagamento validi di cui però è fallito il processing a causa di un errore sul provider

oc_payment_internal_errors_total

cluster, env, app_name

la metrica deve misurare gli eventi di pagamento validi di cui però è fallito il processing per errori interni al codice

oc_payment_provider_latency_bucket

cluster, env, app_name

istogramma che mostra la distribuzione di latenza delle risposte del provider

back-off
Linee Guida di interoperabilità
il formato dei dati
raccomandazioni sul naming
validatore ufficiale OA3
sentry
formato prometheus
standard della piattaforma
http://gitlab.com/opencontent/stanza-del-cittadino/efil-payment-proxy
http://gitlab.com/opencontent/stanza-del-cittadino/iris-payment-proxy
http://gitlab.com/opencontent/stanza-del-cittadino/mypay-payment-proxy
http://gitlab.com/opencontent/stanza-del-cittadino/pmpay-payment-proxy
Presentazione API Interoperability a EuroPython 2018
Presentazione API Interoperability a EuroPython 2019
API Starter Kit
Api Definition

Configurazione tenant e servizi

Panoramica

La configurazione di tenant e servizi avviene interrogando delle API apposite fornite dal Protocol Proxy di riferimento. Quest'ultimo fornisce inoltre un form come json schema sviluppato con form.io mediante il quale l'utente inserisce tali configurazioni.

Per creare un form utilizzare il seguente builder: https://formio.github.io/formio.js/app/builder

Configurazione Servizio

L'admin, dall'interfaccia di configurazione dei protocolli della piattaforma compila la configurazione mediante una form, il cui json schema è servito dall'API /v1/schema del Protocol Proxy

Esempio interfaccia configurazione protocollo

Lo schema della form sopra riportata è il seguente:

{
  "display": "form",
  "settings": null,
  "components": [
    {
      "label": "UUID dell'ente",
      "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      "applyMaskOn": "change",
      "hidden": true,
      "tableView": true,
      "validate": {
        "required": true
      },
      "key": "tenant_id",
      "type": "textfield",
      "input": true
    },
    {
      "label": "UUID del Servizio",
      "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      "applyMaskOn": "change",
      "hidden": true,
      "tableView": true,
      "validate": {
        "required": true
      },
      "key": "id",
      "type": "textfield",
      "input": true
    },
    {
      "label": "Descrizione del servizio",
      "placeholder": "Comune di Bugliano",
      "applyMaskOn": "change",
      "hidden": true,
      "tableView": true,
      "validate": {
        "required": true
      },
      "key": "description",
      "type": "textfield",
      "input": true,
      "defaultValue": "test"
    },
    {
      "label": "Impostazioni specifiche del protocollo",
      "tableView": false,
      "validate": {
        "required": false
      },
      "key": "registry",
      "type": "container",
      "input": true,
      "components": [
        {
          "label": "URL Protocollazione",
          "placeholder": "http://www.example.com/",
          "tableView": true,
          "validate": {
            "required": true
          },
          "key": "url",
          "type": "textfield",
          "input": true
        },
        {
          "label": "Gerarchia di Classificazione",
          "placeholder": "1.2.3",
          "tableView": true,
          "validate": {
            "required": false
          },
          "key": "classification_hierarchy",
          "type": "textfield",
          "input": true
        },
        {
          "label": "Numero Fascicolo",
          "placeholder": "1",
          "tableView": true,
          "validate": {
            "required": false
          },
          "key": "folder_number",
          "type": "textfield",
          "input": true
        },
        {
          "label": "Anno Fascicolo",
          "placeholder": "2022",
          "tableView": true,
          "validate": {
            "required": false
          },
          "key": "folder_year",
          "type": "textfield",
          "input": true
        }
      ]
    },
    {
      "label": "Attivo",
      "tableView": false,
      "validate": {
        "required": false
      },
      "key": "is_active",
      "type": "checkbox",
      "input": true,
      "defaultValue": false
    },
    {
      "label": "Salva",
      "tableView": false,
      "validate": {
        "required": false
      },
      "key": "submit",
      "type": "button",
      "input": true,
      "disableOnInvalid": true
    }
  ]
}

Premendo poi il bottone Salva, viene eseguita una POST /services servita dal Protocol Proxy, con payload

{
    "data": {
        "tenant_id": "",
        "id": "",
        "description": " ",
        "registry": {
            "url": "url.protocollo.it",
            "classification_hierarchy": "1.2.3",
            "folder_number": "1",
            "folder_year": "2023"
        },
        "is_active": true,
        "submit": false
    },
    "metadata": {}
}

Per modificare una configurazione esistente, il proxy serve l'API PUT /services/{service_id} e PATCH /services/{service_id}

Per eliminare una configurazione esistente, il proxy serve l'API DELETE /services/{service_id} . In questo caso l'eliminazione è una soft-delete, ovvero la configurazione viene semplicemente disattivata settando il parametro active a false.

Configurazione del tenant

la configurazione del tenant avviene in maniera nascosta durante la configurazione del servizio. Sarà la piattaforma a chiamare le API del Protocol Proxy. La loro implementazione è descritta nella sezione seguente

Alberatura configurazioni sullo storage

Le configurazioni di tenant e servizi vengono salvate con la seguente alberatura

root |____tenants | |____tenantid1.json | |____tenantid2.json | |____tenantid3.json | |____..... | |____tenantidn.json |____services | |____serviced1.json | |____serviced2.json | |____serviced3.json | |____..... | |____servicedn.json

Definizione API PDND Connector

Endpoint del pdnd-connector

Il pdnd-connector espone endpoint specifici per ciascun e-service integrato, con diverse operazioni supportate tramite verbi HTTP.

Endpoint di Chiamata

Gli endpoint seguono una struttura che identifica l'erogatore e il servizio specifico. Ad esempio, per gli e-service ANPR (ANPR è l'erogatore), si utilizzano path come /e-services/anpr/<nome-e-service>. Esempi di <nome-e-service> includono accertamento-cittadinanza, stato-famiglia, accertamento-residenza.

Verbi HTTP Utilizzati

  • GET: Utilizzato per la fruizione degli e-service, ovvero per richiedere e ottenere i dati dal pdnd-connector, che a sua volta li recupera dagli enti erogatori (es. ANPR, INPS).

  • POST: Utilizzato per la validazione dei dati precedentemente ottenuti tramite GET. Questa operazione verifica che i dati non siano stati alterati prima di essere inviati dal Core.

  • Parametri Richiesti (Payload per POST, URL per GET):

    • Per GET (Fruizione): I parametri sono inclusi nella URL (query parameters). Esempi includono:

      • fiscalCode: Il codice fiscale dell'utente di cui si richiedono i dati.

      • format: Un parametro che specifica il formato in cui i dati devono essere restituiti dal pdnd-connector. Questo formato deve essere compatibile con il Nested Form di OpenCity che riceverà i dati.

      • Esempio URL: GET /e-services/anpr/stato-famiglia?fiscalCode=ABCDEF01G23H456I&format=statoFamigliaArchetipo

    • Per POST (Validazione): Il payload della richiesta POST deve contenere due campi principali:

      • data: L'oggetto JSON contenente i dati esatti (incluso il loro formato) così come ricevuti dalla risposta GET precedente, senza i metadati di isFromPdnd, isReadonly, isUpdatedToday.

      • meta: Un oggetto contenente la firma digitale (signature) dei dati, così come restituita dalla risposta GET.

  • Header Necessari: Per tutte le chiamate agli endpoint del pdnd-connector, è necessario includere un token JWT nell'header Authorization.

    • Authorization: Bearer <JWT_TOKEN>. Il pdnd-connector effettua una validazione del token. Per le API di configurazione (non direttamente usate da OpenCity per fruizione), è richiesto un admin token. Per le API di fruizione e validazione, è richiesto un user token, e il codice fiscale contenuto nel token viene verificato con quello eventualmente presente nella URL.

Esempi di chiamate API

Di seguito, alcuni esempi di richieste e risposte per chiarire il flusso di integrazione.

Accertamento Residenza

Richiesta:

Risposta:

È disponibile anche il formato "residenza_archetipo" che include campi aggiuntivi:

Stato Famiglia

Richiesta:

Risposta (esempio con coniuge e figli):

Validazione delle risposte

Per validare l'autenticità di una risposta, si utilizza il relativo l'endpoint di validazione:

Richiesta:

Risposta:

Definizione OpenAPI

Logo

Processo di sviluppo

Test delle chiamate SOAP/REST dell'intermediario

Le API fornite nella documentazione dell'intermediario vengono generalmente testate in prima battuta via Postman/Insomnia nel caso di chiamate REST o via SoapUI nel caso di chiamate SOAP

Test in ambiente locale

A seguito dei test delle chiamate, si procede con l'implementazione del microservizio. Per testarlo localmente fare riferimento al seguente docker-compose.yml

version: '2'

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.0.0
    hostname: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:7.0.0
    hostname: kafka
    depends_on:
      - zookeeper
    ports:
      - "29092:29092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_CREATE_TOPICS: "applications,payments_cache"

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    ports:
      - "8080:8080"
    depends_on:
      - kafka
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
      # KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
      KAFKA_CLUSTERS_0_KSQLDBSERVER: ksqldb-server:8088

  topic-init:
    image: lorello/alpine-bash:1.4.0    
    depends_on:      
      - kafka    
    volumes:      
      - ./init.d:/data    
    command:      
      - /bin/bash      
      - -c      
      - |          
          wait-for-it kafka:9092          
          kaf config add-cluster local -b kafka:9092 && \          
          kaf config use-cluster local          
          set -ex          
          for I in /data/payment*.json; do            
            echo "Importing $$I"            
            cat $$I | jq -c | kaf produce payments          
          done

  # la configurazione varia in base al proxy da implementare
  iris-proxy:
    build:
      context: .
    ports:
      - "8000:8000"
    environment:
      CANCEL_PAYMENT: "false"
      KAFKA_TOPIC_NAME: "payments"
      KAFKA_BOOTSTRAP_SERVERS: "kafka:9092"
      KAFKA_GROUP_ID: "iris_payment_proxy"
      IRIS_OTF_PAYMENT_URL: "https://apistage.regione.toscana.it/C01/ComunicazionePosizioniDebitorieOTF/v3/IdpAllineamentoPendenzeEnteOTF"
      IRIS_IUV_URL: "https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneIUVService"
      IRIS_OUTCOME_URL: "https://apistage.regione.toscana.it/C01/InvioNotificaPagamentoEsito/v3/IdpInformativaPagamento.Esito"
      IRIS_VERIFY_URL: "https://apistage.regione.toscana.it/C01/VerificaStatoPagamento/v3/IdpVerificaStatoPagamento"
      IRIS_NOTICE_URL: "https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneAvvisiService"
      BASE_PATH_CONFIG: "sdc-payments/iris/tenants/"
      BASE_PATH_EVENT: "sdc-payments/iris/payments/"
      STORAGE_TYPE: "MINIO"
      STORAGE_KEY: "AKIAIOSFODNN7EXAMPLE"
      STORAGE_SECRET: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      STORAGE_BUCKET_NAME: "payments"
      STORAGE_ENDPOINT: "minio:9000"
    depends_on:
      - createbuckets
      - kafka

  # storage dove vengono salvate le configurazioni del tenant, del servizio e lo stato dei vari pagamenti
  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    restart: unless-stopped
    volumes:
      - ./bucket:/data
    ports:
      - 9000:9000
      - 9001:9001
    environment:
      MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE
      MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

  createbuckets:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
        /bin/sh -c "
        while ! /usr/bin/mc config host add minio http://minio:9000 AKIAIOSFODNN7EXAMPLE wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY;
        do echo 'Wait minio to startup...' && sleep 0.1; done;
        /usr/bin/mc mb minio/payments;
        /usr/bin/mc policy set public minio/payments;
        exit 0;
        "

Test pre-deploy

Se si sta facendo il deploy di un nuovo microservizio per la prima volta o si sta aggiungendo una nuova API o una nuova pagina a una interfaccia esistente, è necessario aggiungere alcuni controlli di qualità minima prima del rilascio.

Controlli

  • Test automatici delle API nella CI (usare hurl, qui un esempio di utilizzo e qui un esempio di integrazione nelle CI)

  • Test flusso standard

    • Inserire la configurazione del tenant

    • Inserire la configurazione del servizio

    • Inserire un esempio di pagamento in stato CREATION_PENDING nel topic payments e verificare che venga correttamente creato il debito

      • Chiamare l'API /offline-payment/{id} e verificare che venga scaricato correttamente l'avviso di pagamento cartaceo

      • Chiamare l'API /online-payment/{id} e verificare che si venga rediretti al portale di pagamento

      • Una volta arrivati in fondo al pagamento online, verificare che premendo il bottone "Torna alla Home" venga chiamata correttamente l'API /landing/{id}

      • Chiamare l'API /update/{id} e verificare che venga correttamente controllato lo stato del pagamento ed eventualmente aggiornato e prodotto un evento sul topic payments

      • Chiamare l'API /receipt/{id} e verificare che, a pagamento avvenuto, venga scaricata correttamente la ricevuta telematica

      • Inserire un esempio di pagamento in stato PAYMENT_PENDING che è già stato processato precedentemente dal proxy e verificare che venga ignorato

      • Inserire un esempio di pagamento in stato PAYMENT_PENDING che non è già stato processato precedentemente dal proxy e per cui esiste una configurazione sullo storage, e verificare che venga salvato correttamente sullo storage (caso importazione dovuti)

  • Test flusso di errore

    • Modificare la configurazione del servizio in modo che sia errata (mettendo ad esempio credenziali errate)

    • Inserire un esempio di pagamento in stato CREATION_PENDING nel topic payments e verificare che, a seguito del fallimento, venga prodotto un evento in stato CREATION_FAILED nel topic payments

Test in ambiente QA

I pagamenti sono testabili sul nostro ambiente di qa: https://servizi.comune-qa.bugliano.pi.it/.

Si potrà accedere come utente SPID utilizzando l'utente AGID TEST DEMO.

Si potrà accedere come admin per eventualmente modificare il servizio di test e la relativa configurazione di pagamento

Si potrà accedere come operatore per testare pagamenti posticipati, i quali richiedono la presa in carico della pratica e l'approvazione di quest'ultima affinchè l'utente possa procedere con il pagamento.

GET /anpr/accertamento-residenza
Headers:
  Authorization: Bearer <token_jwt>
Query Parameters:
  fiscal_code: RSSMRA80A01H501U
  config_id: a6fcd036-f1a2-4df5-b986-7410e9e0e97a
  format: residenza
{
  "data": {
    "address": "Via Roma",
    "house_number": "123",
    "municipality": "Roma",
    "county": "RM",
    "postal_code": "00100"
  },
  "meta": {
    "signature": "base64_encoded_signature",
    "format": "residenza",
    "created_at": "2024-03-26T10:00:00Z",
    "source": "ANPR",
    "call_url": "https://api.example.com/anpr/accertamento-residenza?config_id=a6fcd036-f1a2-4df5-b986-7410e9e0e97a&fiscal_code=RSSMRA80A01H501U&format=residenza"
  }
}
{
  "data": {
    "address": "Via Roma",
    "house_number": "123",
    "municipality": "Roma",
    "county": "RM",
    "postal_code": "00100",
    "municipality_istat_code": "058091",
    "locator_within": "int1"
  },
  "meta": {
    // ... stessi campi meta ...
  }
}
GET /anpr/stato-famiglia
Headers:
  Authorization: Bearer <token_jwt>
Query Parameters:
  fiscal_code: RSSMRA80A01H501U
  config_id: c88d7efe-84a1-43c5-9edc-07f18a577289
  format: famiglia
{
  "data": {
    "birth_date": "1980-01-01",
    "birth_place": "Roma",
    "family_name": "Rossi",
    "given_name": "Maria",
    "gender": "femmina",
    "tax_id": "RSSMRA80A41H501U",
    "children": [
      {
        "birth_date": "2010-02-25",
        "birth_place": "Roma",
        "family_name": "Rossi",
        "given_name": "Marco",
        "gender": "maschio",
        "tax_id": "RSSMRC10B25H501U"
      },
      {
        "birth_date": "2012-03-15",
        "birth_place": "Roma",
        "family_name": "Rossi",
        "given_name": "Laura",
        "gender": "femmina",
        "tax_id": "RSSLRA12C55H501U"
      }
    ]
  },
  "meta": {
    "signature": "base64_encoded_signature",
    "format": "famiglia",
    "created_at": "2024-03-26T10:00:00Z",
    "source": "ANPR",
    "call_url": "https://api.example.com/anpr/stato-famiglia?config_id=c88d7efe-84a1-43c5-9edc-07f18a577289&fiscal_code=RSSMRA80A01H501U&format=famiglia"
  }
}
POST /validate/anpr/accertamento-residenza
Headers:
  Authorization: Bearer <token_jwt>
Query Parameters:
  fiscal_code: RSSMRA80A01H501U
  config_id: a6fcd036-f1a2-4df5-b986-7410e9e0e97a
  format: residenza
Body:
{
  "data": {
    // ... dati ricevuti ...
  },
  "meta": {
    // ... meta ricevuti con la firma ...
  }
}
{
  "result": true  // o false se la validazione fallisce
}

WorkFlow sistema di protocollazione

Il Protocol Proxy interagisce con la piattaforma in due fasi distinte ma complementari:

  • Abilitazione e configurazione del sistema di protocollazione

  • Protocollazione dei documenti

Fase 1 - abilitazione di un nuovo servizio di protocollazione per il servizio X(happy path)

Sequence Diagram fase di confiugurazione di un sistema di protocollo

In questa fase l'amministratore della piattaforma configura un servizio, decidendo quale sistema di protocollazione abilitare per la protocollazione dei relativi servizi.

interfaccia di abilitazione di un protocollo esterno

Cliccando sul bottone "abilita" la piattaforma contatta il Protocol proxy relativo tramite chiamate REST API. Con la prima chiamata, la piattaforma richiede la configurazione del form schema che verrà presentato all'Amministratore. Attraverso tale form è possibile inserire le configurazioni relative al tenant e al servizio corrente. Tali configurazioni verranno inviate via REST HTTPS al Protocol Proxy che si occuperà della persistenza di tali configurazioni.

Interfaccia di configurazione di un sistema di protocollazione

Una volta che tale processo si è concluso, il Protocol Proxy è in grado, sfruttando tali configurazioni, di interagire con il sistema di protocollazione esterno.

Le configurazioni di protocollazione possono essere create, modificate e disabilitate. Per questo motivo il Protocol Proxy deve esporre chiamate API CRUD (dettagliate nella pagina seguente).

Fase 2 - Protocollazione dei documenti (happy path)

Prima di eseguire qualsiasi operazione, il protocollo proxy deve verificare la versione dell'evento (event_version) ed elaborare esclusivamente la versione 1, per la quale è abilitato. La versione dell'evento da considerare deve essere un valore fissato nel codice. Tutti gli eventi con event_version diversa da 1 devono essere scartati.

In questo diagramma, viene descritto il workflow di una pratica (ma il processo è uguale per qualsiasi altro tipo di richiesta) presentata da un utente e presa in carico da un operatore(le altre casistiche descritte nella pagina "Architettura del sistema di protocollazione" seguono lo stesso processo).

Una volta che la pratica è stata presa in carico dell'operatore essa deve essere protocollata. La piattaforma produce un evento di tipo documento sul topic "documents". Il Protocol Proxy, consuma tale evento:

  • Per poter protocollare correttamente il documento, è fondamentale verificare l'esistenza delle configurazioni relative al tenant e al servizio pertinenti. Questa operazione può essere eseguita attraverso la verifica del campo document.RemoteCollection.ID, che identifica il servizio, e document.tenantId, che identifical'ID del tenant. Questi campi dovrebbero essere confrontati con gli ID delle configurazioni corrispondenti precedentemente salvate nello storage. Nel caso in cui tali configurazioni non siano disponibili, il documento dovrà essere semplicemente ignorato, procedendo all'evento successivo.

  • Una volta assicuratosi di dover protocollare il documento in esame, il Protocol Proxy deve verificare che il documento non sia già stato protocollato. Un evento è protocollato quando l'oggetto "document_number" è valorizzato con almeno il numero di protocollo. Nel caso il documento sia già stato protocollato, l'evento va semplicemente ignorato.

  • Una volta verificato che il documento sia da protocollare, è necessario modificare il campo status da DOCUMENT_CREATED o REGISTRATION_FAILED o PARTIAL_REGISTRATION a REGISTRATION_PENDING, poi si produce l'evento sul topic documents

  • Una volta che si rilegge lo stesso evento prodotto allo step precedente, e una volta superate le precedenti verifiche, Protocol Proxy può interagire con il sistema di protocollazione esterno per la protocollazione del documento.

  • Se la protocollazione va a buon fine, il documento verrà modificato con i dati di protocollazione (vedi sezione successive), verrà inoltre sovrascritto il campo stato settandolo a REGISTRATION_COMPLETE , infine, sarà necessario aggiornare anche il campo updated_at con il timestamp corrente (time.now()) in formato ISO 8601 (es: 2023-11-10T10:54:44+00:00).

  • Il Protocol Proxy produce un nuovo evento di tipo document sul topic documents con in aggiunta le informazioni di protocollazione (valorizzando l'oggetto registration_data).

  • A questo punto la piattaforma, in ascolto sullo stesso topic, provvederà ad aggiornare il suo stato interno.

Altri campi da popolare quando si produce un evento

Quando viene generato un evento, il protocollo proxy deve assegnare i seguenti valori:

  • app_id: <nome del servizio>:<versione corrente del servizio>. Es: protocol-proxy-fake:1.1.1

  • event_created_at: es: 2023-11-23T14:14:23+00:00

  • event_id: stringa UUID autogenerata che identifica univocamente l'evento

Casi di errore - sistema di retry

In un sistema a eventi che utilizza il pattern event sourcing, è possibile che alcuni eventi non vengano processati correttamente a causa di errori temporanei come per esempio una comunicazione interrotta tra il Protocol Proxy e il sistema di protocollazione esterno. In questo caso basterebbe tentare nuovamente il consumo dello stesso evento più volte finché il problema non viene risolto.

La piattaforma offre questo tipo di possibilità attraverso l'implementazione di un meccanismo di retry che legge da un topic (retry_queue) specifico tutti gli eventi falliti e li reinserisce nei vari topic di origine in base a una politica di retry scelta a monte.

Il servizio che si occupa di questo meccanismo è in grado di gestire eventi eterogenei provenienti da diversi topic. Questo è possibile solo se il servizio è a conoscenza del topic di origine in cui reinserire gli eventi da processare nuovamente.

Per questo motivo, qualsiasi servizio si voglia avvalere del meccanismo di retry, deve modificare l'evento fallito inserendo il topic in cui si vuole che sia reinserito e poi produrre l'evento nel topic di retry impostato tramite variabile d'ambiente.

I metadati da inserire nell'evento sono nella seguente forma:

{   
   "retry_meta": {
     "original_topic": "documents"
   }
}

Quando l'oggetto sarà reinserito nel topic "document" dal Retry Orchestrator, l'oggetto "retry_meta" avrà dei campi aggiuntivi utili per attuare la politica di retry. Tali campi, sono gestiti e utili unicamente al Retry Orchestrator. Il Protocol Proxy non si deve quindi preoccupare di tale oggetto una volta inserito l'oggetto "retry_meta" con il campo "original_topic" valorizzato una tantum.

Use case : protocollazione totalmente fallita

Nel caso in cui non si riesca a protocollare la pratica per un errore da parte del provider per esempio, o nel caso in cui nessuno delle chiamate al protocollo vadano a buon fine, è necessario sovrascrivere il campo status a REGISTRATION_FAILED.

Use case : protocollazione avvenuta a metà e deve essere ripresa da do dove è stata interrotta

Nel caso in cui la protocollazione richieda diversi step (come per esempio protocollazione del documento principale e a seguito la protocollazione dei vari allegati) e questa venga interrotta a metà, l'evento dovrà essere inserito nella coda di retry assime alle informazioni relative allo stato corrente in modo da riprendere la protocollazione da dove è stata interrotta. Specificamente il documento va messo nello stato PARTIAL_REGISTRATION.

In tale circostanza, è possibile inserire liberamente tutti i campi rilevanti per il monitoraggio dello stato all'interno dell'oggetto retry_meta. Questa flessibilità consente di adattare i dati alle specifiche esigenze, garantendo un controllo accurato del processo di protocollazione e prevenendo duplicazioni delle operazioni.

Solitamente la protocollazione di un documento digitale è accompagnata anche dalla protocollazione dei relativi allegati(menzionati nel paragrafo ) e dalla fascicolazione degli stessi. Dal punto di vista del Protocol Proxy, la protocollazione del documento, di tutti i suoi allegati e la fascicolazione è considerata come un unica operazione atomica. Questo implica che il processo di protocollazione è considerato completo solo quando tutte le chiamate relative al documento principale, agli eventuali allegati e alla eventuale fascicolazione si concludono con successo. Se anche solo una chiamata dovesse fallire, questo costituirà una condizione sufficiente per interrompere il processo di protocollazione e avviare il meccanismo di retry descritto nel paragrafo precedente.

Schema degli stati di un documento

Il documento può avere i seguenti stati:

  • DOCUMENT_CREATED : stato iniziale in cui si trova il documento quando viene prodotto dal document dispatcher

  • REGISTRATION_PENDING : stato in cui viene prodotto il documento dal protocol proxy non appena viene trovato in stato DOCUMENT_CREATED, PARTIAL_REGISTRATION o REGISTRATION_FAILED e presenta una configurazione attiva per esso

  • REGISTRATION_COMPLETE : stato in cui viene prodotto il documento dal protocol proxy dopo essere stato protocollato correttamente

  • PARTIAL_REGISTRATION : stato in cui viene prodotto il documento dal protocol proxy nel caso in cui si ha molteplici chiamate da fare e una di questa fallisce (per esempio non vengono caricati tutti gli allegati)

  • REGISTRATION_FAILED : stato in cui viene prodotto il documento dal protocol proxy nel caso in cui la protocollazione fallisce totalmente

Di seguito la rappresentazione grafica

La fascicolazione dei documenti protocollati

La Fascicolazione di un documento rappresenta l'attribuzione dello stesso ad una unità archivistica – “il fascicolo” - che raggruppa un insieme di documenti appartenenti al medesimo procedimento. Non tutti i sistemi di protocollazione offrono questa funzionalità. In questo caso la valorizzazione del campo "folder_number" deve essere discussa e definita a seconda dei casi.

Interazione Protocol Proxy<--> Piattaforma Opencity

Durante la fase di protocollazione, è possibile che il Protocol Proxy debba utilizzare le API della piattaforma per ottenere le informazioni necessarie per eseguire il processo di protocollazione. Un esempio pratico di ciò riguarda la protocollazione degli allegati. Il sistema di protocollazione richiede che il base64 del file allegato sia incluso nel payload della chiamata. Tuttavia, nel documento digitale sono presenti solo i link per scaricare i file. Pertanto, sarà compito del Protocol Proxy autenticarsi sulla piattaforma per recuperare il Token JWS e successivamente chiamare l'API specifica per ottenere il file necessario da cui calcolare il Base64.

Il token può essere recuperato con la seguente chiamata:

  • Username : admin

  • Password: admin

  • Basepath: estratto dal campo "Url" di ogni allegato

  • Endpoint : /lang/api/auth

  • method: POST

  • header Content-Type: application/json

  • payload : { "username": "admin", "password": "admin" }

  • Response: 200 -> {"token": "eyDIJeiojf...."}

  • Response: 401 -> { "code": 401, "message": "Credenziali non valide." }

  • Response 400 -> HTML body

Le API della piattaforma sono documentate al seguente link: https://servizi.comune-qa.bugliano.pi.it/lang/api/doc Calcolo del basepath per recuperare il token L'url per API auth deve essere estratta da campo url di ogni attachment: quindi dal seguente documento avremo una post auth all'endpoint https://servizi.comune-di-vicenza.it/lang/api/auth con credenziali fornite tramite variabili d'ambiente(utente e password). Mentre nel secondo caso avremo una post auth all'endpoint https://servizi.comune-di-bugliano.it/lang/api/auth con credenziali fornite tramite variabili d'ambiente(utente e password).

NOTA BENE: nel seguente esempio abbiamo un array di attachments in cui due url appartengono a due comuni diversi. Questo nel mondo reale non può succedere. Il base url è lo stesso per tutto il documento(cioè un documento appartiene ad un solo comune). Sono stati utilizzati due comuni diversi allo scopo di sottolineare la parte di url che cambia nella costruzione dell'endpoint della API di autenticazione.

{
   "attachments": [
	{
		"name": "dummy.pdf",
		"description": "Allegato - dummy.pdf",
		"mime_type": "application/pdf",
		"url": "https://servizi.comune-di-vicenza.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/a4c81464-ee91-449a-b2a3-7010c59fdee7?version=1",
		"md5": "",
		"filename": "dummy.pdf"
	},
	{
		"name": "dummy2.pdf",
		"description": "Allegato - dummy2.pdf",
		"mime_type": "application/pdf",
		"url": "https://servizi.comune-di-bugliano.it/lang/api/applications/1a30529e-9c41-4352-9198-1fac44c5e911/attachments/d284be8a-2700-48a9-aa4b-fc0136674548?version=1",
		"md5": "",
		"filename": "dummy2.pdf"
	}
   ],
}   

Microservizi

Tutti i microservizi che compongono la piattaforma devo rispettare i seguenti standard e regole generali

Tutti i servizi devono rispettare i 12factor, descritti in modo anche più dettagliato nell'articolo An illustrated guide to 12 Factor Apps

Packaging

Obiettivo

Ogni microservizio deve poter essere eseguito facilmente in locale, anche da chi non conosce a fondo il progetto. L’obiettivo del packaging è:

  • facilitare l’avvio dell’applicativo;

  • semplificare lo sviluppo e il debugging;

  • rendere accessibile un ambiente funzionante con un semplice docker-compose up.

Struttura del repository

Nella root del repository devono essere presenti i seguenti file:

File
Descrizione

Dockerfile

Build dell’immagine base del servizio

docker-compose.yml

Definisce i container minimi per l’esecuzione in produzione o ambienti CI

docker-compose.dev.yml

Contiene configurazioni utili per lo sviluppo locale (es. build, mount volumi, strumenti debug)

.dockerignore

(opzionale) Esclude file e cartelle non rilevanti dalla build


Principi e buone pratiche

  • Il file docker-compose.yml deve essere autosufficiente: chi lo scarica deve poter eseguire il servizio senza dover clonare l’intero repository.

  • Le immagini Docker utilizzate devono essere:

    • disponibili su un registry pubblico (es. DockerHub, GitHub Container Registry ecc.);

    • oppure facilmente costruibili in locale tramite override.

⚠️ Non includere la chiave build: nel docker-compose.yml principale, per evitare conflitti nei contesti di esecuzione remota (es. CI/CD).

Sviluppo locale

Per lo sviluppo in locale:

  1. Copia o rinomina il file docker-compose.dev.yml in docker-compose.override.yml

  2. Esegui:

    docker-compose up
  3. Docker gestirà automaticamente il merge tra docker-compose.yml e docker-compose.override.yml (vedi: Docker Docs – Multiple Compose Files).

✅ In questo modo, chiunque può avviare l’ambiente di sviluppo senza modificare i file originali.

Esempio di uso dei file

docker-compose.yml (semplificato)

version: "3.9"
services:
  myservice:
    image: registry.gitlab.com/opencity-labs/myservice:1.2.3
    ports:
      - "8080:8080"

docker-compose.dev.yml

version: "3.9"
services:
  myservice:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./:/app
    environment:
      - ENVIRONMENT=local

Dopo aver rinominato docker-compose.dev.yml in docker-compose.override.yml, il comando:

docker-compose up

...utilizzerà in automatico l’immagine build locale con i volumi montati per lo sviluppo.

Terminazione Pulita

Obiettivo

Ogni microservizio deve essere in grado di gestire correttamente la terminazione, in modo da:

  • garantire la consistenza dei dati;

  • evitare la perdita di eventi o operazioni incomplete;

  • chiudere le risorse in uso in maniera sicura (DB, Kafka, file, socket...).

La gestione corretta della terminazione è essenziale sia in ambiente Docker che in Kubernetes, dove il segnale di terminazione (SIGTERM) viene inviato prima di un restart o di un downscaling.

Segnali da gestire

Segnale
Significato
Comportamento atteso

SIGTERM

Richiesta di terminazione gentile

Il servizio avvia lo shutdown controllato

SIGKILL

Terminazione forzata e immediata

Non gestibile dal codice; da evitare

✅ Il servizio DEVE catturare SIGTERM e avviare una sequenza di terminazione controllata.


Comportamento atteso

Alla ricezione di SIGTERM, il servizio deve:

  • smettere di accettare nuove richieste o eventi;

  • completare le operazioni critiche in corso;

  • liberare risorse (connessioni, file, lock...);

  • loggare la terminazione a livello INFO;

  • uscire con exit code 0 entro un tempo ragionevole.

Checklist – Terminazione Pulita

Verifica
Descrizione
Esito

Cattura SIGTERM

Il servizio intercetta correttamente il segnale SIGTERM

☐

Terminazione controllata

Avvia una sequenza di chiusura ordinata

☐

Timeout ragionevole

Si chiude in max 10s (o valore configurato)

☐

Operazioni critiche completate

Nessuna perdita o corruzione dati

☐

Chiusura delle risorse

Socket, consumer, file, connessioni DB...

☐

Log di terminazione

Esempio: "SIGTERM ricevuto, shutdown in corso..."

☐

Nessuna nuova richiesta

I listener vengono disattivati

☐

Exit code 0

Il processo si chiude correttamente

☐

Esempio Python – uvicorn + asyncio

import asyncio
import signal
import uvicorn
from myservice import app

APP_HOST = "0.0.0.0"
APP_PORT = 8080

def run_server():
    """Esegue il server intercettando SIGINT e SIGTERM per uno shutdown pulito"""
    config = uvicorn.Config(
        app,
        host=APP_HOST,
        port=int(APP_PORT),
        proxy_headers=True,
        forwarded_allow_ips='*',
        access_log=False,
    )
    server = uvicorn.Server(config)

    loop = asyncio.get_event_loop()

    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, lambda: asyncio.create_task(server.shutdown()))

    try:
        loop.run_until_complete(server.serve())
    except asyncio.CancelledError:
        pass  # Evita traceback in console

if __name__ == '__main__':
    run_server()

Esempio Go – signal.NotifyContext

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("pong"))
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    go func() {
        log.Println("Server in avvio su :8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Errore durante l'avvio del server: %v", err)
        }
    }()

    <-ctx.Done()
    log.Println("SIGTERM ricevuto, avvio terminazione...")

    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := srv.Shutdown(shutdownCtx); err != nil {
        log.Fatalf("Errore durante la terminazione: %v", err)
    }

    log.Println("Terminazione completata.")
}

Script di test automatico

Usare questo script per validare la terminazione pulita in locale o integrarlo nella CI.

#!/bin/bash
set -euo pipefail

SERVICE_NAME="test_shutdown_service"
IMAGE_NAME="your-image-name"
LOG_FILE="shutdown_test.log"

echo "▶️ Avvio container..."
docker run --rm --name "$SERVICE_NAME" -d "$IMAGE_NAME" > /dev/null

sleep 2  # attesa minima per stabilità

echo "🛑 Invio SIGTERM..."
docker kill --signal=SIGTERM "$SERVICE_NAME"

sleep 2

echo "🔍 Verifica terminazione..."
if docker ps -a --format '{{.Names}}' | grep -q "$SERVICE_NAME"; then
  echo "❌ Il container è ancora in esecuzione"
  exit 1
else
  echo "✅ Terminazione completata correttamente"
fi
  • Se si usa docker-compose, si può integrare il test nel CI lanciando:

    docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d
    docker-compose kill -s SIGTERM

Configurazione

Principi

La configurazione dei microservizi deve avvenire:

  • principalmente tramite variabili d’ambiente;

  • opzionalmente tramite:

    • file .env (per ambienti locali);

    • parametri CLI (per job schedulati o debug interattivo).

Best practice

Metodo
Contesto di utilizzo

Variabili d’ambiente

Default per tutti i deployment (prod/dev/test)

.env file

Sviluppo locale o test manuali

CLI parametri

Script cron, job schedulati, test/debug

❗ Le configurazioni non devono essere hardcoded nel codice. Utilizzare sempre default sicuri e sovrascrivibili.

Healthcheck

Obiettivo

Permettere al sistema di orchestrazione (Docker/Kubernetes) di verificare che il servizio sia vivo e funzionante.

HTTP microservizi

  • Deve esporre un endpoint /status:

    • 200 OK se tutto è funzionante;

    • codice diverso in caso di errore.

Altri microservizi

  • Se il servizio non è HTTP, l’healthcheck può essere:

    • la presenza di un file di stato;

    • la verifica di un processo in esecuzione.

✅ L’healthcheck DEVE essere incluso nel Dockerfile.

Logging

Principi generali

  • Tutti i microservizi devono implementare un sistema di log coerente, strutturato e facilmente aggregabile.

  • Riferimento normativo: Allegato 4 delle Linee Guida di Interoperabilità AgID.

  • I log devono supportare almeno i livelli: DEBUG, INFO, ERROR. Una gestione completa prevede sei livelli (vedi sotto).

  • Ogni log di errore o anomalia deve essere su una singola riga, facilmente parsabile (plaintext key=value o JSON).

  • Evitare log verbosi e non strutturati: ostacolano il monitoraggio e la correlazione cross-microservizio.

⚠️ Non mischiare log di tipo HTTP e stacktrace multilinea.

  • Scrivere i log a singola riga su stdout.

  • Scrivere stacktrace (se necessario) separatamente su stderr.

Formato e contenuto dei log

  • Preferibile l’uso di log in formato JSON puro, ma solo se interamente strutturato.

  • È vietato:

    • Mischiare testo libero e JSON nello stesso log.

    • Includere payload JSON grezzi come stringa in un campo JSON (es. loggare interamente l’evento Kafka).

Campi richiesti in ogni log rilevante

Campo
Obbligatorio
Descrizione

level

✅

Definisce il tipo di log (INFO, ERROR, DEBUG, CRITICAL, WARNING, ecc.)

timestamp

✅

In UTC, formato , con T e Z maiuscoli

environment

✅

Definisce l'ambiente in cui il microservizio è in esecuzione

event_id

⚠️

Identificatore univoco dell’evento (se applicabile)

event_type

✅

Tipo di evento o operazione ricevuta

call_type

⚠️

Tipo chiamata: HTTP, CLI, EVENT

topic

⚠️

Kafka topic da cui è stato letto l’evento (se applicabile)

log_message

✅

Messaggio del log (errore o info)

client_ip

⚠️

IP della chiamata in ingresso. Usare X-Forwarded-For quando presente

tenant

✅

identificativo univoco dell'ente a partire dal quale è stato generato il log

provider, application, service

✅

Contesto operativo (se disponibili)

user_id

❌

Se presente, deve essere un riferimento anonimo (es. ID utente). Mai dati personali in chiaro

All’avvio, ogni servizio DEVE loggare la versione in esecuzione e l’ambiente (es. local, boat-qa, boat-prod).


Livelli di log

Level
Description
Required

CRITICAL

Fallimento grave, con perdita di dati o che comporta lo l'uscita dal flusso di esecuzione (shutdown) del servizio per impossibilità a proseguire o perché è più safe non proseguire.

No

ERROR

Anomalia che non è possibile gestire e che avrà effetti sul risultato. A ERROR, il rate dei log che si alza dovrebbe essere indice che ci sono problemi per i quali è importante attrarre l'attenzione degli amministratori. Attenzione a non confondere errori che si verificano su sistemi esterni e che non sono nostri errori. Date sempre per scontato che una riga di errore prodotta dovrebbe sempre essere letta da qualcuno, altrimenti meglio esporre un livello di warning o non loggare proprio. Una anomalia dovrebbe sempre dare origine a una e una sola riga di log.

Si

WARNING

Anomalia che non avrebbe dobuto presentarsi ma che è stata gestita correttamente per non comportare errori nel nostro servizio. Ad esempio ho ricevuto un evento con una versione negativa, ma l'ho ignorato. Oppure non sono riuscito a collegarmi al db la prima volta, ma solo dopo 2 tentativi.

Si

INFO

Comunica un cambiamento di stato del servizio. Un evento significativo produce una riga di log per ogni evento o chiamata ricevuta dall'applicativo. Se una transazione o un evento gestito presenta errori NON si deve produrre la riga di INFO e la riga di ERROR, ma solo la seconda.

Si

DEBUG

Questo è il livello in cui comunicare informazioni diagnostiche, non necessarie se non si sta investigando un errore specifico. Devono essere informazioni utili a comprendere errori, non semplicemente a comprendere il flusso interno del software, per il quale esiste il livello apposito (TRACE). Una informazione utile è dare tutto il contesto che permette di comprendere perché il servizio si sta comportando in un certo modo.

No

TRACE

Questo livello è inteso per tracciare il flusso interno del servizio, in modo molto dettagliato. Per esempio si può inserire un log a questo livello per capire in quale ramo del codice siamo finiti con l'esecuzione, oppure si può inserire una riga all'inizio e una alla fine di certe porzioni di codice rilevanti.

No

✅ Ogni anomalia deve generare una e una sola riga di log a INFO, i dettagli vanno a DEBUG.


Esempi di log ben formattati

JSON strutturato

{
  "level": "ERROR",
  "timestamp": "2025-04-18T10:27:52Z",
  "environment": "boat-prod",
  "event_id": "abcd-1234",
  "event_type": "PaymentRequestReceived",
  "topic": "pagopa-incoming-events",
  "version": "1.3.2",
  "environment": "boat-prod",
  "tenant": "9bee12a4-...",
  "application": "50b4f598-...",
  "service": "0a28427f-...",
  "provider": "sicraweb-wsprotocollodm",
  "client_ip": "10.12.3.1",
  "user_id": "user_92348",
  "status": "error",
  "message": "Impossibile trovare dati di logon validi per l'utente"
}

Plaintext key=value

ERROR 2025-04-18T10:27:52Z event_id=abcd-1234 tracker=252603771125040 provider=sicraweb-wsprotocollodm tenant=9bee12a4 application=50b4f598 service=0a28427f topic=pagopa-incoming-events microservice=payment-handler version=1.3.2 environment=boat-prod client_ip=10.12.3.1 user_id=user_92348 error="Impossibile trovare dati di logon validi per l'utente; nested exception: Connection aborted (Connection reset by peer)"

Checklist di qualità per log ERROR

Usare questa checklist in review e PR:

Monitoring errori

Obiettivo

Raccogliere centralmente gli errori per individuare rapidamente problemi in produzione.

Requisiti

  • Ogni microservizio DEVE integrare Sentry.

  • Una volta configurato:

    • abilitare l’integrazione con Sentry;

    • definire regole di alert personalizzate, se quelle standard non bastano.

📬 Usare SENTRY_DSN come variabile d’ambiente per collegare il servizio a Sentry.

Monitoring metriche

Obiettivo

Esportare metriche di stato e performance per il monitoraggio continuo.

Endpoint Prometheus

  • Se HTTP, il microservizio DEVE esporre un endpoint /metrics in formato Prometheus.

Cosa monitorare

Tipo
Obbligatorio
Descrizione

Error counters

✅

Numero errori critici/intermittenti

Response histograms

✅

Tempi di risposta di servizi esterni

Status code metrics

⚠️

Solo se utili (es. 500 frequenti) – altrimenti usiamo Traefik

404 o bot traffic

❌

NON deve essere esposto come metrica – produce rumore inutile

Cron

Principio

Non reinventare un sistema di schedulazione dentro l’applicazione. Usare invece l’orchestratore.

Linee guida

  • NON implementare schedulatori interni (es. cron manuale, loop time-based).

  • Il servizio deve essere idempotente: rieseguibile sugli stessi dati senza effetti collaterali.

Esecuzione corretta

Implementare task cron come comandi CLI, eseguibili con la stessa immagine Docker:

docker run your-image-name python cron_jobs/protocol_batch.py

Il job verrà poi schedulato tramite clustered cron (es. crazy-max/cronjob).

Parametri e file di configurazione

Principio

Niente overengineering: Docker impone già un mapping tra esterno e interno.

Best practice

Elemento
Regola

Path interni

Usa path fissi tipo /data, senza preoccuparsi del mapping esterno

Configs

Usare per file di configurazione

Secrets

Usare per credenziali e dati sensibili

NO volumes

Evitare l’uso di volume mapping per la configurazione

✅ Questo approccio garantisce sicurezza, semplicità e prevedibilità.

Continuous Integration

Obiettivo

Standardizzare la CI tra i progetti, garantendo efficienza, velocità e qualità.

Requisiti

  • Ogni progetto DEVE includere la CI condivisa:

stages:
 - build
 - test
 - deploy
 - review
 - dast
 - staging
 - canary
 - production
 - incremental rollout 10%
 - incremental rollout 25%
 - incremental rollout 50%
 - incremental rollout 100%
 - performance
 - cleanup

include:
  - project: 'opencity-labs/product'
    ref: main
    file: '.gitlab/ci/devops.yml'
  • Eventuali step personalizzati (es. test, lint, coverage) vanno inseriti dopo lo include.

  • Devono riutilizzare la build già fatta tramite artifact, evitando rebuild inutili.

Performance CI

  • Durata massima della CI: 15 minuti

  • Durata consigliata: ≤ 5 minuti

⚡ Ottimizzare usando:

  • build multi-stage Docker;

  • cache di GitLab;

  • test selettivi.

Test publiccode.yml

Per progetti pubblicati su Developers Italia:

publiccode:
  stage: test
  allow_failure: false
  image:
    name: italia/publiccode-parser-go
    entrypoint: [""]
  script:
    - publiccode-parser /dev/stdin < publiccode.yml
  only:
    - master

Modello di integrazione con l'area personale

Questo documento descrive gli aspetti tecnici dell'importazione delle Pratiche Lite, uno strumento che permette di integrare le pratiche provenienti da altri gestionali.

La nostra piattaforma permette al cittadino di interagire sia con le pratiche generate sulla piattaforma stessa sia con pratiche generate su altri gestionali. Cittadini e Operatori potranno interagire da un'unica interfaccia con un elenco uniforme di pratiche.

Per realizzare questa integrazione è stato ideato il concetto di Pratiche Lite, che rappresenta una pratica con un set minimo di dati, utili per permettere al Cittadino di visualizzarle e interagire con esse in maniera uniforme a quanto accade con le Pratiche generate direttamente nel nostro sistema. Ma non solo, è possibile visualizzare anche informazioni relative ai documenti correlati alla pratica e i pagamenti.

Sarà necessario fornirci un elenco di Pratiche Lite per esporle sulla nostra piattaforma. Questi dati potranno essere forniti tramite API ReST in formato JSON.

I vantaggi dell'integrazione con l'area personale

Integrare le pratiche generare da altri gestionali nell'area personale permette al cittadino di:

  • consultare in un unico posto tutte le pratiche relative a un cittadino, anche se generate da altri sistemi, come previsto dal modello del sito comunale del dipartimento;

  • seguire l'iter della pratica;

  • consultare i dati essenziali di una pratica, come il richiedente e il beneficiario;

  • consultare i documenti associati alla pratica e lo stato della protocollazione;

  • consultare i pagamenti associati alla pratica;

Ma l'area personale non è solo consultazione, il cittadino, se l'integrazione lo prevede, potrà comunque interagire con le pratiche, anche se generate da sistemi esterni. Operazioni come la visualizzazione completa della pratica, la modifica, l'integrazione dei documenti o l'annullamento della richiesta, saranno eseguite direttamente sul gestionale esterno, invocando gli opportuni endpoint HTTP corrispondenti alle azioni da eseguire. Questi endpoint verranno specificati pratica per pratica all'atto dell'importazione.

Dettaglio di una Pratica Lite nell'area personale

Modello della Pratica Lite

Per permettere un'integrazione basilare è necessario inviare solo:

  • titolo e stato della pratica

  • nome, cognome e codice fiscale del richiedente

  • un link per raggiungere la pagina di dettaglio della pratica

Ma è comunque possibile aggiungere ulteriori dati per rendere l'esperienza utente più completa:

  • Pratica: titolo e stato della pratica

    • Richiedente: nome, cognome, codice fiscale e contatti del richiedente

    • Beneficiario : nome, cognome, codice fiscale e contatti del beneficiario

    • Servizio: servizio collegato alla pratica

    • Endpoint delle azioni: endpoint http delle azioni eseguibili sulla pratica

  • Documenti: documenti collegati alla pratica e stato di protocollazione

  • Pagamenti: pagamenti collegati alla pratica

Nel capitolo Flusso di import delle Applications Lite vengono riportati i tracciati dettagliati per una corretta importazione.

Stati della Pratica Lite

Il seguente diagramma mostra i possibili stati in cui può trovarsi una pratica e un possibile flusso di gestione. Come specificato più avanti, le transizioni da uno stato all'altro sono solo indicative, il controllo sui cambi di stato resta al gestionale eterno.

  • Bozza: in questo stato la pratica non è considerata ancora completamente compilata e può essere modificabile

  • Inviata: la pratica è stata compilata in ogni sua parte da cittadino e definitivamente inviata. In questo stato non è più possibile modificare i dati delle pratica

  • Presa in carico: la pratica è stata presa in carico da un Operatore, il quale effettuerà le normali operazioni d'ufficio necessarie a gestirla

  • In attesa di integrazione: l'operatore ha richiesto di integrare la pratica con informazioni e/o documenti mancanti. La pratica resta in questo stato fino a quando non si ottiene una risposta dal Cittadino

  • Rifiutata: la pratica non è stata accettata dall'operatore che l'ha gestita

  • Ritirata: la pratica è stata ritirata dal cittadino prima che l'ufficio si sia pronunciata

  • Annullata: quando l'ufficio definisce che non si procederà ulteriormente con la pratica e non si pronuncerà su ulteriore approvazione o rifiuto

Modalità di integrazione

L'invio dei dati al nostro sistema avviene in modalità push tramite API: è possibile inviarci tramite apposita API REST i dati delle pratiche per inserire nuove pratiche o per aggiornarne lo stato. Questa modalità di aggiornamento è utile per mantenere costantemente aggiornati i dati delle pratiche tra due sistemi, avendo quindi modifiche immediate per il cittadino.

È comunque possibile valutare altre modalità di integrazione, per maggiori dettagli visiona il capitolo Progetti di integrazione ad-hoc con area personale

Autenticazione

Il dialogo tra l'Area personale e il gestionale esterno è bidirezionale: il gestionale importa costantemente sull'area personale i dati aggiornati; il Cittadino, agendo su una Application Lite dall'Area personale, atterra su una pagina del gestionale. I due casi prevedono due autenticazioni differenti.

Il primo caso rappresenta una comunicazione server-to-server che avviene mediante token JWT. Verranno fornite delle credenziali di autenticazione che permetteranno di ottenere un token; il token verrà utilizzato per effettuare le richieste successive. È possibile seguire questa guida per le istruzioni di autenticazione https://docs.opencityitalia.it/developers/integrazioni/integrazioni-con-il-flusso-delle-pratiche/api-rest

Il secondo caso è più complesso del primo perché bisogna garantire che l'utente effettui un solo login anche qualora passi dall'area personale al gestionale. Per questo mettiamo a disposizione un server OAuth2 che permette al Cittadino di avere una sessione attiva su tutti i client autorizzati.

Import di una pratica tramite API

Questa è la rotta per effettuar il singolo import di Pratica, Documenti e Pagamenti

POST /api/application-lite/import
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{ vedi tracciato di import }

Come è possibile vedere dal tracciato riportato nel paragrafo successivo, è possibile inviare in un solo colpo i dati relativi a pratiche, documenti e pagamenti. Questi tre elementi si considerano correlati tra di loro, quindi, documenti e pagamenti, si intendono relativi alla pratica inviata e verranno relazionati a essa.

Flusso di import delle Applications Lite

Le pratiche verranno identificate in maniera univoca dal campo external_id che rappresenta un identificativo del sistema sorgente, se non presente, verrà creata con i dati passati.

La riconciliazione di richiedente e beneficiario (applicant e beneficiary) verrà effettuata basandosi sul codice fiscale (tax_code). Se nel sistema esiste già un utente con quel codice fiscale, verranno utilizzati i dati già presente nel database ignorando i dati di nome, cognome e contatti che vengono passati. Al contrario, se non presenti, verrà importato un nuovo utente con i dati specificati.

Lo stato della pratica segue il flusso definito nei capitoli precedenti, tuttavia non è stato implementato un controllo stringente sul flusso della pratica, in controllo dello stato della pratica è demandato al sistema sorgente.

Lo stato della pratica regola però le azioni che il cittadino potrà effettuare. Nella sezione links è possibile specificare le azioni permesse sulla pratica è l'URL da invocare al click del cittadino. Se una specifica azione non è specificata non verrà proposta al cittadino. Le azioni verranno proposte al cittadino anche in base allo stato della pratica, per esempio, una pratica in stato inviata non può più essere modificata, quindi, anche se indicata l'azione MODIFY, non verrà comunque proposta.

Tracciato di import

{
  "application": {
    "external_id": "EXT123456",
    "subject": "Richiesta Contributo Startup",
    "applicant": {
      "given_name": "Mario",
      "family_name": "Rossi",
      "identifiers": {
        "tax_code": "RSSMRA85M01H501Z"
      },
      "contacts": {
        "mobile": "+393331234567",
        "email": "[email protected]",
        "pec": "[email protected]"
      }
    },
    "beneficiary": {
      "given_name": "Luca",
      "family_name": "Bianchi",
      "identifiers": {
        "tax_code": "BNCGLC85M01H501Z"
      },
      "contacts": {
        "phone": "+390612345678",
        "email": "[email protected]"
      }
    },
    "status": SUBMITTED,
    "service": {
      "id": "123e4567-e89b-12d3-a456-426614174000"
    },
    "actions": [
      {
        "action": "view",
        "url": "https://sitoweb/other-path?do=view"
      },
      {
        "action": "request_integration",
        "url": "https://sitoweb/other-path?do=request_integration"
      }
    ],
    "process_history": [
      {
          "status": "DRAFT",
          "status_changed_at": "2025-05-19T10:22:54+02:00"
      },
      {
          "status": "SUBMITTED",
          "status_changed_at": "2025-05-19T10:28:14+02:00"
      }
    ]
  },
  "documents": [
    {
      "external_id": "00000000-1111-2222-3333-0334ecfb527d",
      "title": "Richiesta: REALIZZAZIONE TETTOIA DEPOSITO-GARAGE, PERMESSO DI COSTRUIRE",
      "description": "REALIZZAZIONE TETTOIA DEPOSITO-GARAGE, PERMESSO DI COSTRUIRE",
      "short_description": "REALIZZAZIONE TETTOIA DEPOSITO-GARAGE, PERMESSO DI COSTRUIRE",
      "folder": "Comunicazione Inizio Lavori-Saverio MMMSSS98D29H123T",
      "has_organization": null,
      "main_document": "https://localhost/comune-di-bugliano/lang/api/applications/00000000-1111-2222-3333-b167c5038be3/attachments/00000000-1111-2222-3333-50413d60ff4e?version=1",
      "registration_data": {
        "date": "2024-10-04T14:35:15+02:00",
        "document_number": "298O/520/25"
      },
      "type": "application-request",
      "valid_from": null,
      "valid_to": null
    }
  ]
}

Questo è lo JSON Schema della richiesta

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ApplicationLite import request",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "application": {
      "type": "object",
      "properties": {
        "external_id": {
          "type": "string",
          "description": "Identificativo pratica sul sistema esterno",
          "minLength": 1
        },
        "subject": {
          "type": "string",
          "description": "Oggetto della pratica",
          "minLength": 3,
          "maxLength": 255
        },
        "applicant": {
          "$ref": "#/definitions/person",
          "description": "Richiedente della pratica"
        },
        "beneficiary": {
          "anyOf": [
            { "type": "null" },
            { "$ref": "#/definitions/person" }
          ],
          "description": "Beneficiario della pratica"
        },
        "status": {
          "type": "string",
          "description": "Stato della pratica. Valori possibili: DRAFT (Bozza), SUBMITTED (Inviata), PROCESSING (Presa in carico), REQUEST_INTEGRATION (In attesa di integrazioni), APPROVED (Approvata), REJECTED (Rifiutata), WITHDRAW (Ritirata), CANCELLED (Annullata)",
          "enum": [
            "DRAFT",
            "SUBMITTED",
            "PROCESSING",
            "REQUEST_INTEGRATION",
            "APPROVED",
            "REJECTED",
            "WITHDRAW",
            "CANCELLED"
          ]
        },
        "service": {
          "type": "object",
          "description": "Servizio correlato alla pratica",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "description": "Identificativo interno del servizio"
            }
          },
          "required": [
            "id"
          ]
        },
        "actions": {
          "type": "array",
          "description": "Azioni che è possibile effettuare sulla Pratica con relativo URL",
          "items": {
            "type": "object",
            "properties": {
              "action": {
                "type": "string",
                "description": "Azione associata al link (es. view, request integration)",
                "enum": [
                  "view",
                  "request_integration"
                ]
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "URL per accedere alla risorsa"
              }
            },
            "required": [
              "action",
              "url"
            ]
          }
        },
        "process_history": {
          "type": "array",
          "description": "An array representing the workflow of the process. Each entry includes the status and the date and time when the status was changed.",
          "items": {
            "properties": {
              "status": {
                "type": "string",
                "description": "Stato della pratica. Valori possibili: DRAFT (Bozza), SUBMITTED (Inviata), PROCESSING (Presa in carico), REQUEST_INTEGRATION (In attesa di integrazioni), APPROVED (Approvata), REJECTED (Rifiutata), WITHDRAW (Ritirata), CANCELLED (Annullata)",
                "enum": [
                  "DRAFT",
                  "SUBMITTED",
                  "PROCESSING",
                  "REQUEST_INTEGRATION",
                  "APPROVED",
                  "REJECTED",
                  "WITHDRAW",
                  "CANCELLED"
                ]
              },
              "status_changed_at": {
                "type": "string",
                "format": "date-time",
                "description": "Date and time when the status change occurred, formatted using ISO 8601 standard",
                "examples": [
                  "2025-01-20T14:30:00Z",
                  "2025-01-20T14:30:00+02:00"
                ]
              }
            }
          }
        }
      },
      "required": [
        "external_id",
        "subject",
        "applicant",
        "status",
        "service",
        "actions",
        "process_history"
      ]
    },
    "documents": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/document"
      }
    }
  },
  "required": [
    "application"
  ],
  "definitions": {
    "person": {
      "type": "object",
      "properties": {
        "given_name": {
          "type": "string",
          "description": "Nome della persona"
        },
        "family_name": {
          "type": "string",
          "description": "Cognome della persona"
        },
        "identifiers": {
          "type": "object",
          "description": "Sistemi di identificazione digitale della persona (attualmente supportiamo solo il codice fiscale)",
          "properties": {
            "tax_code": {
              "type": "string",
              "description": "Codice fiscale",
              "minLength": 16,
              "maxLength": 16
            },
            "spid_code": {
              "type": "string",
              "description": "Codice SPID"
            },
            "id_eidas": {
              "type": "string",
              "description": "Codice eIDAS"
            },
            "anpr_id": {
              "type": "string",
              "description": "Codice unico dell'anagrafe"
            },
            "inps_id": {
              "type": "string",
              "description": "Codice INPS"
            },
            "io_wallet_id": {
              "type": "string",
              "description": "Codice di PagoPA"
            },
            "email": {
              "type": "string",
              "description": "Email se usata come identificativo utente"
            }
          },
          "required": [ "tax_code" ],
          "anyOf": [
            { "required": [ "tax_code" ] },
            { "required": [ "spid_code" ] },
            { "required": [ "id_eidas" ] },
            { "required": [ "anpr_id" ] },
            { "required": [ "inps_id" ] },
            { "required": [ "io_wallet_id" ] },
            { "required": [ "email" ] }
          ]
        },
        "contacts": {
          "type": "object",
          "properties": {
            "mobile": {
              "type": "string",
              "description": "Numero di cellulare"
            },
            "phone": {
              "type": "string",
              "description": "Numero di telefono"
            },
            "email": {
              "type": "string",
              "description": "Email"
            },
            "pec": {
              "type": "string",
              "description": "PEC"
            }
          }
        }
      },
      "required": [
        "given_name",
        "family_name",
        "identifiers"
      ]
    },
    "document": {
      "title": "Document",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "external_id": { "type": "string" },
        "title": { "type": "string" },
        "description": { "type": "string" },
        "folder": { "type": "string" },
        "main_document": {
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" }
        },
        "type": {
          "type": "string",
          "enum": [
            "application-request",
            "integration-request",
            "integration-response",
            "application-outcome",
            "application-withdraw",
            "application-revocation"
          ]
        },
        "registration_data": {
          "type": "object",
          "properties": {
            "date": { "type": "string" },
            "registry_code": { "type": "string" }
          },
          "required": ["date", "registry_code"]
        },
        "sender": {
          "email": "[email protected]"
        }
      },
      "required": [
        "external_id",
        "title",
        "description",
        "folder",
        "main_document",
        "type"
      ]
    }
  }
}

Gestione dello stato

Il seguente endpoint serve per il cambio di stato di una pratica lite. Permette opzionalmente di inviare un messaggio al richiedente contestualmente al cambio stato

POST /api/application-lite/{id}/status
Content-Type: application/json
Authorization: Bearer {{auth_token}}

{ 
    "new_status": 3000,
    "message": "Messaggio da inviare al richiedente al relativo al cambio stato"
}

Progetti di integrazione ad-hoc con area personale

La modalità push tramite API REST e json rappresenta un modo standard e diffuso di interazione tra sistemi, tuttavia, non sempre è possibile implementarlo. In questi casi possiamo valutare altre modalità di integrazione mediante un progetto personalizzato. Di seguito vengono riportate a scopo esplicativo due modalità di integrazione alternative che è possibile valutare:

Modalità di invio massivo (batch): permette di importare una grande quantità di dati ad ogni invocazione ma non garantisce l'esecuzione in tempo reale. È utile sopratutto per le prime importazioni, o in tutti quei casi in cui non ho necessità di avere uno stato aggiornato in tempo reale. È possibile fornire i dati nei formati CSV o JSON.

Modalità polling: il sistema esterno espone un API da invocare a scadenza regolare che permette al nostro sistema di recuperare gli aggiornamenti. Fondamentale che API dia la possibilità di recuperare gli aggiornamenti incorsi in un intervallo di tempo

Confronto tra le possibili modalità:

Modalità push tramite API
Modalità di invio massivo (batch)
Modalità polling

aggiornamento del dato per il cittadino

in tempo reale

latenza di ore

a intervalli di tempo (minuti/ore)

gestione degli errori latenza per correggere un errore

errori gestiti in tempo reale

errori gestiti alla fine del batch

errore gestito alla fine dell'intervallo

Il path per fare accesso è sempre /$lingua/user
API per ottenere il token JWT passando le credenziali
RFC 3339
Docker Swarm Configs
Docker Swarm Secrets
https://servizi.comune.bugliano.pi.it/lang/api/authservizi.comune.bugliano.pi.it
https://servizi.comune.bugliano.pi.it/lang/api/session-authservizi.comune.bugliano.pi.it
API per ottenere il token JWT come utente loggato
https://servizi.comune.bugliano.pi.it/lang/it/userservizi.comune.bugliano.pi.it
https://servizi.comune.bugliano.pi.it/lang/api/session-authservizi.comune.bugliano.pi.it
l'API per ottenere il token JWT è /api/session-auth

Integrazioni con il Sito Istituzionale

I widget dell'area personale sono una serie di componenti js/html/css che sono inseribili in una qualunque pagina web. In particolare alcuni di questi sono servizi built-in che i comuni devono offrire

Widget generali:

  • Login Box

  • Satisfy

I servizi built-in implementati sono:

  • Richiedi assistenza

  • Segnala problema in città

  • Prenota appuntamento

  • Pagamenti dovuti

I servizi sono presenti in ogni tenant fin dalla sua creazione e non sono modificabili dagli amministratori. Inoltre possono generare pratiche del tutto simili a quelle dei servizi tradizionali, ma il modulo è standard e come amministratore non è possibile modificarlo.

Ogni tenant (ente) punta in automatico alla sua versione widget compatibile in modo automatico

Tutti i widget possono essere caricati con bootstrap-italia 2 se il sito che lo contiene non carica già una versione di bts2 oppure senza

Esempio

Se in una pagina ci sono più widget lo script con le variabili si inserisce una volta

Pagamenti dovuti

Codice da inserire nella pagina per il caricamento del widget, sostituire <domain> con il link dell'area personale dell’ente es: https://servizi.comune.bugliano.pi.it

Prenota appuntamento

Segnalazioni

Il widget restituisce come pagina di default / oppure /# la pagina di invio nuova segnalazione

Sotto la path /#/segnalazioni restituisce la pagina della lista di segnalazioni Sotto la path /#/segnalazioni/{id} restituisce la pagine del dettaglio di una segnalazione

Dalla versione 1.5.0 è stata aggiunta una nuova variabile per limitare la ricerca degli indirizzi tramite zone, configurando un bounding_box (x1,y1,x2,y2) x = longitudine, y = latitudine

Dalla versione 1.6.0 sono state aggiunte due variabili d'ambiante per la configurazione del provider delle mappe basato su nominatim di default il provider è configurato su nominatim.openstreetmap.org.

OC_MAP_SEARCH_PROVIDER configura il provider di ricerca es. ricerca indirizzi da select

All'occorrenza può essere cambiato con un'altro provider custom basato su nominatim

OC_MAP_REVERSE_PROVIDER configura il provider di ricerca inversa, ovvero restituisce l'indirizzo da punto su mappa, es. quando sposto un marker da mappa mi restituisce un indirizzo testuale.

All'occorrenza può essere cambiato con un'altro provider custom basato su nominatim

Configurazione per mostrare il campo Valuta l'importanza del problema Dalla versione 1.14.3

OC_SHOW_SEVERITY_FIELD gestisce l'abilitazione del campo Valuta l'importanza del problema nel form della segnalazione disservizio. Di default il campo è nascosto.

Personalizzazione campi obbligatori per la segnalazione disservizio. Dalla versione 1.16.7 Per rendere obbligatori altri campi del form delle segnalazioni oltre a quelli già previsti di default è possibile popolarewindow.OC_REQUIRED_FIELDS con la lista dei campi che si vuole rendere obbligatori.

lista dei campi che è possibile rendere obbligatori

Dalla versione 1.11.0 è stata aggiunta la variabile per rendere modificabile la lista delle categorie

Categorie con canali esterni (dalla versione 1.12.3)

Per le categorie configurate con link esterni, il widget invita l'utente a proseguire tramite i canali esterni e impedisce la prosecuzione della compilazione e dell'invio della segnalazione.

I link esterni vanno configurati nel json richiamato tramite window.OC_CATEGORIES_URL popolando all'interno della categoria, il valore external_ref (opzionale) con i seguenti campi:

channel

esempio:

Configurazione apertura dettaglio segnalazione (dalla versione 1.16.0)

Al termine della compilazione e di invio di una segnalazione se l'utente si è autenticato è presente un link per andare al dettaglio della segnalazione.

Di default il link punta al dettaglio della segnalazione sull'area personale, in alternativa è possibile configurare l'apertura del dettaglio rimanendo nel widget tramite variabile window.OC_SHOW_DETAIL_ON_WEBSITE=true

Dalla versione 1.17.1 è disponibile il routing senza l'uso dell'#

Nelle versione precedenti alla 1.17.0 il routing del widget funzionava solo tramite # HashRouting

Es.

Aggiungendo la nuova variabile d'ambiante window.OC_BASENAME è possibile trasformare il routing in versione BrowserRouting

Es.

Per configurare la variabile window.OC_BASENAME c'è bisogno del path dove è configurato il widget con eventuali sottodomini se il widget è configurato in path non di primo livello.

Es. https://sito.it -> window.OC_BASENAME='/'

Es. https://sito.it/segnalazioni -> window.OC_BASENAME='/segnalazioni'

Es. https://sito.it/segnalazioni/123 -> window.OC_BASENAME='/segnalazioni/123'

Richiesta assistenza

Sui servizi built-in:

  • Richiedi assistenza

  • Segnala problema in città

  • Prenota appuntamento

è stata aggiunta la possibilità di auto generasi un breadcrums semplice del tipo Home / Segnalazioni dove Home è la homepage del sito e segnalzioni è la route del widget, aggiungendo la variabile:

window.OC_RENDER_BREADCRUMB=true

se la variabile non è presente o ha valore false la breadcrums non verrà abilitata

Pulsante accedi all'area personale

Se si hanno altri tipi di autenticazione oltre a SPID è possibile personalizzare il titolo del popup.

Il pulsante Accedi dopo aver effettuato la login può mostrare un menù a tendina con vari voci che ti ridirigono all'area personale, per abilitarlo omettere o impostare come da variabile sotto a false

Per disabilitarlo e vedere solo al voce do logout impostare la variabile a true

Satisfy Widget

Il widget Satisfy consente di raccogliere feedback da parte degli utenti. È disponibile in diverse versioni ed è consigliato utilizzare sempre l’ultima versione aggiornata per garantire compatibilità e funzionalità ottimali. Tutte le versioni che puntano alla Stanza del Cittadino si aggiornano automaticamente.

Tutte le versioni del widget sono disponibili sulla

Installazione

Versione con Bootstrap Italia 2

Se il sito non utilizza già Bootstrap Italia 2 (BI2), includere la versione del widget che lo incorpora:

Sostituire {version} con la versione desiderata (es. 1.6.0). Questa versione è auto-aggiornante.


Versione senza stili (se BI2 è già incluso nel sito)

Se il sito utilizza già Bootstrap Italia 2, includere solamente il componente e lo script del widget:

Oppure, utilizzando una versione CDN:

data-entrypoints è un attributo obbligatorio e contiene l’UUID della configurazione globale del widget.


Configurazioni avanzate

Endpoint personalizzati (dalla versione 1.5.8)

È possibile specificare endpoint alternativi per i salvataggi e per le chiamate API:

Parametri disponibili

Attributo
Descrizione

Versione con bootstrap-italia 2
"https://servizi.comune.bugliano.pi.it/widgets/bookings/bootstrap-italia@2/js/bookings.js"
"https://servizi.comune.bugliano.pi.it/widgets/bookings/bootstrap-italia@2/css/bookings.css"

Versione senza bootstrap-italia 2
"https://servizi.comune.bugliano.pi.it/widgets/bookings/js/bookings.js"
"https://servizi.comune.bugliano.pi.it/widgets/bookings/css/bookings.css"
<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
window.OC_AUTH_URL = 'https://servizi.comune.bugliano.pi.it/lang/login';
</script>
<div id="oc-paymentsDue"></div>
<script>
   window.OC_BASE_URL = '<domain>/lang';
   window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
   window.OC_AUTH_URL = '<domain>/lang/login';
   window.OC_CHECKOUT_URL = 'https://api.stanzadelcittadino.it/pagopa/checkout';
</script>
<link rel="stylesheet" href="<domain>/widgets/payments-due/css/paymentsDue.css" />
<script src="<domain>/widgets/payments-due/js/paymentsDue.js"></script>
<div id="oc-bookings"></div>
<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
window.OC_AUTH_URL = 'https://servizi.comune.bugliano.pi.it/lang/login';
</script>
<link rel="stylesheet" href="https://servizi.comune.bugliano.pi.it/widgets/bookings/css/bookings.css" />
<script src="https://servizi.comune.bugliano.pi.it/widgets/bookings/js/bookings.js"></script>
<div id="oc-inefficiencies"></div>
<link rel="stylesheet" type="text/css" href="https://servizi.comune.bugliano.pi.it/widgets/inefficiencies/css/inefficiencies.css" /> 

<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
window.OC_AUTH_URL = 'https://servizi.comune.bugliano.pi.it/lang/login';
</script>

<script src="https://servizi.comune.bugliano.pi.it/widgets/inefficiencies/js/inefficiencies.js"></script>
 window.OC_BB = '8.66272,44.52197,9.09805,44.37590';
window.OC_MAP_SEARCH_PROVIDER = 'nominatim.openstreetmap.org';
window.OC_MAP_REVERSE_PROVIDER = 'nominatim.openstreetmap.org';
window.OC_SHOW_SEVERITY_FIELD=true;
window.OC_REQUIRED_FIELDS=['address','type','fiscal_code','severity']

nome

descrizione

type

Tipo di disservizio (categoria)

address

Luogo (indirizzo)

fiscal_code

Codice fiscale

severity

Valuta l'importanza del problema

window.OC_CATEGORIES_URL = 'https://static.opencityitalia.it/widgets/inefficiencies/data/categories-multi.json';

nome

descrizione

title

titolo

text

descrizione

channels

[channel]

nome

descrizione

type

web, email, phone, ios o android

href

link href

label

link label

...
{
 "label": "Manutenzione",
 "value": "8f06c823-e476-4ec1-a208-6fd4956e5a56",
 "external_ref": {
   "title": "Contatta questo contatto esterno",
   "text": "Per la manutenzione di:\n \u2022 Strade \n \u2022 Marciapiedi...",
   "channels": [
     {
       "type": "web",
       "label": "vai al sito www.google.it",
       "href": "https://www.google.it"
      },
      {
        "type": "email",
        "label": "Scrivi un'email a [email protected]",
        "href": "mailto:[email protected]"
      },
      {
        "type": "phone",
        "label": "Telefona al numero verde 12345789",
        "href": "tel://12345789"
      },
      {
        "type": "ios",
        "label": "Scarica l'app dedicata",
        "href": "https://apple-store.com/mia-app"
      },
      {
        "type": "android",
        "label": "Scarica l'app dedicata",
        "href": "https://play.google.com/mia-app"
      }
    ]
  }
},
Versione con bootstrap-italia 2
<div id="oc-helpdesk"></div>
<link rel="stylesheet" href="https://servizi.comune.bugliano.pi.it/widgets/helpdesk/bootstrap-italia@2/css/helpdesk.css" />
<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
window.OC_AUTHURL = 'https://servizi.comune.bugliano.pi.it/lang/login';
</script>
<script src="https://servizi.comune.bugliano.pi.it/widgets/helpdesk/bootstrap-italia@2/js/helpdesk.js"></script>
Versione senza bootstrap-italia 2
<div id="oc-helpdesk"></div>
<link rel="stylesheet" href="https://servizi.comune.bugliano.pi.it/widgets/helpdesk/css/helpdesk.css" />
<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_PRIVACY_URL = 'https://www.comune.bugliano.pi.it/Privacy';
window.OC_AUTHURL = 'https://servizi.comune.bugliano.pi.it/lang/login';
</script>
<script src="https://servizi.comune.bugliano.pi.it/widgets/helpdesk/js/helpdesk.js"></script>
<div id="oc-login-box"></div>

<script>
window.OC_BASE_URL = 'https://servizi.comune.bugliano.pi.it/lang';
window.OC_AUTH_URL = 'https://servizi.comune.bugliano.pi.it/lang/login';
window.OC_SPID_BUTTON = 'true' // da usare se si vuole utilizzare la versione senza popup login spid
window.OC_AUTH_LABEL = 'SPID/CNS/EIDAS/CIE'
</script>

<script src="https://servizi.comune.bugliano.pi.it/widgets/login-box/bootstrap-italia@2/js/login-box.js"></script>
window.OC_AUTH_LABEL = 'Testo personalizzato'
window.OC_USER_MENU_DISABLED = false
window.OC_USER_MENU_DISABLED = true
<script src="https://static.opencityitalia.it/widgets/satisfy/version/{version}/bootstrap-italia@2/js/satisfy.js"></script>
<app-widget data-entrypoints="{UUID}"></app-widget>
<script src="https://servizi.comune.bugliano.pi.it/widgets/satisfy/js/satisfy.js"></script>
<app-widget data-entrypoints="{UUID}"></app-widget>
<script src="https://static.opencityitalia.it/widgets/satisfy/version/{version}/js/satisfy.js"></script>
<app-widget 
  data-entrypoints="{UUID}"
  data-api-ratings="https://satisfy.opencityitalia.it/api/v1/ratings"
  data-api-configs="https://satisfy.opencityitalia.it/v1/graphql"
></app-widget>

<script src="https://servizi.comune.bugliano.pi.it/widgets/satisfy/bootstrap-italia@2/js/satisfy.js"></script>

data-entrypoints

Obbligatorio. UUID della configurazione del widget.

data-api-ratings

URL personalizzato per le richieste POST di salvataggio dei feedback. Se non specificato, usa il default https://satisfy.opencityitalia.it/api/v1/ratings.

data-api-configs

URL personalizzato per le chiamate API GraphQL. Se non specificato, usa https://satisfy.opencityitalia.it/v1/graphql.

https://www.comune-qa.bugliano.pi.it/segnala_disservizio#/segnala_disservizio
https://www.comune-qa.bugliano.pi.it/segnala_disservizio/segnala_disservizio
pagina
configurazione del tipo di accesso

Integrazione tecnica tra OpenCity, PDND Connector e PDND

Introduzione

Il pdnd-connector è un microservizio che funge da intermediario tra OpenCity e la piattaforma PDND, gestendo la complessità dell'autenticazione (ottenimento del voucher) e la comunicazione con gli enti erogatori di e-service (come ANPR e INPS). Espone delle API per OpenCity che consentono la fruizione e la validazione dei dati.

Diagramma di sequenza dell’integrazione

Glossario

Token JWT di Opencity

E' il token che collegato all'utenza del cittadino. Il contenuto è il seguente

  • username (codice fiscale utente)

  • roles (USER_ROLE)

  • exp (data scadenza)

  • tenant_id

Audit assertion

L'audit assertion è un token JWT che viene utilizzato per tracciare e verificare le operazioni effettuate sui servizi dell'ente erogatore (es. ANPR). Esso, oltre alle solite info contenute in un token JWT, è composto anche dalle seguenti informazioni di contesto:

  • aud (audience): destinatario del token (il servizio dell'ente erogatore)

  • purposeId: identificativo dello scopo della richiesta

  • iss (issuer): identificativo del client che fa la richiesta

  • sub (subject): identificativo del client

  • userID: identificativo dell'utente che fa la richiesta

  • userLocation: codice IPA dell'ente

  • LoA (Level of Assurance): livello di autenticazione

L'audit assertion viene utilizzato in due modi principali:

  1. Come header Agid-JWT-TrackingEvidence nelle richieste HTTP verso i servizi dell'ente erogatore

  2. Come base per generare il client assertion (viene fatto l'hash SHA-256 dell'audit assertion)

Questo meccanismo serve a:

  • Tracciare chi ha fatto la richiesta

  • Verificare l'autenticità della richiesta

  • Garantire la non ripudiabilità delle operazioni

  • Mantenere un audit trail delle operazioni effettuate

Il token viene firmato usando una chiave privata RSA del client PDND per garantire l'autenticità e l'integrità dei dati.

Client assertion

Il client assertion è un token JWT che viene generato dopo l'audit assertion e serve in fase di richiesta dati dall'ente erogatore come prova di autenticazione del client verso il servizio di autenticazione PDND.

Signature

E' una firma digitale che viene utilizzata per garantire che i dati inviati dal cittadino all'invio del modulo non siano stati alterati rispetto a quando sono stati restituiti al cittadino dal pdnd-connector dopo averli richiesti al servizio dell'ente erogatore. La firma contiene:

  • Digest SHA256 del payload

  • Headers firmati (Content-Type, Digest)

Diagramma di interazione - Happy Path

Diagramma di interazione - Casi di errore

Descrizione Flusso di Fruizione

  1. Autenticazione Iniziale Il flusso ha inizio quando un cittadino si autentica su Opencity attraverso il proprio sistema di identità digitale (SPID, CIE, ecc.). Questa autenticazione genera un JWT token che contiene:

    • Username (codice fiscale)

    • Tenant ID

    • Ruoli

    • Timestamp di scadenza

  2. Avvio della Richiesta Quando il cittadino inizia a compilare un modulo che utilizza un Nested Form abilitato PDND, il PDND Connector riceve una richiesta che include tre elementi fondamentali:

    1. Config ID (identifica la configurazione del servizio)

    2. Codice fiscale

    3. Format (formato di output richiesto)

  3. Configurazione del Connector Il PDND Connector, che funge da intermediario sicuro tra Opencity e la PDND, utilizza le configurazioni precedentemente salvate per preparare la richiesta. Queste configurazioni includono

    1. Client ID

    2. KID (Key ID)

    3. Chiave privata RSA

    4. Service Audience

    5. Purpose ID

    6. Endpoint del servizio

    Il Connector gestisce in modo sicuro sia le chiavi pubbliche che private, esponendo solo le chiavi pubbliche attraverso le API.

  4. Autenticazione a Due Livelli Il Connector inizia un processo di autenticazione a due livelli con la PDND:

    1. Primo Livello (Audit Assertion): Un token JWT firmato con la chiave privata del client, contenente: iat, exp, nbf, aud, purposeId, iss, sub, jti, dnonce, userID, userLocation, LoA. Il token ha una validità di 60 minuti.

    2. Secondo Livello (Client Assertion): Un token JWT che include l'hash SHA-256 dell'Audit Assertion, esso contiene: iss, aud, jti, iat, nbf, exp, purposeId, sub, digest. Il token ha una validità di 24 ore.

  5. Ottenimento del Voucher Il Client Assertion viene utilizzato per ottenere dalla PDND il Voucher di accesso al servizio. Il Voucher rappresenta l'autorizzazione effettiva ad accedere al servizio richiesto all'ente erogatore. Per motivi di compliance legale, il Voucher viene salvato localmente o su S3, creando un audit trail delle operazioni effettuate.

  6. Chiamata al Servizio Erogatore Con il Voucher in mano, il PDND Connector contatta direttamente l'API dell'Ente Erogatore (come ANPR). La richiesta viene arricchita con diversi header di sicurezza:

    • Header Authorization: Bearer <voucher>

    • Header Agid-JWT-TrackingEvidence: <audit_assertion>

    • Header Digest: SHA-256=<digest64>

    • Header Agid-JWT-Signature: <signature>

    Il payload contiene i parametri della richiesta (es. codice fiscale)

  7. Elaborazione della Risposta Quando l'Ente Erogatore risponde, il PDND Connector:

    1. Elabora la risposta per renderla compatibile con il formato atteso dal Nested Form

    2. Aggiunge una sezione "meta" che include una firma digitale dei dati ricevuti

    3. La firma viene generata usando SHA-256 per l'hashing dei dati e la chiave privata RSAper la firma

  8. Verifica e Restituzione La risposta finale, che include sia i dati formattati che la sezione meta con la firma, viene restituita all'area personale. Il client può verificare la firma usando la chiave pubblica, garantendo che:

    1. I dati non siano stati modificati durante la trasmissione

    2. I dati provengano effettivamente dalla fonte dichiarata

Descrizione Flusso di Validazione

  1. Ricezione dei Dati Il flusso di validazione inizia quando l'area personale (backend) riceve i dati e la firma digitale dalla chiamata GET al PDND Connector. Questi dati includono:

    1. Il campo data contenente le informazioni richieste (es. dati di residenza o stato famiglia)

    2. Il campo meta che contiene la firma digitale (signature) generata dal Connector

  2. Preparazione della Richiesta di Validazione L'area personale prepara una richiesta di validazione che include:

    1. I dati da validare (data)

    2. La firma digitale (signature)

    3. Il Config ID che identifica la configurazione del servizio

    4. Il formato dei dati

    5. Il codice fiscale dell'utente

  3. Invio al Connector La richiesta di validazione viene inviata al PDND Connector, che funge da validatore ufficiale dei dati. Questo passaggio è cruciale perché il Connector è l'unico componente che possiede le chiavi necessarie per verificare l'autenticità della firma.

  4. Caricamento della Chiave Pubblica Il Connector:

    1. Carica la configurazione del servizio usando il Config ID

    2. Recupera il Client ID associato

    3. Carica la chiave pubblica RSA del client Questa chiave pubblica è essenziale per la verifica della firma, poiché è la controparte della chiave privata usata per firmare i dati.

  5. Verifica della Firma Il Connector esegue la verifica della firma attraverso un processo in più fasi:

    1. Serializza i dati in formato JSON

    2. Calcola l'hash SHA-256 dei dati

    3. Verifica la firma usando la chiave pubblica RSA

    4. Confronta il risultato con la firma ricevuta

  6. Gestione del Risultato Il Connector gestisce il risultato della verifica:

    1. Se la verifica ha successo, significa che i dati non sono stati alterati

    2. Se la verifica fallisce, significa che i dati potrebbero essere stati modificati

    In caso di errori durante il processo di verifica, viene restituito un errore specifico

  7. Risposta di Validazione Il Connector risponde con un semplice booleano:

    1. true se la firma è valida e i dati non sono stati alterati

    2. false se la firma non è valida o i dati sono stati modificati

    Questo flusso di validazione è fondamentale perché:

    1. Garantisce l'integrità dei dati tra il momento in cui vengono ottenuti dal Connector e il momento in cui vengono utilizzati da Opencity

    2. Previene la manipolazione dei dati durante la trasmissione

    3. Fornisce una prova verificabile dell'autenticità dei dati

    4. Contribuisce alla creazione di un audit trail completo delle operazioni

Validazione dei Dati PDND

Contesto della Validazione

La validazione dei dati PDND avviene in un contesto specifico: quando un utente compila un form in OpenCity che include campi pre-compilati con dati provenienti dalla PDND (nested form con pdndField: true). Questi dati, essendo certificati dalla PDND, devono mantenere la loro integrità durante l'intero processo di compilazione del form.

Scopo della Validazione

La validazione serve a garantire che i dati certificati dalla PDND non siano stati alterati dopo essere stati forniti dal pdnd-connector. Questo è fondamentale perché:

  • Mantiene l'affidabilità delle informazioni certificate

  • Previene la manipolazione manuale dei dati da parte dell'utente

  • Garantisce la tracciabilità e la non ripudiabilità delle informazioni

Hai ragione, ecco una versione più dettagliata del processo di validazione della firma digitale, con particolare attenzione al collegamento tra la firma generata in fase di fruizione e quella verificata in fase di validazione:

Processo di Validazione della Firma Digitale

Generazione della Firma (Fase di Fruizione)

Quando il pdnd-connector restituisce i dati in risposta a una richiesta GET, genera una firma digitale attraverso questi passaggi:

  1. Preparazione dei Dati:

    • I dati vengono serializzati in JSON

    • L'ordine dei campi è importante per la consistenza della firma

  2. Generazione della Firma:

    • Viene calcolato l'hash SHA-256 dei dati JSON

    • La firma viene generata usando la chiave privata RSA

    • La firma viene codificata in Base64

    • Meccanismo di Firma

      • Algoritmo di Hash: SHA-256

      • Algoritmo di Firma: RSA con padding PKCS1v15

      • Codifica: Base64

      • Posizione: Campo meta.signature nella risposta JSO

  3. Inclusione nella risposta:

    • La firma viene inclusa nel campo meta.signature

    • Vengono aggiunti altri metadati per il contesto

Verifica della Firma (Fase di Validazione)

Quando arriva una richiesta di validazione, il pdnd-connector verifica che i dati non siano stati alterati:

  1. Ricezione della Richiesta:

    • Riceve i dati da validare

    • Riceve la firma originale nel campo meta.signature

  2. Preparazione per la Verifica:

    • I dati ricevuti vengono serializzati in JSON

    • È fondamentale che la serializzazione avvenga nello stesso modo della fase di fruizione

  3. Verifica della Firma:

    • Viene decodificata la firma Base64

    • Viene calcolato l'hash SHA-256 dei dati ricevuti

    • La firma viene verificata usando la chiave pubblica RSA

  4. Risposta di Validazione:

    • Se la verifica fallisce, restituisce false

    • Se la verifica ha successo, restituisce true

Collegamento tra Fruizione e Validazione

È cruciale comprendere che:

  1. La firma verificata in fase di validazione deve essere esattamente la stessa generata in fase di fruizione

  2. Qualsiasi modifica ai dati, anche minima, causerà il fallimento della validazione

  3. Il processo di serializzazione JSON deve essere identico in entrambe le fasi

Esempio di Flusso Completo

Questo processo garantisce che:

  • I dati non siano stati alterati durante la trasmissione

  • La firma sia autentica e proveniente dal pdnd-connector

  • L'integrità dei dati sia mantenuta durante l'intero ciclo di vita

Regole di Conservazione

Voucher PDND

  • Durata: Conservazione obbligatoria per 24 mesi

  • Scopo: Garantire la tracciabilità delle operazioni

  • Base Legale: Requisito di compliance

Dati di Validazione

  • I dati originali e le firme vengono conservati per:

    • Audit trail

    • Verifica post-facto

    • Compliance normativa

Aspetti di Sicurezza e Privacy

Analisi del Rischio

  • L'ente fruitore deve dichiarare:

    • Finalità legali dell'accesso

    • Tipologia di dati personali

    • Basi giuridiche del trattamento

    • Misure di sicurezza adottate

Responsabilità

  • L'ente è titolare del trattamento

  • Deve fornire informative privacy appropriate

  • Deve garantire la sicurezza dei dati

Questa struttura rende più chiara la separazione tra i vari aspetti della validazione, evidenziando il contesto, lo scopo, il processo e le regole di conservazione in modo distinto e logico.

Iris Proxy - Swagger UI

API v1

Configurazione tenant

L'admin, dall'interfaccia di configurazione dei pagamenti della Stanza del Cittadino compila la configurazione mediante una form, il cui json schema è servito dall'API /tenants/schema

Lo schema della form sopra riportata è il seguente

Premendo poi il bottone Salva, viene eseguita una POST /tenants servita dal proxy, con payload

Per modificare una configurazione esistente, il proxy serve l'API PUT /tenants/{tenant_id} e PATCH /tenants/{tenant_id}

Per eliminare una configurazione esistente, il proxy serve l'API DELETE /tenants/{tenant_id} . In questo caso l'eliminazione è una soft-delete, ovvero la configurazione viene semplicemente disattivata settando il parametro active a false ed eliminando la configurazione dalla memoria ma non dallo storage.

Configurazione servizio

L'admin, dall'interfaccia di configurazione dei pagamenti per un servizio compila la configurazione mediante una form, il cui json schema è servito dall'API /services/schema

Lo schema della form soprariportata è il seguente

Premendo poi il bottone Salva, viene eseguita una POST /services servita dal proxy, con payload

Per modificare una configurazione esistente, il proxy serve l'API PUT /services/{service_id} e PATCH /services/{service_id}

Per eliminare una configurazione esistente, il proxy serve l'API DELETE /services/{service_id} . In questo caso l'eliminazione è una soft-delete, ovvero la configurazione viene semplicemente disattivata settando il parametro active a false.

Alberatura configurazioni sullo storage

Le configurazioni di tenant e servizi vengono salvate con la seguente alberatura

root |____tenant_id_1 | |____tenant.json | |____service_id_1.json | |____service_id_2.json | |____..... | |____service_id_n.json |____tenant_id_2 | |____tenant.json | |____service_id_1.json | |____service_id_2.json | |____..... | |____service_id_n.json |____tenant_id_n |____tenant.json |____service_id_1.json |____service_id_2.json |____..... |____service_id_n.json

Protocol Proxy: Specifiche Implementative

Requisiti Generali - controllare pagina standard e convenzioni

  • Ogni microservizio deve rispettare gli

  • Ogni proxy deve soddisfare i requisiti suggeriti dal

  • Esposizione di un endpoint/status per l'healthcheck: esso deve essere restituire 200 se il sistema è "healthy" e 503 in caso si presentino un problemi di connessione con kafka

  • Esposizione delle metriche mediante l'endpoint /metrics con Prometheus utilizzando

  • Terminazione pulita del servizio (timeout di 15 secondi)

  • Esposizione di un endpoint /docs per la consultazione della documentazione delle API esposte dal servizio secondo lo standard OpenAPI

  • Integrazione con per l'error reporting

  • Il sistema di logging deve rispettare il formato Apache e descrivere nei tre livelli debug, error e info. Ogni log, quando possibile, deve loggare il remote_id e l'id del documento processato

  • Il README deve seguire le

  • il nome del progetto e del servizio devono essere nella seguente forma : "Protocol Proxy <Nome del Protocollo Specifico>"

  • Inserire il file .gitlab-ci.yml riportando in esso pipeline

Metriche in dettaglio

Il Protocol Proxy deve esporre le seguenti metriche:

Metrica
Labels
Descrizione

Requisiti normativi

  • Avere una specifica in formato OpenAPI v3

  • Rispettare la : in particolare per quanto riguarda

    • (4.2)

    • (4.3) optando per la scelta di snake_case per i nomi degli attributi

    • logging (4.4)

    • risultare valido nel

Documentazione API di Esempio - Swagger

Specifiche funzionali

  • Il servizio deve essere multi tentant e multi servizio: deve quindi essere in grado di gestire le confiugurazioni per più tenant a cui possono essere attribuiti più servizi con il corrente sistema di protocollazione.

  • Ogni richiesta sia essa appartenente alla fase 1 o alla fase 2 descritte nella sezione "" devono essere trattate in maniera atomica.

  • Lo storage deve essere compatibile con Aws s3, Azure e local file system.

Configurazione del microservizio (variabili d'ambiente)

Il servizio deve essere configurabile attraverso le seguenti variabili d'ambiente.

Se le varibili senza valore di default non vengono settate, il servizio deve notificare la mancata impostazione attraverso un log di livello error e terminare in maniera pulita la propria esecuzione.

Nome
Default
Descrizione

Test

Protocol Proxy Tests Checklist

Se si sta facendo il deploy di un nuovo microservizio per la prima volta o si sta aggiungendo una nuova API o una nuova pagina a una interfaccia esistente, è necessario aggiungere alcuni controlli di qualità minima prima del rilascio.

Controlli

  • Test automatici delle API nella CI (usare , un esempio di utilizzo e un esempio di integrazione nelle CI)

  • Test flusso standard

    • Inserire la configurazione del tenant

    • Inserire la configurazione del servizio

    • Inserire un esempio di documento non protocollato nel topic documents e verificare che venga correttamente protocollato

    • Inserire un esempio di documento protocollato nel topic documents e verificare che venga ignorato

    • Inserire un esempio di documento non protocollato nel topic documents per cui non esiste una configurazione di tenant e/o di servizio e verificare che venga ignorato

  • Test flusso di errore

    • Modificare la configurazione del servizio in modo che sia errata (mettendo ad esempio credenziali errate)

    • Inserire un esempio di documento non protocollato nel topic documents e verificare che, a seguito del fallimento, venga prodotto un evento nel topic di retry

    • Correggere la configurazione del servizio

    • Inserire il documento prodotto nel topic di retry all'interno del topic documents e verificare che venga correttamente protocollato

{
    "display": "form",
    "components": [
        {
            "label": "UUID del Tenant",
            "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "spellcheck": false,
            "attributes": {
                "readonly": "readonly"
            },
            "hidden": true,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "id",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Nome dell'ente",
            "placeholder": "Comune di Bugliano",
            "tableView": true,
            "validate": {
                "required": true
            },
            "key": "name",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Codice IPA dell'ente",
            "placeholder": "A000",
            "description": "Codice (tutto maiuscolo) assegnato all'Ente nel registro <a href='http://www.indicepa.gov.it' target='__blank'>indice PA</a>",
            "spellcheck": false,
            "tableView": true,
            "validate": {
                "required": true
            },
            "key": "IPA_code",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Password",
            "placeholder": "************",
            "description": "Password assegnata da MyPay all’Ente",
            "spellcheck": false,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "password",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Password carrello dovuti",
            "placeholder": "************",
            "description": "Password assegnata all’ente creditore. Tale password va richiesta all’amministratore dell’ente creditore.<br>ATTENZIONE: questo dato NON coincide con la password (principale) dell’ente.",
            "spellcheck": false,
            "tableView": true,
            "key": "cart_password",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Codice applicazione",
            "spellcheck": false,
            "tableView": true,
            "key": "application_code",
            "type": "textfield",
            "input": true,
            "hidden": true,
            "defaultValue": "SDC"
        },
        {
            "label": "Abilitato",
            "key": "active",
            "type": "checkbox",
            "input": true,
            "hidden": true,
            "defaultValue": true
        },
        {
            "label": "Salva",
            "showValidations": false,
            "tableView": false,
            "key": "submit",
            "type": "button",
            "input": true
        }
    ]
}
{
  "name": "Comune di Bugliano",
  "IPA_code": "P_TN",
  "password": "4CKSILO7FQPZ",
  "cart_password": "",
  "submit": true,
  "id": "60e35f02-1509-408c-b101-3b1a28109329",
  "application_code": "",
  "active": true
}
{
    "display": "form",
    "components": [
        {
            "label": "UUID del Servizio",
            "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "spellcheck": false,
            "attributes": {
                "readonly": "readonly"
            },
            "hidden": true,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "id",
            "type": "textfield",
            "input": true
        },
        {
            "label": "UUID del Tenant",
            "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "spellcheck": false,
            "attributes": {
                "readonly": "readonly"
            },
            "hidden": true,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "tenant_id",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Scegli il tipo di pagamento",
            "widget": "choicesjs",
            "tableView": true,
            "data": {
                "values": [
                    {
                        "label": "PagoPA",
                        "value": "pagopa"
                    },
                    {
                        "label": "Marca da Bollo Digitale",
                        "value": "stamp"
                    },
                    {
                        "label": "PagoPA + Marca da Bollo Digitale",
                        "value": "pagopa_and_stamp"
                    }
                ]
            },
            "validate": {
                "required": true
            },
            "key": "payment_type",
            "type": "select",
            "input": true,
            "defaultValue": "pagopa"
        },
        {
            "label": "Tipo dovuto",
            "placeholder": "TD_CIE",
            "description": "Riferimento alla tipologia del dovuto secondo la classificazione data dal beneficiario.",
            "applyMaskOn": "change",
            "spellcheck": false,
            "tableView": true,
            "validate": {
                "required": true,
                "maxLength": 64
            },
            "key": "management_id",
            "customConditional": "show = data.payment_type == 'pagopa' || data.payment_type == 'pagopa_and_stamp'",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Dati specifici riscossione",
            "placeholder": "9/3300.1",
            "description": "Rappresenta l’indicazione dell’imputazione della specifica entrata ed è così articolato:<br><b>TIPO_CONTABILITA / CODICE_CONTABILITA</b>, dove TIPO_CONTABILITA ha il seguente significato:<ul><li>0 (Capitolo e articolo di Entrata del Bilancio dello Stato)</li><li>1 (Numero della contabilità speciale)</li><li>2 (Codice SIOPE)</li><li>9 (Altro codice ad uso dell’amministrazione)</li></ul>",
            "applyMaskOn": "change",
            "spellcheck": false,
            "tableView": false,
            "defaultValue": "9/3300.1",
            "validate": {
                "required": true,
                "maxLength": 140
            },
            "key": "collection_data",
            "customConditional": "show = data.payment_type == 'pagopa' || data.payment_type == 'pagopa_and_stamp'",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Abilitato",
            "key": "active",
            "type": "checkbox",
            "input": true,
            "hidden": true,
            "defaultValue": true,
            "tableView": false
        },
        {
            "label": "Bilancio",
            "tableView": false,
            "defaultValue": true,
            "key": "split_enable",
            "customConditional": "show = data.payment_type == 'pagopa' || data.payment_type == 'pagopa_and_stamp'",
            "type": "checkbox",
            "input": true
        },
        {
            "label": "Bilancio",
            "reorder": false,
            "addAnotherPosition": "bottom",
            "layoutFixed": false,
            "enableRowGroups": false,
            "initEmpty": true,
            "tableView": false,
            "defaultValue": [
                {
                    "split_id": "",
                    "split_budget_chapter": "",
                    "split_office_code": "",
                    "split_assessment": "",
                    "split_amount": ""
                }
            ],
            "key": "split",
            "conditional": {
                "show": true,
                "when": "split_enable",
                "eq": "true"
            },
            "customConditional": "show = (data.payment_type == 'pagopa' || data.payment_type == 'pagopa_and_stamp') && data.split_enable",
            "type": "datagrid",
            "input": true,
            "components": [
                {
                    "label": "ID",
                    "description": "Identificativo univoco della voce di bilancio. Testo libero",
                    "placeholder": "c_1",
                    "tableView": true,
                    "validate": {
                        "required": true
                    },
                    "key": "split_id",
                    "type": "textfield",
                    "input": true
                },
                {
                    "label": "Capitolo",
                    "placeholder": "Capitolo di bilancio c1",
                    "validate": {
                        "required": true
                    },
                    "tableView": true,
                    "key": "split_budget_chapter",
                    "type": "textfield",
                    "input": true
                },
                {
                    "label": "Ufficio",
                    "placeholder": "Codice ufficio bilancio c1",
                    "tableView": true,
                    "key": "split_office_code",
                    "type": "textfield",
                    "input": true
                },
                {
                    "label": "Accertamento",
                    "placeholder": "Accertamento c1",
                    "tableView": true,
                    "key": "split_assessment",
                    "type": "textfield",
                    "input": true
                },
                {
                    "label": "Importo",
                    "description": "Importo della voce di bilancio. NB: La somma degli importi delle voci DEVE equivalere all'importo totale",
                    "placeholder": "16.00",
                    "validate": {
                        "required": true
                    },
                    "mask": false,
                    "tableView": false,
                    "delimiter": false,
                    "requireDecimal": false,
                    "inputFormat": "plain",
                    "truncateMultipleSpaces": false,
                    "key": "split_amount",
                    "type": "number",
                    "input": true
                }
            ]
        },
        {
            "label": "hidden",
            "calculateValue": "if (!data.split || data.split == 'undefined') {\n  data.split = []\n} else if (typeof data.split==='object' && Object.keys(data.split).length === 0) {\n  data.split = [];\n}\n\nif (!data.stamps || data.stamps == 'undefined') {\n  data.stamps = []\n} else if (typeof data.stamps==='object' && Object.keys(data.stamps).length === 0) {\n  data.stamps = [];\n}\n\nfor (i = 0; i < data.stamps.length; i++) {\n  if (data.stamps[i].id) {\n    data.stamps[i].amount = submission.metadata.selectData.stamps[i].id.label\n  } else {\n    data.stamps[i].amount = \"\"\n  }\n} ",
            "key": "hidden",
            "type": "hidden",
            "input": true,
            "tableView": false
        },
        {
            "label": "Informazioni Pagamento MBD",
            "attrs": [
                {
                    "attr": "",
                    "value": ""
                }
            ],
            "content": "<b>NOTA BENE:</b> Nel caso in cui si venga configurato <b>solamente</b> il pagamento la marca da bollo digitale, il valore dell'importo e/o del campo payment_amount nel modulo saranno ignorati",
            "refreshOnChange": false,
            "key": "stamp_info",
            "conditional": {
                "show": true,
                "when": "payment_type",
                "eq": "stamp"
            },
            "type": "htmlelement",
            "input": false,
            "tableView": false
        },
        {
            "label": "Marca da Bollo Digitale",
            "reorder": false,
            "addAnotherPosition": "bottom",
            "layoutFixed": false,
            "enableRowGroups": false,
            "initEmpty": true,
            "tableView": false,
            "defaultValue": [
                {
                    "id": "01",
                    "amount": "16.00",
                    "collection_data": "9/3300.1"
                }
            ],
            "validate": {
                "maxLength": "5",
                "custom": "valid = !(data.stamps.length > 4 && data.payment_type == 'pagopa_and_stamp') ? true : 'Puoi inserire un massimo di 4 marche da bollo'"
            },
            "key": "stamps",
            "customConditional": "show = data.payment_type == 'stamp' || data.payment_type == 'pagopa_and_stamp'",
            "type": "datagrid",
            "input": true,
            "components": [
                {
                    "label": "Taglio",
                    "widget": "choicesjs",
                    "tableView": true,
                    "data": {
                        "values": [
                            {
                                "label": "16.00",
                                "value": "01"
                            }
                        ]
                    },
                    "template": "<span>{{ item.label }}</span> €",
                    "validate": {
                        "required": true
                    },
                    "key": "id",
                    "type": "select",
                    "input": true
                },
                {
                    "label": "Dati specifici riscossione",
                    "placeholder": "9/3300.1",
                    "description": "Rappresenta l’indicazione dell’imputazione della specifica entrata ed è così articolato:<br><b>TIPO_CONTABILITA / CODICE_CONTABILITA</b>, dove TIPO_CONTABILITA ha il seguente significato:<ul><li>0 (Capitolo e articolo di Entrata del Bilancio dello Stato)</li><li>1 (Numero della contabilità speciale)</li><li>2 (Codice SIOPE)</li><li>9 (Altro codice ad uso dell’amministrazione)</li></ul>",
                    "applyMaskOn": "change",
                    "spellcheck": false,
                    "tableView": false,
                    "defaultValue": "9/3300.1",
                    "validate": {
                        "required": true,
                        "maxLength": 140
                    },
                    "key": "collection_data",
                    "type": "textfield",
                    "input": true
                }
            ]
        },
        {
            "label": "Salva",
            "showValidations": false,
            "tableView": false,
            "key": "submit",
            "type": "button",
            "input": true
        }
    ]
}
{
  "payment_type": "pagopa_and_stamp",
  "management_id": "COSAP_TOSAP",
  "collection_data": "9/0301109AP/",
  "split_enable": true,
  "split": [
    {
      "split_id": "c_1",
      "split_budget_chapter": "Cap1",
      "split_office_code": "Uff1",
      "split_assessment": "Acc1",
      "split_amount": 1
    }
  ],
  "hidden": "",
  "submit": true,
  "id": "e64a3bd1-0096-4b8e-8676-e53f7d6b4b9a",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "active": true,
  "stamps": [
    {
      "id": "01",
      "amount": "16.00",
      "collection_data": "9/3300.1"
    }
  ]
}

oc_document_validation_errors_total

cluster, env, app_name

la metrica deve misurare gli errori di validazione sull'evento letto (es. il transmission_type contiene un valore diverso da Inbound o Outbound)

oc_api_requests_total

cluster, env, method, app_name, status_code

la metrica deve monitorare le chiamate http indicandone lo status code

oc_document_success_events_total

cluster, env, app_name

la metrica deve misurare solo gli eventi per cui il proxy ha una configurazione e che sono stati processati con successo

oc_document_failed_events_total

cluster, env, app_name

la metrica deve misurare gli eventi validi di cui però è fallito il processing per qualsiasi motivo (escluso il caso in cui non esiste una configurazione per esso)

oc_document_provider_errors_total

cluster, env, app_name

la metrica deve misurare gli eventi validi di cui però è fallito il processing a causa di un errore sul provider

oc_document_internal_errors_total

cluster, env, app_name

la metrica deve misurare gli eventi validi di cui però è fallito il processing per errori interni al codice

oc_document_provider_latency_bucket

cluster, env, app_name

istogramma che mostra la distribuzione di latenza delle risposte del provider

ENVIRONMENT

local

Indica l'ambiente di sviluppo (locale, dev, prod, ecc.) utilizzato da Sentry.

DEBUG

true

...

SENTRY_ENABLED

false

...

SENTRY_DSN

nessun valore

Endpoint per il monitoraggio di Sentry.

KAFKA_SERVER

kafka:9092

Lista di lunghezza variabile i cui elementi devono essere separati da virgola. lista degli Indirizzi dei broker Kafka per connettersi al cluster.

KAFKA_CONSUMER_GROUP

<nome_del_servizio>

Consumer group per Kafka.

KAFKA_CONSUMER_TOPIC

documents

Identifica il topic da cui consumare gli eventi Kafka.

KAFKA_PRODUCER_TOPIC

documents

Identifica il topic a cui inviare gli eventi Kafka.

KAFKA_RETRY_TOPIC

nessun valore

topic in cui produrre gli eventi consumati dal meccanismo di retry

SERVER_ADDRESS_PORT

0.0.0.0:8080

Indica l'indirizzo e la porta utilizzati per l'healthcheck.

CACHE_EXPIRATION

5m

...

CACHE_EVICTION

10m

...

STORAGE_TYPE

local

Tipo di storage dei pagamenti: s3, azure, local

STORAGE_ENDPOINT

nessun valore

Indirizzo di accesso allo storage.

STORAGE_ACCESS_S3_KEY

nessun valore

Chiave di accesso allo storage.

STORAGE_KEY_S3_ACCESS_SECRET

nessun valore

Chiave segreta di accesso allo storage.

STORAGE_S3_REGION

nessun valore

Location del cloud storage

STORAGE_BUCKET

nessun valore

Nome dello storage

STORAGE_BASE_PATH

"/data/"

Basepath dello storage

STORAGE_AZURE_ACCOUNT

nessun valore

Chiave dello storage AZURE

STORAGE_AZURE_KEY

nessun valore

Password dello storage AZURE

STORAGE_LOCAL_PATH

/data/

SDC_AUTH_TOKEN_USER

nessun valore

utente autenticazione per recuperare il token dalla piattaforma

SDC_AUTH_TOKEN_PASSWORD

nessun valore

password autenticazione per recuperare il token dalla piattaforma

standard della piattaforma
12 factors manifest
la convenzione da loro suggerita
Sentry
linee guida della pubblicazione del software open source
questa
Linee Guida di interoperabilità
il formato dei dati
raccomandazioni sul naming
validatore ufficiale OA3
workflow sistema di protocollazione
hurl
qui
qui

Versione 1.0

JSON Evento Pagamento

{
  "id": "68dada78-2398-4a11-b80a-98aaede3371c",
  "user_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "b21c4429-95e4-45d5-930f-44eb74136625",
  "created_at": "2023-06-06T11:59:11+02:00",
  "updated_at": "2023-06-12T00:10:16+02:00",
  "status": "PAYMENT_STARTED",
  "reason": "79501b2a-c9ad-41f8-a9e7-a885f2d570a2 - BNRMHL75C06G702B",
  "remote_id": "79501b2a-c9ad-41f8-a9e7-a885f2d570a2",
  "payment": {
    "transaction_id": null,
    "paid_at": null,
    "expire_at": "2023-09-04T11:59:05+02:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": "001550000000024427",
    "iud": "68dada7823984a11b80a98aaede3371c",
    "iuv": "550000000024427",
    "split": null
  },
  "links": {
    "online_payment_begin": {
      "url": "https://iris-proxy-qa.boat.opencontent.io/online-payment/68dada78-2398-4a11-b80a-98aaede3371c?gw=https://iristest.rete.toscana.it/gateway/PaymentAuthentication?token=1686045553436001U132&expire_at=2023-06-06T12:13:13",
      "last_opened_at": "2023-06-06T11:59:26+02:00",
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/it/pratiche/79501b2a-c9ad-41f8-a9e7-a885f2d570a2/detail",
      "last_opened_at": "2023-06-06T11:59:57+02:00",
      "method": "GET"
    },
    "offline_payment": {
      "url": "https://iris-proxy-qa.boat.opencontent.io/notice/68dada78-2398-4a11-b80a-98aaede3371c",
      "last_opened_at": "2023-06-06T11:59:20+02:00",
      "method": "GET"
    },
    "receipt": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/79501b2a-c9ad-41f8-a9e7-a885f2d570a2/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": "http://iris-proxy-qa.boat-backplane.opencontent.io/update/68dada78-2398-4a11-b80a-98aaede3371c",
      "last_check_at": "2023-06-12T00:10:16+02:00",
      "next_check_at": "2023-06-12T01:10:16+02:00",
      "method": "GET"
    }
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "e0b25e33-9d38-4781-823a-ed04753aea01",
  "event_version": "1.0",
  "event_created_at": "2023-06-12T00:10:16+02:00",
  "app_id": "iris-payment-proxy-qa:1.2.8"
}

Validazione campi

Payment

Campo
Tipo
Obbligatorio
Validazione

id

UUID

user_id

UUID

type

string(50)

Ogni proxy deve implementare un tipo di pagamento. Se per esempio, il proxy in questione gestisce pagamenti pagopa allora il campo va validato verificando che esso sia valorizzato a PAGOPA. In caso contrario si scarta l'evento e si emette un log di errore.

tenant_id

UUID

service_id

UUID

created_at

Datetime

ISO8601

updated_at

Datetime

ISO8601

status

Enum

Valori permessi: CREATION_PENDING CREATION_FAILED PAYMENT_PENDING PAYMENT_STARTED PAYMENT_CONFIRMED PAYMENT_FAILED NOTIFICATION_PENDING

COMPLETE EXPIRED

reason

string(140)

Causale del pagamento. Non può eccedere i 140 caratteri.

remote_id

UUID

payment

PaymentData

links

Links

payer

Payer

event_id

UUID

event_version

string(10)

L'attuale versione dell'evento deve essere "1.0". Ogni proxy deve validare la versione dell'evento in modo da sapere se processarlo o meno.

event_created_at

Datetime

ISO8601

app_id

string(100)

Valorizzarlo nel formato <nome-proxy>:<versione-proxy> Es. iris-payment-proxy:1.3.0

PaymentData

Campo
Tipo
Obbligatorio
Validazione

transaction_id

string(255)

Fornito dall'intermediario di pagamento. La lunghezza dunque può essere variabile

paid_at

Datetime

ISO8601

expire_at

Datetime

ISO8601 E' già valorizzato a priori, quindi non viene gestito dal proxy

amount

float

E' già valorizzato a priori, quindi non viene gestito dal proxy

currency

string(3)

ISO4217

notice_code

string(50)

iud

string(50)

è il valore del campo id del pagamento in formato esadecimale Es. id: 4a263efb-300b-437a-ab50-116fa9aff8a7 iud: 4a263efb300b-437aab50116fa9aff8a7

iuv

string(50)

split

jsonArray

Links

Campo
Tipo
Obbligatorio
Validazione

online_payment_begin

UrlData

online_payment_landing

UrlData

offline_payment

UrlData

receipt

UrlData

notify

List[Notify]

update

Update

UrlData

Campo
Tipo
Obbligatorio
Validazione

url

string

last_opened_at

Datetime

ISO8601

method

Enum

Valori permessi: GET POST

Notify

Campo
Tipo
Obbligatorio
Validazione

url

string

method

Enum

Valori permessi: GET POST

sent_at

Datetime

ISO8601

Update

Campo
Tipo
Obbligatorio
Validazione

url

string

last_check_at

Datetime

ISO8601

next_check_at

Datetime

ISO8601

method

Enum

Valori permessi: GET POST

Payer

Campo
Tipo
Obbligatorio
Validazione

type

Enum

Valori permessi: human legal

tax_identification_number

string(255)

name

string(255)

family_name

string(255)

street_name

string(255)

building_number

string(255)

postal_code

string(255)

town_name

string(255)

country_subdivision

string(2)

ISO 3166-2

country

string(2)

ISO 3166-1 alpha-2

email

string(255)

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Error while loading OpenAPI operation — Failed to fetch OpenAPI file

Logo
once-only

API v2

Aggiornamenti principali rispetto all'API v1

Introduzione di campi "standard"

Con la versione 2 delle API di configurazione sono stati introdotti dei campi da introdurre obbligatoriamente in ogni integrazione.

Tenant

I campi da introdurre obbligatoriamente a livello di configurazione del tenant sono:

  • id: identificativo univoco del tenant

  • name: nome del tenant

  • tax_identification_number: codice fiscale dell'ente

Campo
Tipo
Obbligatorio
Validazione

id

UUID

Identificativo univoco

name

String

Non vuoto, massimo 255 caratteri

tax_identification_number

String

Codice fiscale valido

Configurazione pagamento

I campi da introdurre obbligatoriamente a livello di configurazione del pagamento sono:

  • payment_type: tipo di pagamento, può assumere i valori pagopa o stamp, serve a distinguere tra un pagamento ordinario (es. TARI) e una marca da bollo digitale. Questa distinzione è necessaria in quanto in alcuni intermediare i metodi di creazione di un pagamento di una marca da bollo digitale differiscono da quelli di creazione di un pagamento ordinario.

  • remote_collection.id: identificativo della collezione di provenienza della configurazione di pagamento.

  • remote_collection.type: tipo di collezione di provenienza della configurazione di pagamento, può assumere valori quali application, service o altro.

  • amount: importo del pagamento configurato.

  • reason: causale del pagamento configurato

  • expire_at: numero di giorni di validità del pagamento

  • receiver.tax_identification_number: codice fiscale del beneficiario del pagamento, da usare in caso di pagamenti multibeneficiario

  • receiver.name: nome del beneficiario del pagamento, da usare in caso di pagamenti multibeneficiario

  • collection_data: tassonomia di pagoPA dei pagamenti

Campo
Tipo
Obbligatorio
Validazione

payment_type

Enum

Valori: PAGOPA, STAMP

remote_collection.id

UUID

Identificativo univoco

remote_collection.type

String

Valori: application, service, altro

amount

float

Maggiore di 0

reason

String

Non vuoto, massimo 255 caratteri

expire_at

int

Maggiore di 0

receiver.tax_identification_number

String

Codice fiscale valido

receiver.name

String

Non vuoto, massimo 255 caratteri

collection_data

String

Tassonomia pagoPA valida

Configurazione tenant

L'admin, dall'interfaccia di configurazione dei pagamenti della Stanza del Cittadino compila la configurazione mediante una form, il cui json schema è servito dall'API /tenants/schema

Lo schema della form sopra riportata è il seguente

{
    "display": "form",
    "components": [
        {
            "label": "UUID del Tenant",
            "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "spellcheck": false,
            "attributes": {
                "readonly": "readonly"
            },
            "hidden": true,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "id",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Nome dell'ente",
            "placeholder": "Comune di Bugliano",
            "tableView": true,
            "validate": {
                "required": true
            },
            "key": "name",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Codice IPA dell'ente",
            "placeholder": "A000",
            "description": "Codice (tutto maiuscolo) assegnato all'Ente nel registro <a href='http://www.indicepa.gov.it' target='__blank'>indice PA</a>",
            "spellcheck": false,
            "tableView": true,
            "validate": {
                "required": true
            },
            "key": "IPA_code",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Codice fiscale dell'ente",
            "placeholder": "123456789",
            "spellcheck": false,
            "key": "tax_identification_number",
            "type": "textfield",
            "validate": {
                "required": true
            },
            "input": true,
            "tableView": true
        },
        {
            "label": "Password",
            "placeholder": "************",
            "description": "Password assegnata da MyPay all\u2019Ente",
            "spellcheck": false,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "password",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Password carrello dovuti",
            "placeholder": "************",
            "description": "Password assegnata all\u2019ente creditore. Tale password va richiesta all\u2019amministratore dell\u2019ente creditore.<br>ATTENZIONE: questo dato NON coincide con la password (principale) dell\u2019ente.",
            "spellcheck": false,
            "tableView": true,
            "key": "cart_password",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Codice applicazione",
            "spellcheck": false,
            "tableView": true,
            "key": "application_code",
            "type": "textfield",
            "input": true,
            "hidden": true,
            "defaultValue": "SDC"
        },
        {
            "label": "Abilitato",
            "key": "active",
            "type": "checkbox",
            "input": true,
            "hidden": true,
            "defaultValue": true
        },
        {
            "label": "Salva",
            "showValidations": false,
            "tableView": false,
            "key": "submit",
            "type": "button",
            "input": true
        }
    ]
}

Premendo poi il bottone Salva, viene eseguita una POST /tenants servita dal proxy, con payload

{
    "name": "Comune di Bugliano",
    "IPA_code": "p_tn",
    "tax_identification_number": "00337460224",
    "password": "4CKSILO7FQPZ",
    "cart_password": "",
    "application_code": "",
    "active": true,
    "enable_checkout": false,
    "id": "60e35f02-1509-408c-b101-3b1a28109329"
}

Per modificare una configurazione esistente, il proxy serve l'API PUT /tenants/{tenant_id} e PATCH /tenants/{tenant_id}

Per eliminare una configurazione esistente, il proxy serve l'API DELETE /tenants/{tenant_id} . In questo caso l'eliminazione è una soft-delete, ovvero la configurazione viene semplicemente disattivata settando il parametro active a false ed eliminando la configurazione dalla memoria ma non dallo storage.

Configurazione pagamento

L'admin, dall'interfaccia di configurazione dei pagamenti per un servizio compila la configurazione mediante una form, il cui json schema è servito dall'API /configs/schema

Lo schema della form soprariportata è il seguente

{
    "display": "form",
    "components": [
        {
            "label": "UUID del Tenant",
            "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "spellcheck": false,
            "attributes": {
                "readonly": "readonly"
            },
            "hidden": true,
            "tableView": false,
            "validate": {
                "required": true
            },
            "key": "tenant_id",
            "type": "textfield",
            "input": true
        },
        {
            "label": "Container",
            "tableView": false,
            "validateWhenHidden": false,
            "key": "remote_collection",
            "type": "container",
            "input": true,
            "components": [
                {
                    "label": "UUID del Servizio",
                    "placeholder": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
                    "applyMaskOn": "change",
                    "hidden": true,
                    "spellcheck": false,
                    "tableView": true,
                    "validate": {
                        "required": true
                    },
                    "validateWhenHidden": false,
                    "key": "id",
                    "attributes": {
                        "readonly": "readonly"
                    },
                    "type": "textfield",
                    "input": true
                },
                {
                    "label": "Tipo Collezione",
                    "applyMaskOn": "change",
                    "hidden": true,
                    "tableView": true,
                    "validateWhenHidden": false,
                    "key": "type",
                    "type": "textfield",
                    "input": true
                }
            ]
        },
        {
            "label": "Lista Versamenti",
            "reorder": false,
            "addAnother": "Aggiungi Versamento",
            "addAnotherPosition": "bottom",
            "layoutFixed": false,
            "enableRowGroups": false,
            "initEmpty": false,
            "tableView": false,
            "defaultValue": [
                {
                    "payment_type": "pagopa",
                    "management_id": "",
                    "reason": "",
                    "expire_at": "",
                    "receiver_enable": false,
                    "split": [],
                    "hidden": "",
                    "collection_data": "",
                    "receiver": {
                        "tax_identification_number": "",
                        "name": "",
                        "iban": ""
                    },
                    "pagopa_category": ""
                }
            ],
            "validate": {
                "minLength": "1",
                "maxLength": "1",
                "custom": "valid = true;\n\nif (input && Array.isArray(input)) {\n  for (let i = 0; i < input.length; i++) {\n    const row = input[i] || {};\n\n    const splitRows = row.split || [];\n    if (Array.isArray(splitRows) && splitRows.length > 0) {\n      const totalSplit = splitRows.reduce((sum, s) => sum + (Number(s.amount) || 0), 0);\n      const expectedAmount = Number(row.amount);\n\n      if (!isNaN(expectedAmount)) {\n        if (totalSplit !== expectedAmount) {\n          valid = `Errore nel versamento ${i + 1}: somma split (${totalSplit}) diversa da amount (${expectedAmount}).`;\n          break;\n        }\n      }\n    }\n  }\n}\n"
            },
            "validateWhenHidden": false,
            "key": "items",
            "type": "datagrid",
            "input": true,
            "components": [
                {
                    "label": "Versamenti",
                    "tableView": false,
                    "key": "versamenti",
                    "type": "fieldset",
                    "input": true,
                    "components": [
                        {
                            "label": "Scegli il tipo di pagamento",
                            "widget": "choicesjs",
                            "tableView": true,
                            "data": {
                                "values": [
                                    {
                                        "label": "PagoPA",
                                        "value": "pagopa"
                                    },
                                    {
                                        "label": "Marca da Bollo Digitale",
                                        "value": "stamp"
                                    }
                                ]
                            },
                            "validate": {
                                "required": true
                            },
                            "key": "payment_type",
                            "type": "select",
                            "input": true,
                            "defaultValue": "pagopa"
                        },
                        {
                            "label": "Tipo dovuto",
                            "placeholder": "TD_CIE",
                            "description": "Riferimento alla tipologia del dovuto secondo la classificazione data dal beneficiario. Nel caso di una marca da bollo digitale indicare MARCA_BOLLO_DIGITALE",
                            "applyMaskOn": "change",
                            "spellcheck": false,
                            "tableView": true,
                            "validate": {
                                "required": true,
                                "maxLength": 64
                            },
                            "key": "management_id",
                            "type": "textfield",
                            "input": true
                        },
                        {
                            "label": "Importo",
                            "placeholder": "12.34",
                            "description": "Le cifre decimali vanno separate dalla parte intera tramite il punto.",
                            "applyMaskOn": "change",
                            "mask": false,
                            "tableView": false,
                            "delimiter": false,
                            "requireDecimal": false,
                            "inputFormat": "plain",
                            "truncateMultipleSpaces": false,
                            "validate": {
                                "required": true,
                                "custom": "valid = (row.payment_type == 'stamp' && row.amount == 16) || (row.payment_type == 'pagopa') ? true : \"In caso di marca da bollo digitale l'unico importo permesso è di 16 euro\""
                            },
                            "validateWhenHidden": false,
                            "key": "amount",
                            "type": "number",
                            "input": true
                        },
                        {
                            "label": "Causale",
                            "applyMaskOn": "change",
                            "tableView": true,
                            "validate": {
                                "required": true,
                                "maxLength": 60
                            },
                            "key": "reason",
                            "type": "textfield",
                            "input": true
                        },
                        {
                            "label": "Validità del pagamento (in gg)",
                            "applyMaskOn": "change",
                            "mask": false,
                            "tableView": false,
                            "delimiter": false,
                            "requireDecimal": false,
                            "inputFormat": "plain",
                            "truncateMultipleSpaces": false,
                            "validate": {
                                "required": true,
                                "min": 1
                            },
                            "validateWhenHidden": false,
                            "key": "expire_at",
                            "type": "number",
                            "input": true
                        },
                        {
                            "label": "Dati specifici riscossione",
                            "placeholder": "9/3300.1",
                            "description": "Rappresenta l'indicazione dell'imputazione della specifica entrata ed è così articolato:<br><b>TIPO_CONTABILITA / CODICE_CONTABILITA</b>, dove TIPO_CONTABILITA ha il seguente significato:<ul><li>0 (Capitolo e articolo di Entrata del Bilancio dello Stato)</li><li>1 (Numero della contabilità speciale)</li><li>2 (Codice SIOPE)</li><li>9 (Altro codice ad uso dell'amministrazione)</li></ul>",
                            "applyMaskOn": "change",
                            "spellcheck": false,
                            "tableView": false,
                            "defaultValue": "9/3300.1",
                            "validate": {
                                "required": true,
                                "maxLength": 140
                            },
                            "key": "collection_data",
                            "type": "textfield",
                            "input": true
                        },
                        {
                            "label": "Categoria",
                            "widget": "choicesjs",
                            "description": "Selezione dalla Tassonomia di PagoPA dei pagamenti.",
                            "tableView": true,
                            "dataSrc": "url",
                            "data": {
                                "url": "https://api.opencityitalia.it/datasets/pagopa",
                                "headers": [
                                    {
                                        "key": "",
                                        "value": ""
                                    }
                                ]
                            },
                            "dataType": "object",
                            "idPath": "",
                            "valueProperty": "DATI SPECIFICI DI INCASSO",
                            "template": "<span>{{ item[\"NOME MACRO AREA\"] }}: {{ item[\"TIPO SERVIZIO\"] }}</span>",
                            "validate": {
                                "required": true
                            },
                            "key": "pagopa_category",
                            "type": "select",
                            "disableLimit": false,
                            "noRefreshOnScroll": false,
                            "input": true
                        },
                        {
                            "label": "Bilancio",
                            "reorder": false,
                            "addAnother": "Aggiungi bilancio",
                            "addAnotherPosition": "bottom",
                            "layoutFixed": false,
                            "enableRowGroups": false,
                            "initEmpty": true,
                            "tableView": false,
                            "calculateValue": "if (!row.split || row.split == 'undefined') {\n  row.split = []\n} else if (typeof row.split==='object' && Object.keys(row.split).length === 0) {\n  row.split = [];\n}",
                            "validateWhenHidden": false,
                            "key": "split",
                            "type": "datagrid",
                            "input": true,
                            "components": [
                                {
                                    "label": "ID",
                                    "description": "Identificativo univoco della voce di bilancio. Testo libero",
                                    "placeholder": "c_1",
                                    "tableView": true,
                                    "validate": {
                                        "required": true
                                    },
                                    "key": "id",
                                    "type": "textfield",
                                    "input": true
                                },
                                {
                                    "label": "Capitolo",
                                    "placeholder": "Capitolo di bilancio c1",
                                    "validate": {
                                        "required": true
                                    },
                                    "tableView": true,
                                    "key": "budget_chapter",
                                    "type": "textfield",
                                    "input": true
                                },
                                {
                                    "label": "Ufficio",
                                    "placeholder": "Codice ufficio bilancio c1",
                                    "tableView": true,
                                    "key": "office_code",
                                    "type": "textfield",
                                    "input": true
                                },
                                {
                                    "label": "Accertamento",
                                    "placeholder": "Accertamento c1",
                                    "tableView": true,
                                    "key": "assessment",
                                    "type": "textfield",
                                    "input": true
                                },
                                {
                                    "label": "Importo",
                                    "placeholder": "16.00",
                                    "description": "Importo della voce di bilancio. NB: La somma degli importi delle voci DEVE equivalere all'importo totale",
                                    "applyMaskOn": "change",
                                    "mask": false,
                                    "tableView": false,
                                    "delimiter": false,
                                    "requireDecimal": false,
                                    "inputFormat": "plain",
                                    "truncateMultipleSpaces": false,
                                    "validate": {
                                        "required": true
                                    },
                                    "validateWhenHidden": false,
                                    "key": "amount",
                                    "type": "number",
                                    "input": true
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "label": "Abilitato",
            "key": "active",
            "type": "checkbox",
            "input": true,
            "hidden": true,
            "defaultValue": true,
            "tableView": false
        },
        {
            "type": "button",
            "label": "Salva",
            "key": "submit",
            "disableOnInvalid": true,
            "input": true,
            "tableView": false
        }
    ]
}

Premendo poi il bottone Salva, viene eseguita una POST /configs servita dal proxy, con payload

{
    "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
    "remote_collection": {
        "id": "37dbc4a5-be4c-4246-bfad-15b9f5f5e7f3",
        "type": "service"
    },
    "items": [
        {
            "payment_type": "pagopa",
            "management_id": "SH_OPENCONTENT",
            "amount": 3.0,
            "reason": "Pagamento immediato MyPay 4 Lombardia",
            "expire_at": 180,
            "collection_data": "9/123",
            "pagopa_category": "9/123",
            "split": [],
            "receiver": {
                "tax_identification_number": "80034840167",
                "name": "Comune di Bugliano",
                "iban": "",
                "address": null,
                "building_number": null,
                "postal_code": null,
                "town_name": null,
                "country_subdivision": null,
                "country": null
            }
        }
    ],
    "active": true,
    "id": "37dbc4a5-be4c-4246-bfad-15b9f5f5e7f3"
}

Per ottenere una lista di configurazioni di pagamento (fino a un massimo di 5), il proxy serve l'API GET /configs?config_id=config_id1&config_id=config_id2

Per modificare una configurazione esistente, il proxy serve l'API PUT /configs/{config_id} e PATCH /configs/{config_id}

Per eliminare una configurazione esistente, il proxy serve l'API DELETE /configs/{config_id} . In questo caso l'eliminazione è una soft-delete, ovvero la configurazione viene semplicemente disattivata settando il parametro active a false.

Swagger di esempio

Accertamento Cittadinanza

get

fruizione e-service Accertamento Cittadinanza

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
404

Not Found

application/json
get
GET /anpr/accertamento-cittadinanza HTTP/1.1
Host: 
Accept: */*
{
  "data": {
    "cittadinanza": [
      {
        "country": "text",
        "nationality": "text"
      }
    ]
  },
  "meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}

Accertamento Cittadinanza

post

Validate Accertamento Cittadinanza payload

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
Responses
200

OK

application/json
404

Not Found

application/json
post
POST /anpr/accertamento-cittadinanza HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 165

{
  "Data": {
    "cittadinanza": [
      {
        "country": "text",
        "nationality": "text"
      }
    ]
  },
  "Meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}
{
  "result": true
}

Accertamento Residenza

get

fruizione e-service Accertamento Residenza

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
404

Not Found

application/json
get
GET /anpr/accertamento-residenza HTTP/1.1
Host: 
Accept: */*
{
  "data": {
    "address": "text",
    "address_data": {
      "cap": "text",
      "codice_comune": 1,
      "comune": "text",
      "luogo_istat": "text",
      "pr": "text"
    },
    "codice_istat_comune": "text",
    "country": "text",
    "county": "text",
    "house_number": "text",
    "locator_within": "text",
    "municipality": "text",
    "postal_code": "text"
  },
  "meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}

Accertamento Residenza

post

Validate Accertamento Residenza payload

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
Responses
200

OK

application/json
404

Not Found

application/json
post
POST /anpr/accertamento-residenza HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 373

{
  "Data": {
    "address": "text",
    "address_data": {
      "cap": "text",
      "codice_comune": 1,
      "comune": "text",
      "luogo_istat": "text",
      "pr": "text"
    },
    "codice_istat_comune": "text",
    "country": "text",
    "county": "text",
    "house_number": "text",
    "locator_within": "text",
    "municipality": "text",
    "postal_code": "text"
  },
  "Meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}
{
  "result": true
}

Stato Famiglia

get

Retrieve Stato Famiglia for a specific fiscal code with specific configuration

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
404

Not Found

application/json
get
GET /anpr/stato-famiglia HTTP/1.1
Host: 
Accept: */*
{
  "data": null,
  "meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}

Stato famiglia

post

Validate Stato Famiglia payload

Query parameters
fiscal_codestringOptional

italian fiscal code

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
config_idstringOptional

id that identify a specific configuration to call an e-service

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
formatstringOptional

identify the output payload structure

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
DataanyOptional

data to validate. Can be different depending by the format param

Responses
200

OK

application/json
404

Not Found

application/json
post
POST /anpr/stato-famiglia HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 111

{
  "Data": null,
  "Meta": {
    "call_url": "text",
    "created_at": "text",
    "format": "text",
    "signature": "text",
    "source": "text"
  }
}
{
  "result": true
}

Get e-services

get

Retrieve the list of e-services available through the service

Query parameters
offsetintegerOptional

the starting point or the index from which the data should be retrieved

Example: 6
limitintegerOptional

The limit parameter specifies the maximum number of items to be returned in a single page or request

sortstringOptional

...

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /e-services HTTP/1.1
Host: 
Accept: */*
{
  "data": [
    {
      "aud_client_assertion": "text",
      "audience": "text",
      "endpoint": "text",
      "id": "text",
      "name": "text",
      "slug": "text",
      "version": "text"
    }
  ],
  "links": {
    "next": "text",
    "prev": "text",
    "self": "text"
  },
  "meta": {
    "page": {
      "limit": 1,
      "offset": 1,
      "sort": "text"
    },
    "total": 1
  }
}

Options e-services

options

Get available operations for the e-services resource.

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /e-services HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Status health-check

get

...

Responses
200

OK

application/json
500

Internal Server Error

application/json
get
GET /status HTTP/1.1
Host: 
Accept: */*
{
  "checked_at": "2025-08-13T07:33:11.362Z",
  "id": "text",
  "status": "text"
}

Save Tenant Configuration

post

...

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
ipa_codestringOptional

...

Example: 123456
namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
201

Created

application/json
400

Bad Request

application/json
post
POST /tenants HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 93

{
  "id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "ipa_code": "123456",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "id": "text",
  "ipa_code": "text",
  "name": "text",
  "updated_at": "text"
}

Options Tenant

options

Get available operations for the tenant resource.

Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /tenants HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Get Tenant Configuration

get

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /tenants/{tenant_id} HTTP/1.1
Host: 
Accept: */*
{
  "created_at": "text",
  "id": "text",
  "ipa_code": "text",
  "name": "text",
  "updated_at": "text"
}

Update tenant Configuration

put

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
ipa_codestringOptional

...

Example: 123456
namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
200

OK

application/json
400

Bad Request

application/json
put
PUT /tenants/{tenant_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 49

{
  "ipa_code": "123456",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "id": "text",
  "ipa_code": "text",
  "name": "text",
  "updated_at": "text"
}

delete Tenants Configuration

delete

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
204

No Content

application/json
400

Bad Request

application/json
delete
DELETE /tenants/{tenant_id} HTTP/1.1
Host: 
Accept: */*
{
  "created_at": "text",
  "id": "text",
  "ipa_code": "text",
  "name": "text",
  "updated_at": "text"
}

Update tenant Configuration

patch

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
ipa_codestringOptional

...

Example: 123456
namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
200

OK

application/json
400

Bad Request

application/json
patch
PATCH /tenants/{tenant_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 49

{
  "ipa_code": "123456",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "id": "text",
  "ipa_code": "text",
  "name": "text",
  "updated_at": "text"
}

Options Tenant by id

options

Get available operations for the tenant resource.

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /tenants/{tenant_id} HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Get Clients list

get

Get Clients Configuration list for specific tenant

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Query parameters
limitintegerOptional

The limit parameter specifies the maximum number of items to be returned in a single page or request

sortstringOptional

...

offsetintegerOptional

the starting point or the index from which the data should be retrieved

Example: 6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /tenants/{tenant_id}/clients HTTP/1.1
Host: 
Accept: */*
{
  "data": [
    {
      "created_at": "text",
      "env": "text",
      "id": "text",
      "key_id": "text",
      "key_pair_id": "text",
      "name": "text",
      "public_key": "text",
      "tenant_id": "text",
      "updated_at": "text"
    }
  ],
  "links": {
    "next": "text",
    "prev": "text",
    "self": "text"
  },
  "meta": {
    "page": {
      "limit": 1,
      "offset": 1,
      "sort": "text"
    },
    "total": 1
  }
}

store client pdnd Configuration

post

Save client pdnd Configuration of specific tenant

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
envstringOptional

ambiente del client

Example: collaudo
idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
key_idstringOptional

key id del client

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
key_pair_idstringOptional

id interno del materiale critografico

namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
201

Created

application/json
400

Bad Request

application/json
403

Forbidden

application/json
post
POST /tenants/{tenant_id}/clients HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 159

{
  "env": "collaudo",
  "id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "key_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "key_pair_id": "text",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "env": "text",
  "id": "text",
  "key_id": "text",
  "key_pair_id": "text",
  "name": "text",
  "public_key": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Options Clients

options

Get available operations for the Clients resource.

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Query parameters
limitintegerOptional

The limit parameter specifies the maximum number of items to be returned in a single page or request

sortstringOptional

...

offsetintegerOptional

the starting point or the index from which the data should be retrieved

Example: 6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /tenants/{tenant_id}/clients HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Get client pdnd Configuration

get

Get client pdnd Configuration of specific tenant

Path parameters
client_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
403

Forbidden

application/json
get
GET /tenants/{tenant_id}/clients/{client_id} HTTP/1.1
Host: 
Accept: */*
{
  "created_at": "text",
  "env": "text",
  "id": "text",
  "key_id": "text",
  "key_pair_id": "text",
  "name": "text",
  "public_key": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Update client pdnd Configuration

put

Update client pdnd Configuration of specific tenant

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
client_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
envstringOptional

ambiente del client

Example: collaudo
key_idstringOptional

key id del client

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
key_pair_idstringOptional

id interno del materiale critografico

namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
200

OK

application/json
400

Bad Request

application/json
403

Forbidden

application/json
put
PUT /tenants/{tenant_id}/clients/{client_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 115

{
  "env": "collaudo",
  "key_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "key_pair_id": "text",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "env": "text",
  "id": "text",
  "key_id": "text",
  "key_pair_id": "text",
  "name": "text",
  "public_key": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Delete client pdnd Configuration

delete

Delete client pdnd Configuration of specific tenant

Path parameters
client_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
204

No Content

400

Bad Request

application/json
403

Forbidden

application/json
delete
DELETE /tenants/{tenant_id}/clients/{client_id} HTTP/1.1
Host: 
Accept: */*

No content

Update client pdnd Configuration

patch

Update client pdnd Configuration of specific tenant

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
client_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
envstringOptional

ambiente del client

Example: collaudo
key_idstringOptional

key id del client

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
key_pair_idstringOptional

id interno del materiale critografico

namestringOptional

nome del tenant

Example: comune di Bugliano
Responses
200

OK

application/json
400

Bad Request

application/json
403

Forbidden

application/json
patch
PATCH /tenants/{tenant_id}/clients/{client_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 115

{
  "env": "collaudo",
  "key_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "key_pair_id": "text",
  "name": "comune di Bugliano"
}
{
  "created_at": "text",
  "env": "text",
  "id": "text",
  "key_id": "text",
  "key_pair_id": "text",
  "name": "text",
  "public_key": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Options Clients

options

Get available operations for the Clients resource.

Path parameters
client_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /tenants/{tenant_id}/clients/{client_id} HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Get Config list of specific tenant

get

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Query parameters
limitintegerOptional

The limit parameter specifies the maximum number of items to be returned in a single page or request

sortstringOptional

...

offsetintegerOptional

the starting point or the index from which the data should be retrieved

Example: 6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /tenants/{tenant_id}/configs HTTP/1.1
Host: 
Accept: */*
{
  "data": [
    {
      "call_url": "text",
      "client_id": "text",
      "created_at": "text",
      "eservice_id": "text",
      "id": "text",
      "is_active": true,
      "purpose_id": "text",
      "tenant_id": "text",
      "updated_at": "text"
    }
  ],
  "links": {
    "next": "text",
    "prev": "text",
    "self": "text"
  },
  "meta": {
    "page": {
      "limit": 1,
      "offset": 1,
      "sort": "text"
    },
    "total": 1
  }
}

Save Service Configuration

post

...

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
client_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
eservice_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
is_activeboolean | nullableOptional

...

Example: false
purpose_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
201

Created

application/json
400

Bad Request

application/json
post
POST /tenants/{tenant_id}/configs HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 175

{
  "client_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "eservice_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "is_active": false,
  "purpose_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6"
}
{
  "call_url": "text",
  "client_id": "text",
  "created_at": "text",
  "eservice_id": "text",
  "id": "text",
  "is_active": true,
  "purpose_id": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Options Config

options

Get available operations for the Config resource.

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
201

Created

options
OPTIONS /tenants/{tenant_id}/configs HTTP/1.1
Host: 
Accept: */*
201

Created

No content

Get Config

get

Get config of specific tenant by id

Path parameters
config_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /tenants/{tenant_id}/configs/{config_id} HTTP/1.1
Host: 
Accept: */*
{
  "call_url": "text",
  "client_id": "text",
  "created_at": "text",
  "eservice_id": "text",
  "id": "text",
  "is_active": true,
  "purpose_id": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Update Config

put

Update config of specific tenant

Path parameters
config_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
client_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
eservice_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
is_activeboolean | nullableOptional

...

Example: false
purpose_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
put
PUT /tenants/{tenant_id}/configs/{config_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 175

{
  "client_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "eservice_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "is_active": false,
  "purpose_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6"
}
{
  "call_url": "text",
  "client_id": "text",
  "created_at": "text",
  "eservice_id": "text",
  "id": "text",
  "is_active": true,
  "purpose_id": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Disable Config

delete

Soft deleting config of specific tenant

Path parameters
config_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
204

No Content

400

Bad Request

application/json
delete
DELETE /tenants/{tenant_id}/configs/{config_id} HTTP/1.1
Host: 
Accept: */*

No content

Patch Config

patch

Patch config of specific tenant

Path parameters
config_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
client_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
eservice_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
is_activeboolean | nullableOptional

...

Example: false
purpose_idstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
patch
PATCH /tenants/{tenant_id}/configs/{config_id} HTTP/1.1
Host: 
Content-Type: application/json
Accept: */*
Content-Length: 175

{
  "client_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "eservice_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "is_active": false,
  "purpose_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6"
}
{
  "call_url": "text",
  "client_id": "text",
  "created_at": "text",
  "eservice_id": "text",
  "id": "text",
  "is_active": true,
  "purpose_id": "text",
  "tenant_id": "text",
  "updated_at": "text"
}

Options Config

options

Get available operations for the Config resource.

Path parameters
config_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

options
OPTIONS /tenants/{tenant_id}/configs/{config_id} HTTP/1.1
Host: 
Accept: */*
200

OK

No content

Get Key

get

Get key unused key for a specific tenant

Path parameters
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

OK

application/json
400

Bad Request

application/json
get
GET /tenants/{tenant_id}/keys HTTP/1.1
Host: 
Accept: */*
{
  "id": "text",
  "public_key": "text"
}

Delete Key

delete

Delete key used by specific tenant

Path parameters
keys_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
tenant_idstringRequired

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Header parameters
AuthorizationstringOptional

...

Example: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
204

No Content

400

Bad Request

application/json
delete
DELETE /tenants/{tenant_id}/keys/{keys_id} HTTP/1.1
Host: 
Accept: */*

No content

Metrics

get

Metriche Prometheus

Responses
200

Successful Response

text/plain
Responsestring
422

Validation Error

application/problem+json
get
GET /registry-proxy/agspr/v1/metrics HTTP/1.1
Host: api.qa.stanzadelcittadino.it
Accept: */*
text

Get Health Kafka Status

get

Get Status

Responses
200

Successful Response

application/problem+json
422

Validation Error

application/problem+json
503

Service Unavailable

application/problem+json
get
GET /registry-proxy/agspr/v1/status HTTP/1.1
Host: api.qa.stanzadelcittadino.it
Accept: */*
{
  "type": "text",
  "title": "text",
  "status": 1,
  "detail": "text",
  "instance": "text"
}

Get Schema

get

Get local Schema

Responses
200

Successful Response

application/json
Responseany
422

Validation Error

application/problem+json
get
GET /registry-proxy/agspr/v1/schema HTTP/1.1
Host: api.qa.stanzadelcittadino.it
Accept: */*

No content

Save Tenant Configuration

post

Create New Tenant Configuration

Body
aoo_codestringRequired
base_urlstringRequired
created_atstringOptionalDefault: ""
descriptionstringRequired
institution_codestringRequired
modified_atstringOptionalDefault: ""
slugstringRequired
idstringRequired
Responses
201

Successful Response

application/json
Responseany
422

Validation Error

application/problem+json
post
POST /registry-proxy/agspr/v1/tenants/ HTTP/1.1
Host: api.qa.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 143

{
  "aoo_code": "text",
  "base_url": "text",
  "created_at": "",
  "description": "text",
  "institution_code": "text",
  "modified_at": "",
  "slug": "text",
  "id": "text"
}

No content

Save Service Configuration

post

Save Service Configuration

Body
is_activebooleanRequired
ws_url_fascicolostringRequired
ws_url_protocolstringRequired
usernamestringRequired
passwordstringRequired
codentestringRequired
codice_amministrazionestringRequired
emailstringRequired
class_codstringRequired
denominazionestringRequired
id_unit_orgstringRequired
codice_aoostringRequired
dispatch_typestringRequired
tipo_documentoany ofOptionalDefault: ""
stringOptional
or
nullOptional
folder_yearany ofOptionalDefault: ""
stringOptional
or
nullOptional
folder_numberany ofOptionalDefault: ""
stringOptional
or
nullOptional
competence_unitany ofOptionalDefault: ""
stringOptional
or
nullOptional
creation_unitany ofOptionalDefault: ""
stringOptional
or
nullOptional
idstringRequired
tenant_idstringRequired
Responses
201

Successful Response

application/json
Responseany
422

Validation Error

application/problem+json
post
POST /registry-proxy/agspr/v1/services/ HTTP/1.1
Host: api.qa.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 402

{
  "is_active": true,
  "ws_url_fascicolo": "text",
  "ws_url_protocol": "text",
  "username": "text",
  "password": "text",
  "codente": "text",
  "codice_amministrazione": "text",
  "email": "text",
  "class_cod": "text",
  "denominazione": "text",
  "id_unit_org": "text",
  "codice_aoo": "text",
  "dispatch_type": "text",
  "tipo_documento": "",
  "folder_year": "",
  "folder_number": "",
  "competence_unit": "",
  "creation_unit": "",
  "id": "text",
  "tenant_id": "text"
}

No content

Get Tenant Form Schema

get
Responses
200

Successful Response

application/json
Responseany
get
GET /payment-proxy/iris/v2/tenants/schema HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Options Tenant Schema

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/tenants/schema HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Get Tenant Configuration

get
Path parameters
tenant_idstring · uuidRequiredExample: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
200

Successful Response

application/json
422

Validation Error

application/json
get
GET /payment-proxy/iris/v2/tenants/{tenant_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
{
  "tax_identification_number": "01234567891",
  "name": "Comune di Bugliano",
  "code": "CBugliano",
  "e2e_code": "CBugliano",
  "receiver_code": "RTIRIS",
  "e2e_receiver_code": "RTIRIS",
  "receiver_sil_id": "SIL_IRIS_ITR",
  "sil_id": "SIL_CBUGLIANO_OPEN",
  "password": "PASSWORD",
  "cert": "-----BEGIN CERTIFICATE REQUEST-----MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNVBAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl-----END CERTIFICATE REQUEST-----",
  "application_code": "",
  "active": true,
  "id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6"
}

Update Tenant Configuration

put
Path parameters
tenant_idstring · uuidRequiredExample: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
tax_identification_numberstringRequired
namestringOptional
codestringRequired
e2e_codestringOptional
sil_idstringRequired
receiver_codestringOptional
e2e_receiver_codestringOptional
receiver_sil_idstringOptional
passwordstringRequired
certstringRequired
application_codestringOptionalDefault: ""
activebooleanRequired
stamp_codestringOptional
stamp_passwordstringOptional
Responses
200

Successful Response

application/json
Responseany
422

Validation Error

application/json
put
PUT /payment-proxy/iris/v2/tenants/{tenant_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 559

{
  "tax_identification_number": "01234567891",
  "name": "Comune di Bugliano",
  "code": "CBugliano",
  "e2e_code": "CBugliano",
  "receiver_code": "RTIRIS",
  "e2e_receiver_code": "RTIRIS",
  "receiver_sil_id": "SIL_IRIS_ITR",
  "password": "PASSWORD",
  "stamp_code": "CBugliano_SIL_E_BOLLO",
  "stamp_password": "PASSWORD",
  "sil_id": "SIL_CBUGLIANO_OPEN",
  "cert": "-----BEGIN CERTIFICATE REQUEST-----MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNVBAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl-----END CERTIFICATE REQUEST-----",
  "application_code": "",
  "active": true
}

No content

Delete Tenant

delete
Path parameters
tenant_idstring · uuidRequiredExample: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Responses
204

Successful Response

422

Validation Error

application/json
delete
DELETE /payment-proxy/iris/v2/tenants/{tenant_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*

No content

Update Existing Tenant Configuration

patch
Path parameters
tenant_idstring · uuidRequiredExample: b212c4b4-db26-4404-8c7c-47dab99dd2e6
Body
object · NewConfigurationOptional
Responses
200

Successful Response

application/json
Responseany
422

Validation Error

application/json
patch
PATCH /payment-proxy/iris/v2/tenants/{tenant_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 42

{
  "tax_identification_number": "1234567891"
}

No content

Options Tenant Id

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/tenants/{tenant_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Save Tenant Configuration

post
Body
tax_identification_numberstringRequired
namestringOptional
codestringRequired
e2e_codestringOptional
sil_idstringRequired
receiver_codestringOptional
e2e_receiver_codestringOptional
receiver_sil_idstringOptional
passwordstringRequired
certstringRequired
application_codestringOptionalDefault: ""
activebooleanRequired
stamp_codestringOptional
stamp_passwordstringOptional
idstring · uuidRequired
Responses
201

Successful Response

application/json
Responseany
422

Validation Error

application/json
post
POST /payment-proxy/iris/v2/tenants HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 538

{
  "tax_identification_number": "01234567891",
  "name": "Comune di Bugliano",
  "code": "CBugliano",
  "e2e_code": "CBugliano",
  "receiver_code": "RTIRIS",
  "e2e_receiver_code": "RTIRIS",
  "receiver_sil_id": "SIL_IRIS_ITR",
  "sil_id": "SIL_CBUGLIANO_OPEN",
  "password": "PASSWORD",
  "cert": "-----BEGIN CERTIFICATE REQUEST-----MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNVBAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl-----END CERTIFICATE REQUEST-----",
  "application_code": "",
  "active": true,
  "id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6"
}

No content

Options Tenants

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/tenants HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Get Config Form Schema

get
Responses
200

Successful Response

application/json
Responseany
get
GET /payment-proxy/iris/v2/configs/schema HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Options Config Schema

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/configs/schema HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Get Payment Configurations List

get
Query parameters
config_idsstring · uuid[]Optional

Lista id delle configurazioni di pagamento

Responses
200

Successful Response

application/json
422

Validation Error

application/json
get
GET /payment-proxy/iris/v2/configs HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
[
  {
    "tenant_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
    "payment_type": "pagopa",
    "code": "CONTR_PRTPM",
    "description": "Abbonamento aree di sosta",
    "active": true,
    "split": [
      {
        "split_id": "c_1",
        "split_type": "Tipo c1",
        "split_code": "Codice c1",
        "split_description": "Descrizione c1",
        "split_amount": 0.5,
        "split_budget_chapter": "Capitolo di bilancio c1",
        "split_assessment": "Accertamento c1"
      }
    ],
    "amount": 1,
    "reason": "test",
    "expire_at": 180,
    "collection_data": "9/0101100IM/",
    "office_code": "FSE",
    "office_description": "Fondo Sociale Europeo",
    "reference_code": "BOLLO_TIROCINI",
    "notes": "",
    "remote_collection": {
      "id": "23d57b65-5eb9-4f0a-a507-fbcf3057b248",
      "type": "service"
    }
  }
]

Save Payment Configuration

post
Body
tenant_idstring · uuidRequired
payment_typestringOptionalDefault: pagopa
codestringOptional
descriptionstringOptional
amountnumberRequired
reasonstring · max: 140Optional
expire_atintegerRequired
collection_datastringRequired
activebooleanRequired
office_codestringOptional
office_descriptionstringOptional
reference_codestringOptional
notesstringOptional
idstring · uuidOptional
Responses
201

Successful Response

application/json
Responseany
422

Validation Error

application/json
post
POST /payment-proxy/iris/v2/configs HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 770

{
  "id": "3e68161e-9abb-43ac-99e6-39b0ece65b40",
  "tenant_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "payment_type": "pagopa",
  "code": "CONTR_PRTPM",
  "description": "Abbonamento aree di sosta",
  "active": true,
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": 0.5,
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    }
  ],
  "amount": 1,
  "reason": "test",
  "expire_at": 10,
  "collection_data": "9/0101100IM/",
  "office_code": "FSE",
  "office_description": "Fondo Sociale Europeo",
  "reference_code": "BOLLO_TIROCINI",
  "notes": "",
  "remote_collection": {
    "id": "3e68161e-9abb-43ac-99e6-39b0ece65b40",
    "type": "service"
  },
  "receiver": {
    "tax_identification_number": "06363391001",
    "name": "Agenzia delle Entrate"
  }
}

No content

Options Configs

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/configs HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Get Payment Configuration

get
Path parameters
config_idstring · uuidRequiredExample: 23d57b65-5eb9-4f0a-a507-fbcf3057b248
Responses
200

Successful Response

application/json
422

Validation Error

application/json
get
GET /payment-proxy/iris/v2/configs/{config_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
{
  "tenant_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "payment_type": "pagopa",
  "code": "CONTR_PRTPM",
  "description": "Abbonamento aree di sosta",
  "active": true,
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": 0.5,
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    }
  ],
  "amount": 1,
  "reason": "test",
  "expire_at": 180,
  "collection_data": "9/0101100IM/",
  "office_code": "FSE",
  "office_description": "Fondo Sociale Europeo",
  "reference_code": "BOLLO_TIROCINI",
  "notes": "",
  "remote_collection": {
    "id": "23d57b65-5eb9-4f0a-a507-fbcf3057b248",
    "type": "service"
  }
}

Update Payment Configuration

put
Path parameters
config_idstring · uuidRequiredExample: 23d57b65-5eb9-4f0a-a507-fbcf3057b248
Body
tenant_idstring · uuidRequired
payment_typestringOptionalDefault: pagopa
codestringOptional
descriptionstringOptional
amountnumberRequired
reasonstring · max: 140Optional
expire_atintegerRequired
collection_datastringRequired
activebooleanRequired
office_codestringOptional
office_descriptionstringOptional
reference_codestringOptional
notesstringOptional
Responses
200

Successful Response

application/json
Responseany
422

Validation Error

application/json
put
PUT /payment-proxy/iris/v2/configs/{config_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 727

{
  "tenant_id": "b212c4b4-db26-4404-8c7c-47dab99dd2e6",
  "payment_type": "pagopa",
  "code": "CONTR_PRTPM",
  "description": "Abbonamento aree di sosta",
  "active": true,
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": 0.5,
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    }
  ],
  "amount": 1,
  "reason": "test",
  "expire_at": 180,
  "collection_data": "9/0101100IM/",
  "office_code": "FSE",
  "office_description": "Fondo Sociale Europeo",
  "reference_code": "BOLLO_TIROCINI",
  "notes": "",
  "remote_collection": {
    "id": "3e68161e-9abb-43ac-99e6-39b0ece65b40",
    "type": "service"
  },
  "receiver": {
    "tax_identification_number": "06363391001",
    "name": "Agenzia delle Entrate"
  }
}

No content

Delete Config

delete
Path parameters
config_idstring · uuidRequiredExample: 23d57b65-5eb9-4f0a-a507-fbcf3057b248
Responses
204

Successful Response

422

Validation Error

application/json
delete
DELETE /payment-proxy/iris/v2/configs/{config_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*

No content

Update Existing Payment Configuration

patch
Path parameters
config_idstring · uuidRequiredExample: 23d57b65-5eb9-4f0a-a507-fbcf3057b248
Body
object · NewConfigurationOptional
Responses
200

Successful Response

application/json
Responseany
422

Validation Error

application/json
patch
PATCH /payment-proxy/iris/v2/configs/{config_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Content-Type: application/json
Accept: */*
Content-Length: 589

{
  "payment_type": "pagopa",
  "code": "CONTR_PRTPM",
  "description": "Abbonamento aree di sosta",
  "active": true,
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": 0.5,
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    }
  ],
  "amount": 1,
  "reason": "test",
  "expire_at": 10,
  "collection_data": "9/0101100IM/",
  "office_code": "FSE",
  "office_description": "Fondo Sociale Europeo",
  "reference_code": "BOLLO_TIROCINI",
  "notes": "",
  "remote_collection": {
    "id": "3e68161e-9abb-43ac-99e6-39b0ece65b40",
    "type": "service"
  }
}

No content

Options Config Id

options
Responses
200

Successful Response

application/json
Responseany
options
OPTIONS /payment-proxy/iris/v2/configs/{config_id} HTTP/1.1
Host: api.stanzadelcittadino.it
Accept: */*
200

Successful Response

No content

Versione 2.0

Aggiornamenti principali rispetto alla versione 1.0

NOTA 1: per gli sviluppatori python è stata definita una libreria a cui fare riferimento per la validazione del nuovo evento, è consultabile qui.

NOTA 2: per gli sviluppatori python, il repository da cui prendere spunto per applicare le modifiche riportate in questa sezione e in quelle successive è consultabile qui.

Arricchimento della descrizione del pagamento

Con la versione 2.0 aggiornata del pagamento sono stati aggiunti nuovi campi che arricchiscono la descrizione del pagamento, in particolare i campi sono:

  • type: serve a distinguere tra un pagamento ordinario e una marca da bollo digitale, può avere PAGOPA o STAMP come valori

  • receiver: definisce il beneficiario del pagamento, non è un campo obbligatorio ma può essere utilizzato nel caso di pagamenti con beneficiario differente dall'ente su cui viene creato il pagamento. Se viene valorizzato è obbligatorio valorizzare i campi receiver.tax_identification_number e receiver.name

  • due_type: indica la categorizzazione del dovuto sull'intermediario di pagamento, e viene valorizzato dalla configurazione del pagamento

  • pagopa_category: indica la categorizzazione del dovuto su pagoPA, e viene valorizzato dalla configurazione del pagamento

  • document: è un campo che serve a descrivere il documento su cui viene apposta la marca da bollo digitale, se viene valorizzato è obbligatorio valorizzare il campo document.hash .

  • split: con la versione 2.0 dell'evento, questo campo, che viene alimentato dal bilancio contenuto nella configurazione del pagamento, avrà un tipo ben definito, composto da

    • split.code: indica l'identificativo della voce di bilancio

    • split.amount: indica l'importo della voce di bilancio

    • split.meta: oggetto contenente il resto dei campi presenti nella voce di bilancio

  • links.confirm: è un campo che serve a contenere l'API per forzare il completamento di un pagamento, per il momento tutti i proxy mancano di questa API, ma il campo è stato aggiunto per utilizzi futuri.

  • links.cancel: è un campo che serve a contenere l'API per forzare l'annullamento di un pagamento.

  • debtor: in aggiunta al payer è stato aggiunto un campo equivalente debtor, e serve a distinguere tra il debitore e il pagatore di un pagamento, ad esempio è possibile che un cittadino paghi per conto di terzi e in questo caso e debitore e pagatore sono distinti. Per ora non viene valorizzato né utilizzato.

Nota importante

Alcuni dei campi che vengono popolati in fase di creazione del pagamento sull'intermediario si leggevano solo dall'evento payment. Con l'upgrade delle API (i cui aggiornamenti sono consultabili qui), questi campi potrebbero non essere valorizzati nell'evento, in tal caso, vanno letti invece dalla configurazione inserita via API. I campi in questione sono:

  • payment.reason

  • payment.payment.amount

  • payment.payment.expire_at

  • payment.payment.receiver

In sintesi quindi: se questi campi sono valorizzati nell'evento payment, allora li si legge da lì, altrimenti li si legge dalla configurazione inserita via API.

JSON Evento Pagamento

{
  "id": "68dada78-2398-4a11-b80a-98aaede3371c",
  "user_id": "5b00796e-ef10-4c98-b9f5-b0f7ffd7a17d",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "b21c4429-95e4-45d5-930f-44eb74136625",
  "created_at": "2023-06-06T11:59:11+02:00",
  "updated_at": "2023-06-12T00:10:16+02:00",
  "status": "PAYMENT_STARTED",
  "reason": "79501b2a-c9ad-41f8-a9e7-a885f2d570a2 - BNRMHL75C06G702B",
  "remote_id": "79501b2a-c9ad-41f8-a9e7-a885f2d570a2",
  "payment": {
    "type": "PAGOPA",
    "transaction_id": null,
    "paid_at": null,
    "expire_at": "2023-09-04T11:59:05+02:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": "001550000000024427",
    "iud": "68dada7823984a11b80a98aaede3371c",
    "iuv": "550000000024427",
    "receiver": {
      "tax_identification_number": "777777777",
      "name": "Comune di BlaBla",
      "iban": "IT60X0542811101000000123456",
      "address": "Via Del Campo",
      "building_number": "12",
      "postal_code": "38022",
      "town_name": "Roma",
      "country_subdivision": "RM",
      "country": "IT",
    },
    "due_type": "TARI",
    "pagopa_category": "9/12129/TS",
    "document": null,
    "split": [
      {
        "code": "c_1",
        "amount": 1.00,
        "meta": {}
      },
      {
        "code": "c_2",
        "amount": 0.34,
        "meta": {}
      }
    ]
  },
  "links": {
    "online_payment_begin": {
      "url": "https://iris-proxy-qa.boat.opencontent.io/online-payment/68dada78-2398-4a11-b80a-98aaede3371c?gw=https://iristest.rete.toscana.it/gateway/PaymentAuthentication?token=1686045553436001U132&expire_at=2023-06-06T12:13:13",
      "last_opened_at": "2023-06-06T11:59:26+02:00",
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune-qa.bugliano.pi.it/lang/it/pratiche/79501b2a-c9ad-41f8-a9e7-a885f2d570a2/detail",
      "last_opened_at": "2023-06-06T11:59:57+02:00",
      "method": "GET"
    },
    "offline_payment": {
      "url": "https://iris-proxy-qa.boat.opencontent.io/notice/68dada78-2398-4a11-b80a-98aaede3371c",
      "last_opened_at": "2023-06-06T11:59:20+02:00",
      "method": "GET"
    },
    "receipt": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune-qa.bugliano.pi.it/lang/api/applications/79501b2a-c9ad-41f8-a9e7-a885f2d570a2/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": "http://iris-proxy-qa.boat-backplane.opencontent.io/update/68dada78-2398-4a11-b80a-98aaede3371c",
      "last_check_at": "2023-06-12T00:10:16+02:00",
      "next_check_at": "2023-06-12T01:10:16+02:00",
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": "PATCH"
    },
    "cancel": {
      "url": "https://api.qa.stanzadelcittadino.it/payment-proxy/mypay-trentino/payments/5f2c4be0-effb-47a9-a6dc-a972043580f3",
      "last_opened_at": null,
      "method": "PATCH"
    }
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "e0b25e33-9d38-4781-823a-ed04753aea01",
  "event_version": "2.0",
  "event_created_at": "2023-06-12T00:10:16+02:00",
  "app_id": "iris-payment-proxy-qa:1.2.8"
}

Validazione campi

Payment

Campo
Tipo
Obbligatorio
Validazione

id

UUID

user_id

UUID

type

string(50)

Ogni proxy deve implementare un tipo di pagamento. Se per esempio, il proxy in questione gestisce pagamenti pagopa allora il campo va validato verificando che esso sia valorizzato a PAGOPA. In caso contrario si scarta l'evento e si emette un log di errore.

tenant_id

UUID

service_id

UUID

created_at

Datetime

ISO8601

updated_at

Datetime

ISO8601

status

Enum

Valori permessi: CREATION_PENDING CREATION_FAILED PAYMENT_PENDING PAYMENT_STARTED PAYMENT_CONFIRMED PAYMENT_FAILED NOTIFICATION_PENDING

COMPLETE EXPIRED

reason

string(140)

Causale del pagamento. Non può eccedere i 140 caratteri.

remote_id

UUID

payment

PaymentData

links

Links

payer

Payer

Soggetto Versante

debtor

Debtor

Soggetto Pagatore

event_id

UUID

event_version

string(10)

L'attuale versione dell'evento deve essere "1.0". Ogni proxy deve validare la versione dell'evento in modo da sapere se processarlo o meno.

event_created_at

Datetime

ISO8601

app_id

string(100)

Valorizzarlo nel formato <nome-proxy>:<versione-proxy> Es. iris-payment-proxy:1.3.0

PaymentData

Campo
Tipo
Obbligatorio
Validazione

type

Enum

Valori permessi: PAGOPA nel caso in cui si tratto di un dovuto standard STAMP nel caso in cui si tratta di un dovuto di tipo marca da bollo digitale

transaction_id

string(255)

Fornito dall'intermediario di pagamento. La lunghezza dunque può essere variabile

paid_at

Datetime

ISO8601

expire_at

Datetime

ISO8601 E' già valorizzato a priori, quindi non viene gestito dal proxy

amount

float

E' già valorizzato a priori, quindi non viene gestito dal proxy

reason

string(140)

Causale del pagamento. Non può eccedere i 140 caratteri.

currency

string(3)

ISO4217

notice_code

string(50)

iud

string(50)

è il valore del campo id del pagamento in formato esadecimale Es. id: 4a263efb-300b-437a-ab50-116fa9aff8a7 iud: 4a263efb300b-437aab50116fa9aff8a7

iuv

string(50)

receiver

Receiver

Destinatario o beneficiario del versamento/dovuto

due_type

string(256)

Tipo di dovuto secondo la classificazione dell'intermediario di riferiemento

pagopa_category

string(12)

Tipo di dovuto secondo la ufficiale di pagoPA

document

Document

Contiene le informazioni riguardanti il documento informatico o la segnatura di protocollo cui è associata la marca da bollo digitale

split

List[PaymentDataSplit]

Receiver

Campo
Tipo
Obbligatorio
Validazione

tax_identification_number

string(255)

name

string(255)

iban

string(34)

ISO 13616

street_name

string(255)

building_number

string(255)

postal_code

string(255)

town_name

string(255)

country_subdivision

string(2)

ISO 3166-2

country

string(2)

ISO 3166-1 alpha-2

Document

Campo
Tipo
Obbligatorio
Validazione

id

UUID

Identificativo del documento

ref

string

Url per scaricare il documento

hash

string

Hash digest del documento informatico

PaymentDataSplit

Campo
Tipo
Obbligatorio
Validazione

code

string(50)

amount

float

meta

json

Links

Campo
Tipo
Obbligatorio
Validazione

online_payment_begin

UrlData

online_payment_landing

UrlData

offline_payment

UrlData

receipt

UrlData

notify

List[Notify]

update

Update

confirm

UrlData

cancel

UrlData

UrlData

Campo
Tipo
Obbligatorio
Validazione

url

string

last_opened_at

Datetime

ISO8601

method

Enum

Valori permessi: GET POST PUT PATCH DELETE

Notify

Campo
Tipo
Obbligatorio
Validazione

url

string

method

Enum

Valori permessi: GET POST

sent_at

Datetime

ISO8601

Update

Campo
Tipo
Obbligatorio
Validazione

url

string

last_check_at

Datetime

ISO8601

next_check_at

Datetime

ISO8601

method

Enum

Valori permessi: GET POST

Payer

Campo
Tipo
Obbligatorio
Validazione

type

Enum

Valori permessi: human legal

tax_identification_number

string(255)

name

string(255)

family_name

string(255)

street_name

string(255)

building_number

string(255)

postal_code

string(255)

town_name

string(255)

country_subdivision

string(2)

ISO 3166-2

country

string(2)

ISO 3166-1 alpha-2

email

string(255)

Debtor

Campo
Tipo
Obbligatorio
Validazione

type

Enum

Valori permessi: human legal

tax_identification_number

string(255)

name

string(255)

family_name

string(255)

street_name

string(255)

building_number

string(255)

postal_code

string(255)

town_name

string(255)

country_subdivision

string(2)

ISO 3166-2

country

string(2)

ISO 3166-1 alpha-2

email

string(255)

Flussi

Punti aperti

  1. Determinazione delle URL di ritorno: Come si gestiscono le URL di ritorno? È possibile utilizzare l'indirizzo del proxy per gestire i redirect?

  2. Gestione dell'happy path del pagamento: Come gestire il percorso ideale del pagamento e cosa accade se l'utente chiude la pagina di pagamento e poi ritorna?

Configurazione Pagamento

Happy Path

Error Path

Compilazione Pratica

  1. Compilazione e invio pratica: L'utente compila la pratica per il servizio e invia la richiesta al Core.

  2. Verifica pagamenti: Il Core verifica se, nella fase attuale della pratica, è necessario effettuare dei pagamenti (es. pagamento anticipato o posticipato).

  3. Creazione pagamenti: Il Core invia i dettagli al servizio di pagamento, che restituisce gli identificativi dei pagamenti creati. Questo passaggio è fondamentale, poiché il Core deve salvare i dettagli dei pagamenti all'interno della pratica, assicurandosi che siano coerenti con il flusso (anticipato o posticipato).

  4. Restituzione della pratica: Una volta ottenuti gli ID dei pagamenti, il Core restituisce all'utente l'intera pratica aggiornata con gli identificativi.

Punti aperti:

Gestione degli errori nella creazione dei pagamenti:

  • Cosa accade se la creazione di un pagamento fallisce?

    • Il personale dell'ufficio preposto dovrebbe essere notificato in caso di fallimento.

  • Stato "Creation Pending": Quando un pagamento viene creato, si ottiene comunque un ID associato al pagamento, ma questo potrebbe essere in stato di "creation pending".

  • Politiche di retry: Si potrebbe considerare l'implementazione di politiche di retry, ma è necessario consultare il Product Manager (PM) per decidere la miglior esperienza utente (UX).

    Ipotesi: Al secondo o terzo tentativo di retry (o dopo un timeout di 10 secondi), potremmo mostrare un messaggio d'errore all'utente, suggerendo di riprovare o di avvisare l'assistenza (es. con un link "Clicca qui e avvisa"). Questo sarebbe un errore a livello ERROR.

  • Errore di configurazione: Se si verifica un errore di configurazione errata, è possibile fare affidamento sui messaggi restituiti dall'intermediario. In questo caso, si può evitare il retry e notificare direttamente il responsabile (es. Lorello). Questo tipo di errore potrebbe essere classificato come FATAL.

Happy Path

Error Path - Creazione Pagamenti

Il sequence diagram sopra riportato rappresenta il flusso di creazione e gestione dei pagamenti in un'applicazione, con particolare attenzione al retry in caso di fallimento e alla gestione dello stato del pagamento. Il flusso si svolge come segue:

  1. Inizio del processo: L'utente invia la pratica e il Core richiama il Payment Dispatcher API per creare il pagamento. Se la creazione ha successo, si procede, altrimenti si ritenta fino a 3 volte.

  2. Attesa di aggiornamenti dal Read Model: Quando la creazione del pagamento va a buon fine, il Core si mette in attesa di aggiornamenti dal Read Model.

  3. Retry gestito dal Retry Orchestrator: Se il Read Model restituisce lo stato CREATION_FAILED, il Core delega il processo di retry al Retry Orchestrator, che gestisce i retry tramite Kafka. Il Retry Orchestrator tenterà di risolvere il problema creando il pagamento sull'intermediario esterno.

  4. Risultati del Retry Orchestrator:

    • Se il retry riesce, il Retry Orchestrator segnalerà il successo al Core, che mostrerà all'utente lo stato PAYMENT_PENDING per procedere con il pagamento.

    • Se i retry falliscono, l'utente riceverà un alert di errore.

  5. Conclusione: L'utente vedrà le informazioni di pagamento in caso di successo o un messaggio d'errore in caso di fallimento, dopo aver superato il numero massimo di tentativi.

Error Path - Creazione Carrello Pagamenti

  • Gestione di pagamenti parziali: Se due pagamenti vanno a buon fine e uno fallisce, come dovrebbe comportarsi il frontend? Attualmente, il processo di checkout potrebbe risultare incompleto.

  • Invio degli ID di pagamento: Durante il checkout, il sistema riceve tutti gli ID dei pagamenti che devono essere completati in un’unica operazione.

Creazione pagamento posticipato da operatore

Il sequence diagram sopra riportato rappresenta il flusso di creazione e gestione dei pagamenti posticipati in fase approvazione della pratica da parte dell'operatore. Il flusso si svolge come segue:

  1. Creazione delle configurazioni: L'utente modifica e invia le configurazioni, che vengono temporaneamente salvate tramite il Payment Proxy. Se fallisce, l'errore viene mostrato all'utente.

  2. Creazione del pagamento tramite Payment Dispatcher: Il Core invia la richiesta di creazione pagamento al Payment Dispatcher API. Se fallisce, viene avviato un retry per un massimo di 3 tentativi. Se dopo 3 tentativi non riesce, viene mostrato un errore all'utente.

  3. Attesa di aggiornamenti dal Read Model: Quando la creazione del pagamento va a buon fine, il Core si mette in attesa di aggiornamenti dal Read Model, che monitora lo stato del pagamento.

  4. Stato CREATION_FAILED e Retry Orchestrator:

    • Se il Read Model restituisce lo stato CREATION_FAILED, il Core delega il retry al Retry Orchestrator, che gestisce i retry attraverso i topic di Kafka.

    • Se il Retry Orchestrator completa con successo la creazione, il Read Model aggiorna lo stato del pagamento, e l'utente può procedere con il pagamento (PAYMENT_PENDING).

    • Se anche il Retry Orchestrator fallisce dopo vari tentativi, viene restituito un errore al Core e un alert viene mostrato all'utente.

  5. Output finale: Se il pagamento va a buon fine (PAYMENT_PENDING), l'utente riceve le informazioni per procedere con il pagamento. In caso di errore persistente, viene mostrato un alert.

tassonomia

Documento digitale

Descrizione in dettaglio di un documento generato dalla piattaforma

Contesto

Secondo l'AGID, il documento informatico è la “rappresentazione informatica di atti, fatti o dati giuridicamente rilevanti" in contrapposizione al documento analogico ("rappresentazione non informatica di atti, fatti o dati giuridicamente rilevanti")".

La piattaforma genera documenti informatici a partire da diversi tipi di servizi offerti.

Il documento informatico ci permette di avere un'unica struttura dati a partire da diverse tipologie di servizi. Questo ci dà il vantaggio di dover gestire un’unica entità durante il processo di protocollazione.

Struttura

Il documento generato dall’area personale del cittadino è stato progettato seguendo le linee guida suggerite dall’AGID. Esso è composto dai seguenti elementi:

  • Documento principale: descrive in dettaglio il servizio o l'oggetto a cui il documento si riferisce.

  • Allegati del documento principale: rappresentano l'insieme dei file che accompagnano la documento principale, fornendo ulteriori dettagli, dati o documentazione di supporto.

  • metadati: includono informazioni utili per la comprensione del contesto del documento informatico. Ci forniscono dettagli sulle informazioni di base, come la data di creazione, il mittente, il destinatario, e altri dettagli pertinenti. Alcuni di essi non possono mancare(vedi sezione seguente).

Specifiche funzionali del documento

Nella nostra piattaforma il documento è implementato in forma di evento Kafka, in formato JSON, descritto dal seguente JSON SCHEMA.

Per una migliore visualizzazione si consiglia di usare un viewer online.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema#",
  "$id": "https://schema.opencontent.io/document.schema#",
  "type": "object",
  "title": "Documento",
  "definitions": {
    "registration_info": {
      "type": "object",
      "title": "Informazioni sul protocollo del documento",
      "properties": {
        "transmission_type": {
          "title": "Tipo di trasmissione",
          "type": "string",
          "enum": [
            "inbound",
            "outbound",
            "internal"
          ],
          "examples": [
            "inbound"
          ]
        },
        "date": {
          "title": "Data protocollo",
          "type": "string",
          "format": "date-time",
          "examples": [
            "2022-08-24T11:59:57+02:00"
          ]
        },
        "document_number": {
          "title": "Numero di protocollo",
          "description": "Numero di protocollo assegnato dal sistema di protocollazione",
          "type": "string",
          "maxLength": 255,
          "examples": [
            "Prot-123-456-789",
            "123"
          ]
        }
      },
      "required": [
        "transmission_type",
        "date",
        "document_number"
      ]
    },
    "folder_info": {
      "type": "object",
      "title": "Informazioni sul fascicolo",
      "properties": {
        "title": {
          "title": "Oggetto del Fascicolo",
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255
        },
        "id": {
          "title": "Numero di Fascicolo",
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255
        }
      },
      "required": [
        "title",
        "id"
      ]
    },
    "file": {
      "title": "File",
      "type": "object",
      "properties": {
        "name": {
          "title": "Titolo del file",
          "type": "string"
        },
        "description": {
          "title": "Descrizione del file",
          "type": [
            "string",
            "null"
          ]
        },
        "mime_type": {
          "title": "Mime type del file",
          "type": "string"
        },
        "url": {
          "title": "Download url della risorsa",
          "type": "string",
          "format": "uri"
        },
        "md5": {
          "title": "Hash md5 del binario",
          "type": "string"
        },
        "filename": {
          "title": "Nome completo del file con estensione",
          "type": "string"
        },
        "internal_url": {
          "title": "Eventuale url interno della risorsa a disposizione dei sistemi dell'ente che sarà utilizzato per servire il binario se questo non viene salvato nello storage della piattaforma",
          "type": [
            "string",
            "null"
          ],
          "format": "uri"
        }
      },
      "required": [
        "name",
        "filename",
        "description",
        "mime_type",
        "url",
        "md5"
      ]
    },
    "image": {
      "type": "object",
      "title": "Risorsa immagine",
      "properties": {
        "name": {
          "title": "Titolo dell'immagine",
          "type": "string"
        },
        "filename": {
          "title": "Nome dell'immagine",
          "type": "string"
        },
        "description": {
          "title": "Descrizione dell'immagine",
          "type": "string"
        },
        "license": {
          "title": "Licenza di utilizzo",
          "type": "string",
          "enum": [
            "?",
            "??",
            "???"
          ]
        },
        "type": {
          "title": "Tipologia dell'immagine o mime-type",
          "type": "string"
        },
        "url": {
          "title": "Url della risorsa",
          "type": "string",
          "format": "uri"
        }
      },
      "required": [
        "name",
        "filename",
        "license",
        "type",
        "url",
        "description"
      ]
    },
    "author": {
      "type": "object",
      "title": "Autore",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "human",
            "legal"
          ]
        },
        "tax_identification_number": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "family_name": {
          "type": [
            "string",
            "null"
          ]
        },
        "street_name": {
          "type": [
            "string",
            "null"
          ]
        },
        "building_number": {
          "type": [
            "string",
            "null"
          ]
        },
        "postal_code": {
          "type": [
            "string",
            "null"
          ]
        },
        "town_name": {
          "type": [
            "string",
            "null"
          ]
        },
        "country_subdivision": {
          "type": [
            "string",
            "null"
          ]
        },
        "country": {
          "type": [
            "string",
            "null"
          ]
        },
        "email": {
          "type": "string",
          "format": "email"
        }
      },
      "required": [
        "type",
        "tax_identification_number",
        "name",
        "family_name",
        "street_name",
        "building_number",
        "postal_code",
        "town_name",
        "country_subdivision",
        "country",
        "email",
        "role"
      ]
    }
  },
  "properties": {
    "title": {
      "type": "string",
      "maxLength": 255,
      "title": "Titolo del documento",
      "description": "Nome del documento. Il nome del Documento deve essere facilmente comprensibile dai cittadini. Vincoli: massimo 160 caratteri spazi inclusi",
      "examples": [
        "Istanza di certificato anagrafico"
      ]
    },
    "id": {
      "type": "string",
      "format": "uuid",
      "title": "Identificativo interno del documento",
      "description": "Identificatore uuid ad uso interno",
      "examples": [
        "4a68415b-e1a8-4f3a-a229-3f6678fd81d1"
      ]
    },
    "app_id": {
      "title": "App ID",
      "type": "string",
      "examples": [
        "document-dispatcher:1.1.1"
      ]
    },
    "event_created_at": {
      "title": "Data di creazione dell'evento",
      "type": "string",
      "format": "date-time",
      "examples": [
        "2023-11-23T14:14:23+00:00"
      ]
    },
    "event_id": {
      "title": "ID dell'evento",
      "type": "string",
      "format": "uuid"
    },
    "event_version": {
      "type": "integer",
      "title": "Versione del documento",
      "description": "Versione del documento",
      "default": 1,
      "examples": [
        1
      ]
    },
    "external_id": {
      "type": [
        "string",
        "null"
      ],
      "title": "Identificativo del documento ad uso dell'ente",
      "description": "Un numero identificativo del documento (es DOI, ISBN)",
      "examples": [
        "Cert567-2023",
        "4a68415b-e1a8-4f3a-a229-3f6678fd81d1"
      ]
    },
    "registration_data": {
      "title": "Protocollo",
      "anyOf": [
        {
          "title": "Informazioni sul protocollo del documento",
          "$ref": "#/definitions/registration_info"
        },
        {
          "title": "Nessuna informazione sul protocollo",
          "type": "null"
        }
      ]
    },
    "folder": {
      "title": "Fascicolo",
      "anyOf": [
        {
          "title": "Informazioni sul fascicolo",
          "$ref": "#/definitions/folder_info"
        },
        {
          "title": "Nessuna informazione sul fascicolo",
          "type": "null"
        }
      ]
    },
    "status": {
      "title": "Stato del documento",
      "description": "Stato del documento: descrive lo stato di protocollazione del documento",
      "type": "string",
      "enum": [
        "DOCUMENT_CREATED",
        "REGISTRATION_PENDING",
        "REGISTRATION_FAILED",
        "PARTIAL_REGISTRATION",
        "REGISTRATION_COMPLETE"
      ]
    },
    "type": {
      "title": "Tipo di documento",
      "description": "Tipologia del documento: viene utilizzata una tassonomia custom che va intesa come sotto tassonomia di Istanza https://schema.gov.it/lodview/controlled-vocabulary/classifications-for-documents/government-documents-types/8",
      "type": "string",
      "enum": [
        "application-request",
        "integration-request",
        "integration-response",
        "application-outcome",
        "application-withdraw",
        "application-revocation"
      ]
    },
    "remote_id": {
      "title": "Pratica associata",
      "description": "Identificativo della pratica associata",
      "type": [
        "string",
        "null"
      ],
      "format": "uuid",
      "examples": [
        "842b37e7-d835-49f0-92e2-a393ecda2e53"
      ]
    },
    "remote_collection": {
      "title": "Servizio collegato",
      "anyOf": [
        {
          "title": "Servizio collegato",
          "description": "Se il documento non è collegato a una pratica è possibile indicare il servizio che ha generato il documento",
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "842b37e7-12a4-49f0-92e2-a393ecda2e53"
              ]
            },
            "type": {
              "type": "string",
              "enum": [
                "service",
                "service-group",
                "subscription",
                "calendar"
              ],
              "examples": [
                "service"
              ]
            }
          },
          "required": [
            "id",
            "type"
          ]
        },
        {
          "title": "Nessuna servizio collegato",
          "type": "null"
        }
      ]
    },
    "topics": {
      "title": "Argomenti",
      "description": "Argomenti collegabili",
      "type": "array",
      "minItems": 0,
      "items": {
        "title": "Argomento",
        "type": "string",
        "minItems": 0
      }
    },
    "short_description": {
      "title": "Descrizione breve",
      "description": "Descrizione sintetica del documento (max 255 caratteri) utilizzando un linguaggio semplice che possa aiutare qualsiasi utente a identificare con chiarezza il documento. Non utilizzare un linguaggio ricco di riferimenti normativi",
      "type": "string",
      "maxLength": 255
    },
    "description": {
      "title": "Descrizione",
      "description": "L'oggetto del documento, utilizzando un linguaggio semplice che possa aiutare qualsiasi utente a identificare con chierazza il documento. Non utilizzare un linguaggio ricco di riferimenti normativi.",
      "type": [
        "string",
        "null"
      ]
    },
    "main_document": {
      "title": "URL documento",
      "$ref": "#/definitions/file"
    },
    "image_gallery": {
      "title": "Galleria di immagini",
      "type": "array",
      "items": {
        "$ref": "#/definitions/image"
      },
      "minItems": 0
    },
    "has_organization": {
      "title": "Ufficio responsabile del documento",
      "anyOf": [
        {
          "title": "Link alla scheda dell'ufficio responsabile del documento",
          "type": "string",
          "format": "uri",
          "examples": [
            "https://example.com/offices/1"
          ]
        },
        {
          "title": "Nessuna informazione sull'ufficio",
          "type": "null"
        }
      ]
    },
    "attachments": {
      "title": "Allegati",
      "description": "Altre risorse oltre alla principale",
      "type": "array",
      "minItems": 0,
      "items": {
        "$ref": "#/definitions/file"
      }
    },
    "distribution_license_id": {
      "title": "Licenza di distribuzione",
      "type": [
        "string",
        "null"
      ]
    },
    "related_public_services": {
      "title": "Servizi collegati",
      "description": "Servizi che usano questo documento come input",
      "type": "array",
      "items": {
        "type": "object",
        "title": "Servizio collegato",
        "properties": {
          "name": {
            "id": "Id del servizio",
            "type": "string"
          },
          "type": {
            "title": "Tipologia del servizio",
            "type": "string"
          },
          "url": {
            "title": "Descrizione del file",
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "id",
          "type",
          "url"
        ]
      },
      "minItems": 0
    },
    "valid_from": {
      "title": "Data inizio validità",
      "description": "Data da cui il documento è valido",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "valid_to": {
      "title": "Data fine validità",
      "description": "Data da cui il documento non è più valido",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "removed_at": {
      "title": "Data ultima disponibilità",
      "description": "Data fino alla quale il documento sarà disponibile online",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "expire_at": {
      "title": "Data scadenza",
      "description": "Solitamente uguale a fine validità, è la data entro la quale il documento deve essere eventualmente rinnovato",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "more_info": {
      "title": "Ulteriori informazioni",
      "description": "Ulteriori informazioni sul documento",
      "type": [
        "string",
        "null"
      ]
    },
    "normative_requirements": {
      "title": "Riferimenti normativi",
      "description": "Lista di link con riferimenti normativi utili per il documento",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "string",
        "format": "uri"
      }
    },
    "related_documents": {
      "title": "Documenti collegati",
      "description": "Lista di documenti allegati: link a quelli strutturati a loro volta come documenti",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "string",
        "format": "uri"
      }
    },
    "life_events": {
      "title": "Life Events",
      "description": "Life Events collegabili",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "string"
      }
    },
    "business_events": {
      "title": "Business Events",
      "description": "Business Events collegabili",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "string"
      }
    },
    "allowed_readers": {
      "title": "Lista di identificatori di persone che possono accedere in lettura al documento",
      "description": "??? Vedi onotologia...",
      "type": "array",
      "minItems": 0,
      "items": {
        "type": "string"
      }
    },
    "tenant_id": {
      "title": "Ente",
      "type": "string",
      "format": "uuid",
      "description": "Uuid del tenant",
      "examples": [
        "842b37e7-d835-49f0-92e2-a393ecda2e53"
      ]
    },
    "owner_id": {
      "title": "Proprietario",
      "description": "Uuid del proprietario del documento",
      "type": "string",
      "format": "uuid"
    },
    "document_url": {
      "title": "Url pubblico del documento",
      "type": [
        "string",
        "null"
      ],
      "format": "uri"
    },
    "created_at": {
      "title": "Data di creazione",
      "type": "string",
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "updated_at": {
      "title": "Data ultima modifica",
      "type": "string",
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    },
    "author": {
      "title": "Autore/i",
      "description": "Persone che hanno redatto il documento",
      "type": "array",
      "minItems": 0,
      "items": {
        "$ref": "#/definitions/author"
      }
    },
    "source_type": {
      "title": "Tipologia del soggetto che ha generato il documento",
      "description": "In base a questo che si identifica la transmission type",
      "type": "string",
      "enum": [
        "tenant",
        "user"
      ]
    },
    "recipient_type": {
      "title": "Tipologia di destinatario del documento",
      "description": "In base a questo che si identifica la transmission type",
      "type": "string",
      "enum": [
        "tenant",
        "user"
      ]
    },
    "last_seen": {
      "title": "Data ultima visualizzazione da parte del destinatario",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "examples": [
        "2022-08-24T11:59:57+02:00"
      ]
    }
  },
  "required": [
    "title",
    "id",
    "event_version",
    "app_id",
    "status",
    "event_created_at",
    "event_id",
    "type",
    "short_description",
    "main_document",
    "tenant_id",
    "owner_id",
    "created_at",
    "updated_at",
    "source_type",
    "recipient_type"
  ]
}

Struttura e mapping

Campo
Tipo
Obbligatorio
Validazione

title

String

✅

MaxLength: 255, descrizione chiara del documento, massimo 160 caratteri spazi inclusi

id

String (UUID)

✅

Formato: UUID, identificativo univoco

app_id

String

✅

-

event_created_at

String (DateTime)

✅

Formato: DateTime

event_id

String (UUID)

✅

Formato: UUID

event_version

Integer

✅

Valore di default: 1

external_id

String, Null

❌

-

registration_data

Object, Null

❌

Informazioni di protocollo

folder

Object, Null

❌

Informazioni sul fascicolo

status

String

✅

Valori: DOCUMENT_CREATED, REGISTRATION_PENDING, REGISTRATION_FAILED, PARTIAL_REGISTRATION, REGISTRATION_COMPLETE

type

String

✅

Tassonomia di Istanza: application-request, integration-request, integration-response, etc.

remote_id

String (UUID), Null

❌

-

remote_collection

Object, Null

❌

-

topics

Array[String]

❌

MinItems: 0

short_description

String

✅

MaxLength: 255

description

String, Null

❌

-

main_document

Object

✅

-

image_gallery

Array[Object]

❌

MinItems: 0

has_organization

String (URI), Null

❌

-

attachments

Array[Object]

❌

MinItems: 0

distribution_license_id

String, Null

❌

-

related_public_services

Array[Object]

❌

-

valid_from

String (DateTime), Null

❌

-

valid_to

String (DateTime), Null

❌

-

removed_at

String (DateTime), Null

❌

-

expire_at

String (DateTime), Null

❌

-

more_info

String, Null

❌

-

normative_requirements

Array[URI]

❌

-

related_documents

Array[URI]

❌

-

life_events

Array[String]

❌

MinItems: 0

business_events

Array[String]

❌

MinItems: 0

allowed_readers

Array[String]

❌

MinItems: 0

tenant_id

String (UUID)

✅

Uuid del tenant

owner_id

String (UUID)

✅

Uuid del proprietario del documento

document_url

String (URI), Null

❌

-

created_at

String (DateTime)

✅

-

updated_at

String (DateTime)

✅

-

author

Array[Object]

❌

MinItems: 0

source_type

String

✅

Valori: tenant, user

recipient_type

String

✅

Valori: tenant, user

last_seen

String (DateTime), Null

❌

-

registration_data

Campo
Tipo
Obbligatorio
Validazione

transmission_type

String

✅

Valori: Inbound, Outbound

date

String (Date)

❌

Data valida in formato YYYY-MM-DD

document_number

String

❌

Numero del documento

folder

Campo
Tipo
Obbligatorio
Validazione

title

String

✅

Titolo descrittivo del fascicolo

id

String

❌

Identificativo del fascicolo

main_document

Campo
Tipo
Obbligatorio
Validazione

name

String

✅

Nome del file

description

String

✅

Descrizione del documento

mime_type

String

✅

Formato MIME, es: application/pdf

url

String (URI)

✅

URL del documento

md5

String

❌

Hash MD5 del file

filename

String

✅

Nome del file salvato sul server

attachments

Campo
Tipo
Obbligatorio
Validazione

name

String

✅

Nome del file allegato

description

String

✅

Descrizione dell'allegato

mime_type

String

✅

Formato MIME, es: application/pdf

url

String (URI)

✅

URL dell'allegato

md5

String

❌

Hash MD5 del file

filename

String

✅

Nome del file salvato sul server

author

Campo
Tipo
Obbligatorio
Validazione

type

String

✅

Valori: human, legal

tax_identification_number

String

✅

Codice fiscale

name

String

✅

Nome dell'autore

family_name

String

✅

Cognome dell'autore

street_name

String

✅

Indirizzo

postal_code

String

✅

Codice postale

email

String

✅

Formato: email valido

role

String

✅

Ruolo (es: sender, receiver)

Struttura

Un pagamento in dettaglio

Descrizione dettagliata di tutti i passaggi che portano alla creazione e alla chiusura di un pagamento a partire da una pratica.

Premessa

Essendo il sistema in questione costruito su un'architettura a eventi, è fondamentale che il payment proxy controlli la versione dell'evento Payment (campo event_version) prima di processarlo e dichiari con quale versione dell'evento quest'ultimo è compatibile. L'evento qui descritto ha versione 2.0.

Step 1

L'utente compila e invia la pratica, la quale viene inserita come evento nel topic applications

Un esempio di evento è il seguente, si faccia particolare attenzione al blocco payment_data:

{
  "app_version": "2.6.0-rc.7",
  "attachments": [],
  "authentication": {
    "authentication_method": "cps/cns",
    "certificate": "DQpSZXN1bHQgZ29lcyBoZXJlLi4uDQpCYXNlNjQNCg0KQmFzZTY0IGlzIGEgZ2VuZXJpYyB0ZXJtIGZvciBhIG51bWJlciBvZiBzaW1pbGFyIGVuY29kaW5nIHNjaGVtZXMgdGhhdCBlbmNvZGUgYmluYXJ5IGRhdGEgYnkgdHJlYXRpbmcgaXQgbnVtZXJpY2FsbHkgYW5kIHRyYW5zbGF0aW5nIGl0IGludG8gYSBiYXNlIDY0IHJlcHJlc2VudGF0aW9uLiBUaGUgQmFzZTY0IHRlcm0gb3JpZ2luYXRlcyBmcm9tIGEgc3BlY2lmaWMgTUlNRSBjb250ZW50IHRyYW5zZmVyIGVuY29kaW5nLg==",
    "certificate_issuer": "FAKE_issuerdn",
    "certificate_subject": "FAKE_subjectdn",
    "instant": "2000-01-01T00-00Z",
    "session_id": "abc123abc123abc123abc123abc123abc123abc123",
    "session_index": "abc123abc123abc123abc123abc123abc123abc123",
    "spid_code": null,
    "spid_level": null
  },
  "backoffice_data": null,
  "compiled_modules": [],
  "created_at": "2022-12-06T17:43:42+01:00",
  "creation_time": 1670345022,
  "data": {
    "applicant.data.Born.data.natoAIl": "1976-09-01T00:00:00+02:00",
    "applicant.data.Born.data.place_of_birth": "Ponte di Piave",
    "applicant.data.address.data.address": "Via Gramsci, 1",
    "applicant.data.address.data.county": "PI",
    "applicant.data.address.data.house_number": "",
    "applicant.data.address.data.municipality": "Bugliano",
    "applicant.data.address.data.postal_code": "56056",
    "applicant.data.completename.data.name": "Vittorino",
    "applicant.data.completename.data.surname": "Coliandro",
    "applicant.data.email_address": "[email protected]",
    "applicant.data.fiscal_code.data.fiscal_code": "CLNVTR76P01G822Q",
    "applicant.data.gender.data.gender": "maschio",
    "payment_amount": "1,34"
  },
  "event_created_at": "2022-12-06T17:43:51+01:00",
  "event_id": "01e86cd4-8263-4ac0-a229-8cada5916eae",
  "event_version": 2,
  "flow_changed_at": "2022-12-06T17:43:51+01:00",
  "id": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e",
  "integrations": [],
  "latest_status_change_at": "2022-12-06T17:43:51+01:00",
  "latest_status_change_time": 1670345031,
  "links": [],
  "meetings": [],
  "outcome": null,
  "outcome_attachments": [],
  "outcome_file": null,
  "outcome_motivation": null,
  "outcome_protocol_document_id": null,
  "outcome_protocol_number": null,
  "outcome_protocol_numbers": null,
  "outcome_protocol_time": null,
  "outcome_protocolled_at": null,
  "path": "/applications",
  "payment_data": {
    "amount": "1.34",
    "expire_at": "2023-03-06T17:43:51+01:00",
    "landing": {
      "method": "GET",
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/detail"
    },
    "notify": {
      "method": "POST",
      "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/payment"
    },
    "reason": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e - CLNVTR76P01G822Q",
    "split": []
  },
  "payment_type": "efil",
  "protocol_document_id": null,
  "protocol_folder_code": null,
  "protocol_folder_number": null,
  "protocol_number": null,
  "protocol_numbers": [],
  "protocol_time": null,
  "protocolled_at": null,
  "service": "pagamento-immediato-efil",
  "service_group_name": null,
  "service_id": "daa0f528-b582-4d1c-9691-e226ac443424",
  "service_name": "Pagamento immediato EFIL",
  "source_type": "http",
  "status": "1500",
  "status_name": "status_payment_pending",
  "subject": null,
  "submission_time": null,
  "submitted_at": null,
  "tenant": "60e35f02-1509-408c-b101-3b1a28109329",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "timestamp": "2022-12-06T16:43:51.829146577Z",
  "user": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "user_compilation_notes": null,
  "user_name": "Vittorino Coliandro",
  "x-forwarded-for": "172.31.73.11"
}

Step 2

Il payment dispatcher legge l'evento della pratica dal topic applications, e dopo aver controllato mediante query su KSQLDB che il pagamento per tale pratica non sia già stato creato, estrae da esso i dati del pagamento, successivamente li riversa in un nuovo evento di tipo Payment e lo inserisce nel topic payments in stato CREATION_PENDING, indicando come chiave dell'evento il valore del campo service_id. Da qui in poi solo il proxy si occuperà di scrivere sul topic payments.

{
  "id": "2cc87ee5-3f57-4f20-a703-e6718c21b95b",
  "user_id": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "daa0f528-b582-4d1c-9691-e226ac443424",
  "created_at": "2022-12-06T17:43:52+01:00",
  "updated_at": "2022-12-06T17:43:52+01:00",
  "status": "CREATION_PENDING",
  "reason": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e - CLNVTR76P01G822Q",
  "remote_id": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e",
  "payment": {
    "transaction_id": null,
    "paid_at": null,
    "expire_at": "2023-03-06T17:43:51+01:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": null,
    "iud": "2cc87ee53f574f20a703e6718c21b95b",
    "iuv": null,
    "receiver": null,
    "due_type": null,
    "pagopa_category": "9/12129/TS",
    "document": null,
    "split": []
  },
  "links": {
    "online_payment_begin": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/detail",
      "last_opened_at": null,
      "method": "GET"
    },
    "offline_payment": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "receipt": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": null,
      "last_check_at": null,
      "next_check_at": null,
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": null
    },
    "cancel": {
      "url": null
      "last_opened_at": null,
      "method": null
    }    
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "CLNVTR76P01G822Q",
    "name": "Vittorino",
    "family_name": "Coliandro",
    "street_name": "Via Gramsci, 1",
    "building_number": "",
    "postal_code": "56056",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "c3ecf316-ca76-4bf3-ab72-26b941a5ce4f",
  "event_version": "2.0",
  "event_created_at": "2022-12-06T17:43:52+01:00",
  "app_id": "payment-dispatcher:1.1.3"
}

Step 3

Il proxy legge dal topic payments e, se ha una configurazione attiva per quel pagamento (quindi se l'evento letto ha un tenant_id e un service_id per cui ha una configurazione salvata nello storage del proxy), crea una posizione debitoria sull'Intermediario di riferimento.

Step 3 - Marca da Bollo Digitale

È possibile che la configurazione relativa al pagamento letto dal topic payments abbia configurato al suo interno una marca da bollo digitale. In tal caso, sarà necessario, per ogni marca da bollo digitale, inserire come minimo le seguenti informazioni all'interno del payload da inviare all'intermediario di pagamento con cui ci si sta integrando (in base a poi all'intermediario potrebbero essere necessari altri campi):

  1. Il taglio della marca da bollo o l'importo inserito nella configurazione (per ora l'unico importo supportato è quello da 16 €)

  2. La provincia di residenza del pagatore, la quale si trova nel campo payer.country_subdivision dell'evento payment

  3. L'hash del documento informatico o della segnatura di protocollo cui è associata la marca da bollo digitale (per semplicità il documento in questione sarà il json dell'evento letto dal topic payments)

  4. Il valore del campo reason letto dall'evento nel topic payments o, in assenza di questo, la causale della marca da bollo inserita nella configurazione

Successivamente, sarà necessario:

  • Valorizzare il campo payment.amount l'importo della marca da bollo inserita in configurazione

  • Valorizzare il campo payment.due_type con il tipo di dovuto inserito nella configurazione

  • Valorizzare il campo payment.pagopa_category con il valore della tassonomia di pagoPA contenuta nella configurazione (se presente)

  • Valorizzare il campo payment.document.hash contenente l'hash del documento informatico o della segnatura di protocollo cui è associata la marca da bollo digitale

Step 3 - Bilancio fisso

ATTENZIONE: questa struttura sarà presente solo sugli eventi payment con event_version valorizzato a 2.0 , sarà necessario dunque controllare la versione dell'evento prima di processarlo

In alcuni casi è possibile che il pagamento che il proxy deve processare presenti un bilancio fisso, ovvero un bilancio i cui importi per ogni voce sono già predeterminati a priori in fase di configurazione del servizio. In tal caso il campo split, quando letto dal proxy per la prima volta, si presenterà come un array vuoto [], il proxy dunque:

  1. Leggerà il campo service_id e reperirà nel mediante esso la configurazione del servizio dentro il quale ci sono le informazioni del bilancio.

  2. Utilizzerà le informazioni ricavate per popolare il payload di creazione della posizione debitoria sull'intermediario di riferimento e per arricchire l'evento appena letto con i dati del bilancio.

Esempio

Nel proxy è configurato per il servizio A il seguente bilancio

{
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": "16.00",
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    },
    {
      "split_id": "c_2",
      "split_type": "Tipo c2",
      "split_code": "Codice c2",
      "split_description": "Descrizione c12",
      "split_assessment": "0.50",
      "split_budget_chapter": "Capitolo di bilancio c2",
      "split_assessment": "Accertamento c2"
    }
  ]
}

Il bilancio da utilizzare per arricchire il campo split del pagamento sarà:

{
  "split": [
    {
      "code": "c_1",
      "amount": "16.00",
      "meta": {
        "split_type": "Tipo c1",
        "split_code": "Codice c1",
        "split_description": "Descrizione c1",
        "split_budget_chapter": "Capitolo di bilancio c1",
        "split_assessment": "Accertamento c1"
      }
    },
    {
      "code": "c_2",
      "amount": "0.50",
      "meta": {
        "split_type": "Tipo c2",
        "split_code": "Codice c2",
        "split_description": "Descrizione c12",
        "split_budget_chapter": "Capitolo di bilancio c2",
        "split_assessment": "Accertamento c2"
      }
    }
  ]
}

Step 3 - Bilancio variabile

In alcuni casi è possibile che il pagamento che il proxy deve processare presenti un bilancio variabile, ovvero un bilancio i cui importi per ogni voce vengono determinati dinamicamente a seconda del cittadino. In tal caso il campo split , quando letto dal proxy per la prima volta, si presenterà con un payload così strutturato

[
  {
    "code": "c_1",
    "amount" : "14.00",
    "meta": {}
  },
  {
    "code": "c_2",
    "amount" : "2.50",
    "meta": {}
  }
]

oppure così nel caso in cui il cittadino sia esentato dal pagamento di una certa voce del bilancio

[
  {
    "code": "c_1",
    "amount" : null,
    "meta": {}
  },
  {
    "code": "c_2",
    "amount" : "0.50",
    "meta": {}
  }
]

dove il campo code indica un identificativo interno della voce di bilancio determinato in fase di configurazione del servizio, e il campo amount indica l'importo per ogni voce di bilancio. Nel caso in cui il valore di amount sia null significa che quella riga deve essere eliminata in fase di creazione del pagamento sul gateway. Il proxy dunque:

  1. Leggerà il campo service_id e reperirà mediante esso la configurazione del servizio dentro il quale ci sono le informazioni del bilancio.

  2. Per ogni voce di bilancio ricavata dalla configurazione del servizio verrà sostituito l'importo con quello letto nel campo split del pagamento

  3. Utilizzerà le informazioni ricavate per popolare il payload di creazione della posizione debitoria sull'intermediario di riferimento e per arricchire l'evento appena letto con i dati del bilancio.

Esempio 1

Nel proxy è configurato per il servizio A il seguente bilancio

{
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": "16.00",
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    },
    {
      "split_id": "c_2",
      "split_type": "Tipo c2",
      "split_code": "Codice c2",
      "split_description": "Descrizione c12",
      "split_assessment": "0.50",
      "split_budget_chapter": "Capitolo di bilancio c2",
      "split_assessment": "Accertamento c2"
    }
  ]
}

Nel messaggio del topic payments il campo split è valorizzato a

[
  {
    "code": "c_1",
    "amount" : "14.00",
    "meta": {}
  },
  {
    "code": "c_2",
    "amount" : "2.50",
    "meta": {}
  }
]

Il bilancio da utilizzare per arricchire il campo split del pagamento sarà:

{
  "split": [
    {
      "code": "c_1",
      "amount": "14.00",
      "meta": {
        "split_type": "Tipo c1",
        "split_code": "Codice c1",
        "split_description": "Descrizione c1",
        "split_budget_chapter": "Capitolo di bilancio c1",
        "split_assessment": "Accertamento c1"
      }
    },
    {
      "code": "c_2",
      "amount": "2.50",
      "meta": {
        "split_type": "Tipo c2",
        "split_code": "Codice c2",
        "split_description": "Descrizione c12",
        "split_budget_chapter": "Capitolo di bilancio c2",
        "split_assessment": "Accertamento c2"
      }
    }
  ]
}

Esempio 2

Nel proxy è configurato per il servizio a il seguente bilancio

{
  "split": [
    {
      "split_id": "c_1",
      "split_type": "Tipo c1",
      "split_code": "Codice c1",
      "split_description": "Descrizione c1",
      "split_amount": "16.00",
      "split_budget_chapter": "Capitolo di bilancio c1",
      "split_assessment": "Accertamento c1"
    },
    {
      "split_id": "c_2",
      "split_type": "Tipo c2",
      "split_code": "Codice c2",
      "split_description": "Descrizione c12",
      "split_assessment": "0.50",
      "split_budget_chapter": "Capitolo di bilancio c2",
      "split_assessment": "Accertamento c2"
    }
  ]
}

Nel messaggio del topic payments il campo split è valorizzato a

[
  {
    "code": "c_1",
    "amount" : null,
    "meta": {}
  },
  {
    "code": "c_2",
    "amount" : "0.50",
    "meta": {}
  }
]

Il bilancio da utilizzare per arricchire il campo split del pagamento sarà:

{
  "split": [
    {
      "code": "c_2",
      "amount": "0.50",
      "meta": {
        "split_type": "Tipo c2",
        "split_code": "Codice c2",
        "split_description": "Descrizione c12",
        "split_budget_chapter": "Capitolo di bilancio c2",
        "split_assessment": "Accertamento c2"
      }
    }
  ]
}

Step 4

In caso di risposta positiva:

  1. Il pagamento viene passato in stato PAYMENT_PENDING modificando il campo status,

  2. Vengono compilate le informazioni del bilancio nel campo split (se presenti),

  3. Viene compilata l'url per pagare online (links.online_payment_begin.url), la quale è data dalla concatenazione della variabile d'ambiente EXTERNAL_API_URL alla stringa /online-payment/{payment_id}

  4. Viene compilata l'url di ritorno dal pagamento (links.online_payment_landing.url), la quale è data dalla concatenazione della variabile d'ambiente EXTERNAL_API_URL alla stringa /landing/{payment_id}

  5. Viene compilata l'url per pagare offline (links.offline_payment.url), la quale è è data dalla concatenazione della variabile d'ambiente EXTERNAL_API_URL alla stringa /offline-payment/{payment_id}

  6. Viene compilata l'url della ricevuta di pagamento (links.receipt.url), la quale è data dalla concatenazione della variabile d'ambiente EXTERNAL_API_URL alla stringa /receipt/{payment_id}

  7. Viene compilata l'url per richiedere l'aggiornamento del pagamento (links.update.url), la quale è data dalla concatenazione della variabile d'ambiente INTERNAL_API_URL alla stringa /update/{payment_id}

    1. Nota: a differenza dell'altre url, questa viene valorizzata utilizzando una variabile d'ambiente separata in quanto deve essere possibile chiamarla pubblicamente, questo perchè è compito del solo payments-poller richiedere periodicamente l'aggiornamento del pagamento.

  8. Viene compilata l'url per forzare l'annullamento di un pagamento (links.cancel.url), la quale è data dalla concatenazione della variabile d'ambiente EXTERNAL_API_URL alla stringa /payments/{payment_id} . Viene inoltre compilato il metodo con cui chiamare la url (links.cancel.method) con il valore PATCH.

  9. Viene segnato il timestamp di aggiornamento dell'evento di pagamento (updated_at)

  10. Viene infine salvato il pagamento sullo storage e scritto il corrispondente evento aggiornato sul topic payments, indicando come chiave dell'evento il valore del campo service_id.

{
  "id": "2cc87ee5-3f57-4f20-a703-e6718c21b95b",
  "user_id": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "daa0f528-b582-4d1c-9691-e226ac443424",
  "created_at": "2022-12-06T17:43:52+01:00",
  "updated_at": "2022-12-06T17:43:53+01:00",
  "status": "PAYMENT_PENDING",
  "reason": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e - CLNVTR76P01G822Q",
  "remote_id": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e",
  "payment": {
    "transaction_id": null,
    "paid_at": null,
    "expire_at": "2023-03-06T17:43:51+01:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": "302872234000000343",
    "iud": "2cc87ee53f574f20a703e6718c21b95b",
    "iuv": "02872234000000343",
    "receiver": null,
    "due_type": "TARI",
    "pagopa_category": "9/12129/TS",
    "document": null,
    "split": []
  },
  "links": {
    "online_payment_begin": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/online-payment/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/detail",
      "last_opened_at": null,
      "method": "GET"
    },
    "offline_payment": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/notice/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "receipt": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/receipt/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": "http://efil-proxy-qa.boat-backplane.opencontent.io/update/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_check_at": null,
      "next_check_at": "2022-12-06T17:43:53+01:00",
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": "PATCH"
    },
    "cancel": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/payments/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "PATCH"
    } 
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "CLNVTR76P01G822Q",
    "name": "Vittorino",
    "family_name": "Coliandro",
    "street_name": "Via Gramsci, 1",
    "building_number": "",
    "postal_code": "56056",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "aa716d45-f56c-4b38-8c62-668a48818a04",
  "event_version": "2.0",
  "event_created_at": "2022-12-06T17:43:53+01:00",
  "app_id": "efil-payment-proxy-qa:1.2.2"
}

In caso di risposta negativa, il pagamento viene passato in stato CREATION_FAILED, viene salvato il pagamento sullo storage e viene scritto l'evento aggiornato sul topic payments

{
  "id": "7037158b-f990-46d9-9377-1392adb81186",
  "user_id": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "b2c33452-a741-4841-acc1-69a2c007e398",
  "created_at": "2022-11-22T17:39:38+01:00",
  "updated_at": "2022-11-22T17:39:38+01:00",
  "status": "CREATION_FAILED",
  "reason": "84a4bc4b-6eaf-4ea2-b4bc-0c4d73b05c6c - CLNVTR76P01G822Q",
  "remote_id": "84a4bc4b-6eaf-4ea2-b4bc-0c4d73b05c6c",
  "payment": {
    "transaction_id": null,
    "paid_at": null,
    "expire_at": "2023-02-20T17:39:37+01:00",
    "amount": 1,
    "currency": "EUR",
    "notice_code": null,
    "iud": "7037158bf99046d993771392adb81186",
    "iuv": null,
    "receiver": null,
    "due_type": null,
    "pagopa_category": null,
    "document": null,
    "split": []
  },
  "links": {
    "online_payment_begin": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/84a4bc4b-6eaf-4ea2-b4bc-0c4d73b05c6c/detail",
      "last_opened_at": null,
      "method": "GET"
    },
    "offline_payment": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "receipt": {
      "url": null,
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/84a4bc4b-6eaf-4ea2-b4bc-0c4d73b05c6c/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": null,
      "last_check_at": null,
      "next_check_at": null,
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": "PATCH"
    },
    "cancel": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/payments/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "PATCH"
    }
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "CLNVTR76P01G822Q",
    "name": "Vittorino",
    "family_name": "Coliandro",
    "street_name": "Via Gramsci, 1",
    "building_number": "",
    "postal_code": "56056",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "8b3452d8-bd5d-4380-b8a6-7e4f551b09d8",
  "event_version": "2.0",
  "event_created_at": "2022-11-22T17:39:39+01:00",
  "app_id": "pmpay-payment-proxy:1.0.0-rc.2"
}

Nel core intanto, la UI del cittadino monitora lo stato del pagamento interrogando una tabella su KSQLDB creata a partire dal topic payments, e se in stato PAYMENT_PENDING, vengono mostrate all'utente le opzioni per pagare

Step 4 - "Paga online"

  1. Verra richiamata l'url {EXTERNAL_API_URL}/online-payment/{payment_id} servita dal proxy

  2. Il proxy richiederà all'intermediario di pagamento il link per pagare

  3. Sarà segnato il timestamp di apertura del link (links.online_payment_begin.last_opened_at)

  4. Sarà segnato il timestamp di aggiornamento dell'evento (updated_at)

  5. Il pagamento verrà salvato sullo storage

  6. L'evento aggiornato verrà scritto sul topic payments, indicando come chiave dell'evento il valore del campo service_id

  7. Verrà restituito il link ritornato allo step 2 mediante il quale l'utente verrà rediretto al portale dell'intermediario

  8. Al termine della procedura di pagamento, l'utente verrà fatto ritornare sul dettaglio della pratica mediante l'url di landing specificata in fase di creazione della posizione debitoria (links.online_payment_landing.url) allo step 5, a questa url andrà aggiunto in query string un parametro payment da valorizzare con OK o KO a seconda dell'esito del pagamento

  9. Contemporaneamente, essendo che si passa per il proxy, verrà segnato il timestamp in cui l'utente ha aperto il link di ritorno (links.online_payment_landing.last_opened_at)

  10. Il pagamento verrà portato in stato PAYMENT_STARTED

  11. Sarà segnato il timestamp di aggiornamento dell'evento (updated_at)

  12. Il pagamento verrà salvato sullo storage

  13. L'evento aggiornato verrà scritto sul topic payments, indicando come chiave dell'evento il valore del campo service_id.

{
  "id": "2cc87ee5-3f57-4f20-a703-e6718c21b95b",
  "user_id": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "daa0f528-b582-4d1c-9691-e226ac443424",
  "created_at": "2022-12-06T17:43:52+01:00",
  "updated_at": "2022-12-06T17:44:51+01:00",
  "status": "PAYMENT_STARTED",
  "reason": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e - CLNVTR76P01G822Q",
  "remote_id": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e",
  "payment": {
    "transaction_id": "7b32b299-67db-4f84-9419-f61962c3e202",
    "paid_at": null,
    "expire_at": "2023-03-06T17:43:51+01:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": "302872234000000343",
    "iud": "2cc87ee53f574f20a703e6718c21b95b",
    "iuv": "02872234000000343",
    "receiver": null,
    "due_type": "TARI",
    "pagopa_category": "9/12129/TS",
    "document": null,
    "split": []
  },
  "links": {
    "online_payment_begin": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/online-payment/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": "2022-12-06T17:43:56+01:00",
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/detail",
      "last_opened_at": "2022-12-06T17:44:51+01:00",
      "method": "GET"
    },
    "offline_payment": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/notice/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "receipt": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/receipt/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": "http://efil-proxy-qa.boat-backplane.opencontent.io/update/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_check_at": null,
      "next_check_at": "2022-12-06T17:43:53+01:00",
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": "PATCH"
    },
    "cancel": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/payments/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "PATCH"
    } 
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "CLNVTR76P01G822Q",
    "name": "Vittorino",
    "family_name": "Coliandro",
    "street_name": "Via Gramsci, 1",
    "building_number": "",
    "postal_code": "56056",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "dcd203c7-a6e8-4084-b09b-f700e8fa94d8",
  "event_version": "2.0",
  "event_created_at": "2022-12-06T17:44:51+01:00",
  "app_id": "efil-payment-proxy-qa:1.2.2"
}

Step 4 - "Paga offline"

  1. Verra richiamata l'url {EXTERNAL_API_URL}/offline-payment/{payment_id} servita dal proxy

  2. Il proxy richiederà all'intermediario di pagamento il pdf dell'avviso di pagamento

  3. Sarà segnato il timestamp di apertura del link (links.offline_payment.last_opened_at)

  4. Sarà segnato il timestamp di aggiornamento dell'evento (updated_at)

  5. Il pagamento verrà salvato sullo storage

  6. L'evento aggiornato verrà scritto sul topic payments, indicando come chiave dell'evento il valore del campo service_id

  7. Verrà eseguito il download del pdf ritornato allo step 1

Esempio di avviso di pagamento generato

Step 4 - Annulla pagamento

È possibile che l'operatore abbia necessità di annullare un pagamento mediante l'opzione di registrazione del pagamento, ciò può accadere ad esempio se il cittadino, dopo aver creato il pagamento, decide di non concluderlo, e di pagare invece mediante altri canali creando un nuovo pagamento diverso da quello presente sulla piattaforma. Per evitare quindi di lasciare il pagamento pendente per un tempo indefinito, e per permettere l'avanzamento della pratica all'interno della quale è stato creato tale pagamento, sarà possibile annullare quest'ultimo.

Gli step sono i seguenti:

  1. Verra richiamata l'url PATCH {EXTERNAL_API_URL}/payments/{payment_id} servita dal proxy con payload {"status": "CANCELED"}

  2. Il proxy, dopo aver letto il pagamento dallo storage, segnerà il campo status a CANCELED

  3. Sarà segnato il timestamp di apertura del link (links.offline_payment.last_opened_at)

  4. Sarà segnato il timestamp di aggiornamento dell'evento (updated_at)

  5. Il pagamento verrà salvato sullo storage

  6. L'evento aggiornato verrà scritto sul topic payments, indicando come chiave dell'evento il valore del campo service_id

Step 4 - Dovuti importati da fonte esterna all'Area personale

In alcuni casi è possibile che i pagamenti non siano generati a partire dall'area personale, ma siano invece importati da una fonte esterna. In questo caso gli eventi relativi a tali pagamenti saranno inseriti nel topic payments già in status PAYMENT_PENDING e conterranno al loro interno tutte le informazioni che il proxy normalmente inserisce quando fa passare un pagamento generato dall'area personale dallo status CREATION_PENDING allo status PAYMENT_PENDING.

In tal caso tuttavia, questi pagamenti non saranno presenti sullo storage del proxy, dal momento che questa è un'operazione che normalmente farebbe il proxy. Di conseguenza, nel momento in cui dovesse venir letto un evento in stato PAYMENT_PENDING, ci fosse una configurazione attiva per quest'ultimo (ciò si determina controllando che ci sia sullo storage una configurazione con tenant_id e service_id equivalenti a quelli contenuti nel pagamento) ma non esistesse sullo storage, bisogna semplicemente salvare il pagamento sullo storage.

Nel core intanto, questo tipo di dovuti saranno mostrati nella sezione "Pagamenti" insieme a tutti i pagamenti (importati e non) generati dal cittadino, i dati qui presenti sono estratti mediante interrogazione di una tabella su KSQLDB creata a partire dal topic payments.

Cliccando su "Vedi dettaglio" si andrà sul dettaglio dello specifico pagamento dove saranno mostrate le opzioni per pagare (online, offline)

Step 5

Il payments poller nel frattempo:

  1. Interroga la tabella PAYMENTS_DETAIL per ottenere lo stato dei pagamenti, e se questi sono in stato PAYMENT_PENDING o PAYMENT_STARTED, eseguirà una chiamata http all'url {INTERNAL_API_URL}/update/{payment_id} servita dal proxy (quella di aggiornamento menzionata allo step 5)

  2. Il proxy a sua volta recupera dallo storage il pagamento, interroga l'intermediario di riferimento per verificare lo stato del pagamento, e in caso lo aggiorna in stato COMPLETE o PAYMENT_STARTED (questa casistica può verificarsi nel caso in cui l'utente paghi ma poi chiuda il browser anziche cliccare sul bottone contenente la landing url)

    1. Se il pagamento viene portato in stato COMPLETE , si dovrà salvare, se presente come informazione ritornata dall'intemediario di riferimento, il timestamp di avvenuto pagamento (payment.paid_at), e l'id della transazione (payment.transaction_id)

  3. Il proxy segna il timestamp in cui è stato aperto il link di aggiornamento (links.update.last_opened_at)

  4. Il proxy segna il timestamp di aggiornamento del pagamento (updated_at)

  5. Il proxy salva il pagamento sullo storage e lo scrive sul topic payments, indicando come chiave dell'evento il valore del campo service_id.

Logica di polling

Più passa il tempo, più è probabile che il pagamento non venga eseguito dall'utente, di conseguenza ad ogni chiamata del poller verso il proxy, viene incrementato l'intervallo di tempo tra una chiamata e l'altra, ciò viene fatto in base al campo links.update.next_check_at.

Nello specifico il polling avviene con al seguente logica:

  1. Condizione iniziale: Se il campo last_check_at è nullo (non è stato effettuato alcun controllo in precedenza), la funzione termina immediatamente senza aggiornare il prossimo controllo.

  2. Checkpoint temporali: La funzione utilizza diversi checkpoint per classificare il tempo trascorso dalla creazione della risorsa:

    1. 5 minuti

    2. 15 minuti

    3. 7 giorni

    4. 30 giorni

    5. 365 giorni

Ciascun intervallo ha una frequenza di controllo associata:

  • Fino a 5 minuti: Controlli ogni 1 minuto.

  • Fino a 15 minuti: Controlli ogni 5 minuti.

  • Fino a 7 giorni: Controlli ogni ora.

  • Fino a 30 giorni: Controlli ogni 6 ore.

  • Fino a 365 giorni: Controlli settimanali, sempre alla fine della settimana corrente.

  • Oltre 365 giorni: La risorsa è considerata scaduta, lo stato è impostato su STATUS_EXPIRED e il prossimo controllo è disabilitato (next_check_at impostato a None).

  • Impostazione del prossimo controllo: Se determinato, il next_check è aggiornato al valore calcolato

  • Aggiornamento dello stato: Se il tempo dalla creazione supera i 365 giorni, lo stato della risorsa viene aggiornato a STATUS_EXPIRED.

Nota 2: non tutti i proxy aggiornano i pagamenti mediante update, alcuni vengono aggiornati predisponendo un'apposita API che l'intermediario di pagamento PagoPA chiama inviando la notifica di completamento del pagamento. Questa viene quindi elaborata dal proxy il quale aggiorna il pagamento portandolo in stato COMPLETE, segna il timestamp di aggiornamento del pagamento (updated_at), lo salva sullo storage e lo scrive sul topic payments, indicando come chiave dell'evento il valore del campo service_id .

{
  "id": "2cc87ee5-3f57-4f20-a703-e6718c21b95b",
  "user_id": "1c340b05-0808-4dbe-ad12-81ebbf3b6abf",
  "type": "PAGOPA",
  "tenant_id": "60e35f02-1509-408c-b101-3b1a28109329",
  "service_id": "daa0f528-b582-4d1c-9691-e226ac443424",
  "created_at": "2022-12-06T17:43:52+01:00",
  "updated_at": "2022-12-07T09:47:35+01:00",
  "status": "COMPLETE",
  "reason": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e - CLNVTR76P01G822Q",
  "remote_id": "a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e",
  "payment": {
    "transaction_id": "7b32b299-67db-4f84-9419-f61962c3e202",
    "paid_at": null,
    "expire_at": "2023-03-06T17:43:51+01:00",
    "amount": 1.34,
    "currency": "EUR",
    "notice_code": "302872234000000343",
    "iud": "2cc87ee53f574f20a703e6718c21b95b",
    "iuv": "02872234000000343",
    "receiver": null,
    "due_type": "TARI",
    "pagopa_category": "9/12129/TS",
    "document": null,
    "split": []
  },
  "links": {
    "online_payment_begin": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/online-payment/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": "2022-12-06T17:43:56+01:00",
      "method": "GET"
    },
    "online_payment_landing": {
      "url": "https://servizi.comune.bugliano.pi.it/x/it/pratiche/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/detail",
      "last_opened_at": "2022-12-06T17:44:51+01:00",
      "method": "GET"
    },
    "offline_payment": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/notice/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "receipt": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/receipt/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "GET"
    },
    "notify": [
      {
        "url": "https://servizi.comune.bugliano.pi.it/x/api/applications/a1bbcafb-ee78-4cd1-9b8e-7ddeb434d84e/payment",
        "method": "POST",
        "sent_at": null
      }
    ],
    "update": {
      "url": "http://efil-proxy-qa.boat-backplane.opencontent.io/update/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_check_at": "2022-12-07T09:47:35+01:00",
      "next_check_at": "2022-12-07T10:47:35+01:00",
      "method": "GET"
    },
    "confirm": {
      "url": null,
      "last_opened_at": null,
      "method": "PATCH"
    },
    "cancel": {
      "url": "https://efil-proxy-qa.boat.opencontent.io/payments/2cc87ee5-3f57-4f20-a703-e6718c21b95b",
      "last_opened_at": null,
      "method": "PATCH"
    } 
  },
  "payer": {
    "type": "human",
    "tax_identification_number": "CLNVTR76P01G822Q",
    "name": "Vittorino",
    "family_name": "Coliandro",
    "street_name": "Via Gramsci, 1",
    "building_number": "",
    "postal_code": "56056",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "debtor": {
    "type": "human",
    "tax_identification_number": "BNRMHL75C06G702B",
    "name": "Michelangelo",
    "family_name": "Buonarroti",
    "street_name": "Cesare Battisti",
    "building_number": "",
    "postal_code": "38010",
    "town_name": "Bugliano",
    "country_subdivision": "PI",
    "country": "IT",
    "email": "[email protected]"
  },
  "event_id": "1a745224-37bb-4843-a85f-3011764395a6",
  "event_version": "2.0",
  "event_created_at": "2022-12-07T09:47:35+01:00",
  "app_id": "efil-payment-proxy-qa:1.2.2"
}

Il payment updater monitora lo stato dei pagamenti leggendo dal topic payments, e contestualmente aggiorna lo stato della pratica, se il pagamento è in statoPAYMENT_STARTED, aggiorna lo stato della pratica a "In attesa dell'esito di pagamento", se invece il pagamento è in stato COMPLETE, aggiorna lo stato della pratica a "Accettata" se il pagamento è posticipato, o Inviata se il pagamento è immediato.

TABELLA STATI PAGAMENTO

Gli stati per i quali passa un pagamento durante il suo ciclo di vita sono riassunti nella seguente tabella

STATO PAGAMENTO
DESCRIZIONE
NOTE

CREATION_PENDING

pagamento in attesa di essere creato sul provider di riferimento

CREATION_FAILED

pagamento di cui è fallita la creazione sul provider di riferimento

PAYMENT_PENDING

pagamento creato sul provider di riferimento e in attesa di essere eseguito dall'utente

PAYMENT_STARTED

procedura di pagamento iniziata dall'utente

COMPLETE

pagamento completato a seguito di conferma dal provider di riferimento

PAYMENT_FAILED

pagamento fallito a causa di scadenza del termine ultimo entro cui doveva essere eseguito

nei proxy sviluppati questo stato non è quasi mai stato utilizzato

CANCELED

pagamento annullato forzatamente da un operatore

REQUISITI

Tutti i proxy devono:

  • Validare gli eventi in input e output

  • Integrazione con sentry

  • Healthcheck

  • Esporre metriche di monitoraggio in formato prometheus