Skip to main content

Overview

pn-data-vault is the PagoPA service responsible for storing and tokenising personally identifiable information (PII). Paper Channel uses Data Vault to:
  • Encode (tokenise) a recipient’s fiscal code into an opaque internal identifier before persisting or forwarding it.
  • Decode (de-tokenise) an internal identifier back to the original fiscal code when the real value is needed for downstream operations such as address lookups.
This keeps raw fiscal codes out of Paper Channel’s own DynamoDB tables and event queues.

Implementation — DataVaultEncryptionImpl

Data Vault integration lives in DataVaultEncryptionImpl, which implements the DataEncryption interface and is registered as the Spring bean dataVaultEncryption.
// DataVaultEncryptionImpl.java
@Component("dataVaultEncryption")
public class DataVaultEncryptionImpl extends BaseClient implements DataEncryption {

    @PostConstruct
    public void init() {
        ApiClient apiClient = new ApiClient(
            super.initWebClient(ApiClient.buildWebClientBuilder())
        );
        apiClient.setBasePath(pnPaperChannelConfig.getClientDataVaultBasepath());
        this.recipientsApi = new RecipientsApi(apiClient);
    }
}
The WebClient is built by BaseClient.initWebClient(...) and the base URL is injected at startup from PnPaperChannelConfig.

Operations

encode — tokenise a fiscal code

// DataVaultEncryptionImpl.java
public String encode(String fiscalCode, String type) {
    return this.recipientsApi
        .ensureRecipientByExternalId(
            type.equalsIgnoreCase(RecipientTypeDto.PF.getValue())
                ? RecipientTypeDto.PF
                : RecipientTypeDto.PG,
            fiscalCode
        )
        .retryWhen(
            Retry.backoff(2, Duration.ofMillis(25))
                .filter(t -> t instanceof TimeoutException || t instanceof ConnectException)
        )
        .block();
}
  • Calls RecipientsApi.ensureRecipientByExternalId(recipientType, externalId).
  • Returns the opaque internal token (a string).
  • Retries up to 2 times with 25 ms backoff on transient connectivity errors.
  • Blocks the calling thread — this is a synchronous operation in the current implementation.

decode — resolve a token to a fiscal code

// DataVaultEncryptionImpl.java
public String decode(String data) {
    List<String> toDecode = List.of(data);
    return this.recipientsApi
        .getRecipientDenominationByInternalId(toDecode)
        .retryWhen(
            Retry.backoff(2, Duration.ofMillis(25))
                .filter(t -> t instanceof TimeoutException || t instanceof ConnectException)
        )
        .map(BaseRecipientDtoDto::getTaxId)
        .blockFirst();
}
  • Calls RecipientsApi.getRecipientDenominationByInternalId(List<String>).
  • Returns the plain-text fiscal code extracted from the first BaseRecipientDtoDto in the response.
  • Same 2-retry / 25 ms backoff policy.

Recipient types

RecipientTypeDtoMeaning
PFPersona Fisica — natural person
PGPersona Giuridica — legal entity
The type is passed as an enum value to ensureRecipientByExternalId. The mapping logic is:
StringUtils.equalsIgnoreCase(type, RecipientTypeDto.PF.getValue())
    ? RecipientTypeDto.PF
    : RecipientTypeDto.PG
Any value that is not exactly "PF" (case-insensitive) is treated as PG.

DataEncryption interface

// DataEncryption.java
public interface DataEncryption {
    default String encode(String data)         { return data; }  // no-op default
    default String encode(String data, String type) { return data; }  // no-op default
    String decode(String data);
}
The no-op default encode allows local / test configurations to bypass tokenisation by injecting a different DataEncryption implementation.

Configuration

PropertyDescriptionExample
pn.paper-channel.client-datavault-basepathBase URL of the Data Vault servicehttp://localhost:1080
Unlike other microservice clients, DataVaultEncryptionImpl constructs its ApiClient directly in a @PostConstruct method rather than relying on auto-configured Spring beans. The base path is read once at startup.

Error handling

ExceptionCauseBehaviour
DATA_VAULT_ENCRYPTION_ERRORAny exception during encodeWrapped in PnGenericException and propagated
DATA_VAULT_DECRYPTION_ERRORAny exception during decodeWrapped in PnGenericException and propagated; error message is logged
TimeoutException / ConnectExceptionTransient connectivity issueRetried up to 2 times with 25 ms backoff; if all retries fail, the appropriate exception above is thrown
Both encode and decode use a blocking block() / blockFirst() call. Call these methods only from a thread that is allowed to block (e.g., a Schedulers.boundedElastic() context). Calling them from a Reactor event-loop thread will cause a deadlock.

Build docs developers (and LLMs) love