diff --git a/docs/onboarding/Configuration-Properties.md b/docs/onboarding/Configuration-Properties.md index 799d45a15..dcf3f9cd2 100644 --- a/docs/onboarding/Configuration-Properties.md +++ b/docs/onboarding/Configuration-Properties.md @@ -46,7 +46,6 @@ The Onboarding Server uses the following public configuration properties: | `enrollment-server-onboarding.identity-verification.otp.enabled` | `true` | Whether OTP verification is enabled during identity verification. | | `enrollment-server-onboarding.identity-verification.max-failed-attempts` | `5` | Maximum failed attempts for identity verification. | | `enrollment-server-onboarding.identity-verification.max-failed-attempts-document-upload` | `5` | Maximum failed attempts for document upload. | -| `enrollment-server-onboarding.client-evaluation.max-failed-attempts` | `5` | Maximum failed attempts for client evaluation. | ## Digital Onboarding Adapter Configuration @@ -69,6 +68,7 @@ The Onboarding Server uses the following public configuration properties: | Property | Default | Note | |---|---|---| | `enrollment-server-onboarding.client-evaluation.max-failed-attempts` | 5 | Number of maximum failed attempts for client evaluation. | +| `enrollment-server-onboarding.client-evaluation.include-extracted-data` | `false` | Include extracted data to the evaluate client request. The format of extracted data is defined by the provider of document verification. | ## Document Verification Provider Configuration diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java index 37e1d40be..379db3cd0 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/configuration/IdentityVerificationConfig.java @@ -79,6 +79,9 @@ public class IdentityVerificationConfig { @Value("${enrollment-server-onboarding.client-evaluation.max-failed-attempts:5}") private int clientEvaluationMaxFailedAttempts; + @Value("${enrollment-server-onboarding.client-evaluation.include-extracted-data:false}") + private boolean sendingExtractedDataEnabled; + @PostConstruct void validate() { // Once in the future, we may replace OTP in SCA by NFC document reading diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java index 640e1cde8..cd4b19f2c 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationService.java @@ -21,7 +21,9 @@ import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; import com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase; import com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus; +import com.wultra.app.enrollmentserver.model.integration.DocumentSubmitResult; import com.wultra.app.enrollmentserver.model.integration.OwnerId; +import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.service.AuditService; @@ -33,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Set; import static com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus.ACCEPTED; @@ -86,20 +89,27 @@ public ClientEvaluationService( public void processClientEvaluation(final IdentityVerificationEntity identityVerification, final OwnerId ownerId) { logger.debug("Client evaluation started for {}", identityVerification); + final Set acceptedDocuments = selectAcceptedDocuments(identityVerification); + final String verificationId; try { - verificationId = getVerificationId(identityVerification); + verificationId = fetchVerificationId(identityVerification, acceptedDocuments); } catch (Exception e) { processVerificationIdError(identityVerification, ownerId, e); return; } - final EvaluateClientRequest request = EvaluateClientRequest.builder() + final EvaluateClientRequest.EvaluateClientRequestBuilder requestBuilder = EvaluateClientRequest.builder() .processId(identityVerification.getProcessId()) .userId(identityVerification.getUserId()) .identityVerificationId(identityVerification.getId()) .verificationId(verificationId) - .build(); + .provider(config.getDocumentVerificationProvider()); + + if (config.isSendingExtractedDataEnabled()) { + requestBuilder.extractedData(fetchDocumentsExtractedData(acceptedDocuments, identityVerification)); + } + final EvaluateClientRequest request = requestBuilder.build(); final int maxFailedAttempts = config.getClientEvaluationMaxFailedAttempts(); for (int i = 0; i < maxFailedAttempts; i++) { @@ -117,10 +127,29 @@ public void processClientEvaluation(final IdentityVerificationEntity identityVer processTooManyEvaluationError(identityVerification, ownerId); } - private static String getVerificationId(final IdentityVerificationEntity identityVerification) { - final Set verificationIds = identityVerification.getDocumentVerifications().stream() + private static Set selectAcceptedDocuments(final IdentityVerificationEntity identityVerification) { + return identityVerification.getDocumentVerifications().stream() .filter(DocumentVerificationEntity::isUsedForVerification) .filter(it -> it.getStatus() == DocumentStatus.ACCEPTED) + .collect(toSet()); + } + + private static List fetchDocumentsExtractedData(final Set documents, final IdentityVerificationEntity identityVerification) { + return documents.stream() + .map(doc -> selectLatestDocumentResult(doc, identityVerification)) + .map(DocumentResultEntity::getExtractedData) + .filter(data -> !DocumentSubmitResult.NO_DATA_EXTRACTED.equals(data)) + .toList(); + } + + private static DocumentResultEntity selectLatestDocumentResult(final DocumentVerificationEntity documentVerificationEntity, final IdentityVerificationEntity identityVerification) { + return documentVerificationEntity.getResults().stream() + .findFirst() + .orElseThrow(() -> new IllegalStateException("Missing document result for %s of %s".formatted(documentVerificationEntity, identityVerification))); + } + + private static String fetchVerificationId(final IdentityVerificationEntity identityVerification, final Set documents) { + final Set verificationIds = documents.stream() .map(DocumentVerificationEntity::getVerificationId) .collect(toSet()); diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java index b175d88de..3ee5c7599 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/model/request/EvaluateClientRequest.java @@ -22,6 +22,8 @@ import com.wultra.core.annotations.PublicApi; import lombok.*; +import java.util.List; + /** * Request object for {@link OnboardingProvider#evaluateClient(EvaluateClientRequest)}. * @@ -45,4 +47,11 @@ public final class EvaluateClientRequest { @NonNull private String verificationId; + + private String provider; + + /** + * Data extracted from each document/page. Format is defined by the document verification provider used. + */ + private List extractedData; } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java index f636460dc..8e73a915f 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/ClientEvaluateRequestDto.java @@ -19,6 +19,8 @@ import lombok.Data; +import java.util.List; + /** * Request object for client evaluation. * @@ -39,4 +41,9 @@ class ClientEvaluateRequestDto { private String verificationId; private String provider; + + /** + * Data extracted from each document/page. Format is defined by the document verification provider used. + */ + private List extractedData; } diff --git a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java index 814ebd876..d6607bed8 100644 --- a/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java +++ b/enrollment-server-onboarding/src/main/java/com/wultra/app/onboardingserver/provider/rest/RestOnboardingProvider.java @@ -245,6 +245,8 @@ private static ClientEvaluateRequestDto convert(final EvaluateClientRequest sour target.setIdentityVerificationId(source.getIdentityVerificationId()); target.setUserId(source.getUserId()); target.setVerificationId(source.getVerificationId()); + target.setProvider(source.getProvider()); + target.setExtractedData(source.getExtractedData()); return target; } } diff --git a/enrollment-server-onboarding/src/main/resources/application.properties b/enrollment-server-onboarding/src/main/resources/application.properties index 09f3c82f0..f64b55a8c 100644 --- a/enrollment-server-onboarding/src/main/resources/application.properties +++ b/enrollment-server-onboarding/src/main/resources/application.properties @@ -90,6 +90,7 @@ enrollment-server-onboarding.onboarding-process.max-error-score=15 # Client Evaluation Configuration enrollment-server-onboarding.client-evaluation.max-failed-attempts=5 +enrollment-server-onboarding.client-evaluation.include-extracted-data=false # Identity Verification Configuration enrollment-server-onboarding.identity-verification.enabled=false diff --git a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java index 28caa5d02..55dd8ec5a 100644 --- a/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java +++ b/enrollment-server-onboarding/src/test/java/com/wultra/app/onboardingserver/impl/service/ClientEvaluationServiceTest.java @@ -19,7 +19,9 @@ import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus; import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin; +import com.wultra.app.enrollmentserver.model.integration.DocumentSubmitResult; import com.wultra.app.enrollmentserver.model.integration.OwnerId; +import com.wultra.app.onboardingserver.common.database.entity.DocumentResultEntity; import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity; import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity; import com.wultra.app.onboardingserver.common.service.AuditService; @@ -34,6 +36,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -71,12 +74,15 @@ class ClientEvaluationServiceTest { void testProcessClientEvaluation_successful() throws Exception { when(identityVerificationConfig.getClientEvaluationMaxFailedAttempts()) .thenReturn(1); + when(identityVerificationConfig.isSendingExtractedDataEnabled()) + .thenReturn(true); final EvaluateClientRequest evaluateClientRequest = EvaluateClientRequest.builder() .processId("p1") .userId("u1") .identityVerificationId("i1") .verificationId("v1") + .extractedData(List.of("d1_data")) .build(); final EvaluateClientResponse evaluateClientResponse = EvaluateClientResponse.builder() .accepted(true) @@ -90,8 +96,8 @@ void testProcessClientEvaluation_successful() throws Exception { identityVerification.setUserId("u1"); identityVerification.setPhase(CLIENT_EVALUATION); identityVerification.setDocumentVerifications(Set.of( - createDocumentVerification("d1", DocumentStatus.ACCEPTED, "v1"), - createDocumentVerification("d2", DocumentStatus.ACCEPTED, "v1"), + createDocumentVerificationWithResults("d1", DocumentStatus.ACCEPTED, "v1", "d1_data"), + createDocumentVerificationWithResults("d2", DocumentStatus.ACCEPTED, "v1", DocumentSubmitResult.NO_DATA_EXTRACTED), createDocumentVerification("d3", DocumentStatus.DISPOSED, "v2"))); final OwnerId ownerId = new OwnerId(); @@ -166,4 +172,13 @@ private static DocumentVerificationEntity createDocumentVerification(final Strin documentVerification.setUsedForVerification(true); return documentVerification; } + + private static DocumentVerificationEntity createDocumentVerificationWithResults(final String id, final DocumentStatus status, final String verificationId, final String extractedData) { + final DocumentResultEntity documentResult = new DocumentResultEntity(); + documentResult.setExtractedData(extractedData); + + final DocumentVerificationEntity documentVerification = createDocumentVerification(id, status, verificationId); + documentVerification.setResults(Set.of(documentResult)); + return documentVerification; + } }