kyc
java
a month ago
11 kB
1
Indexable
Never
package com.breakwater.kycservice.request.service; import com.breakwater.common.core.util.UuidGenerator; import com.breakwater.common.exception.exception.EntityNotFoundException; import com.breakwater.common.exception.exception.InvalidStateException; import com.breakwater.kycservice.common.mapper.DocumentResultMapper; import com.breakwater.kycservice.common.model.*; import com.breakwater.kycservice.common.repository.PaymentAccountRepository; import com.breakwater.kycservice.common.repository.PlayerRepository; import com.breakwater.kycservice.documenttype.model.DocumentType; import com.breakwater.kycservice.documenttype.repository.DocumentTypeRepository; import com.breakwater.kycservice.external.hooyu.service.HooyuApiService; import com.breakwater.kycservice.playerverification.service.PlayerVerificationService; import com.breakwater.kycservice.providerconfiguration.model.ProviderConfiguration; import com.breakwater.kycservice.providerconfiguration.service.ProviderConfigurationService; import com.breakwater.kycservice.request.dto.query.RequestCollectionQuery; import com.breakwater.kycservice.request.model.KycRequest; import com.breakwater.kycservice.request.repository.KycRequestRepository; import com.breakwater.paymentservice.paymentaccount.model.PaymentAccountStatus; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Clock; import java.util.List; import java.util.Map; import java.util.UUID; import static com.breakwater.kycservice.common.error.KycServiceErrorCode.*; import static com.breakwater.kycservice.request.model.KycStatus.REQUESTED; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; import static reactor.function.TupleUtils.function; @Service @RequiredArgsConstructor public class KycRequestService { private static final String DOCUMENT_GROUP = DocumentType.Fields.documentGroup; private static final String EXTERNAL_TYPE = DocumentRequest.Fields.externalType; private static final String EXTERNAL_ACCOUNT_ID = DocumentRequest.Fields.externalAccountId; private static final String METHOD_TYPE = DocumentRequest.Fields.methodType; private final KycRequestRepository kycRequestRepository; private final ProviderConfigurationService providerConfigurationService; private final DocumentTypeRepository documentTypeRepository; private final PlayerRepository playerRepository; private final PaymentAccountRepository paymentAccountRepository; private final HooyuApiService hooyuApiService; private final UuidGenerator uuidGenerator; private final DocumentResultMapper documentResultMapper; private final KycRequestPersistenceService kycRequestPersistenceService; private final PlayerVerificationService playerVerificationService; private final Clock clock; @Transactional @PreAuthorize("@kycRequestPermissionEvaluator.checkDocumentsRequestEdit(authentication, #request)") public Mono<KycRequest> create(KycRequest request) { request.setCreated(clock.instant()); return validateAndSendRequest(request.setId(uuidGenerator.get())) .flatMap(documentTypeMap -> toDocumentResults(request, documentTypeMap)) .map(request::setDocumentResults) .flatMap(kycRequestPersistenceService::create) .delayUntil(playerVerificationService::processKycRequest); } @PreAuthorize("@kycRequestPermissionEvaluator.checkDocumentsRequestView(authentication, #query)") public Mono<Page<KycRequest>> readCollection(RequestCollectionQuery query, Pageable pageable) { return kycRequestRepository.findByCollectionQuery(query, pageable); } @PreAuthorize("@kycRequestPermissionEvaluator.checkDocumentsRequestView(authentication, #id)") public Mono<KycRequest> read(UUID id) { return kycRequestRepository.findById(id); } private Mono<Map<String, DocumentType>> validateAndSendRequest(KycRequest request) { return Mono.zip(providerConfigurationService.readByBusinessUnitId(request.getBusinessUnitId()), tryGetPlayer(request.getPlayerId()), Mono.just(request)) .flatMap(function(this::validateRequestedDocumentsAndSendRequest)); } private Mono<Player> tryGetPlayer(UUID playerId) { return playerRepository.findById(playerId) .switchIfEmpty(Mono.error(() -> new EntityNotFoundException( PLAYER_NOT_FOUND, Map.of("playerId", playerId)))); } /** * Currently there is only one HOOYU */ private Mono<Map<String, DocumentType>> validateRequestedDocumentsAndSendRequest(ProviderConfiguration conf, Player player, KycRequest request) { request.setProvider(conf.getProvider()) .setStatus(REQUESTED); var externalTypeToDocumentTypeMap = getExternalTypeToDocumentTypeMap(request); var activePaymentAccounts = getActivePaymentAccountsForPlayer(request).collectList(); return Mono.zip(Mono.just(request), externalTypeToDocumentTypeMap, activePaymentAccounts) .flatMap(function(this::validateRequestedDocuments)) .then(Mono.defer(() -> hooyuApiService.submitRequest(conf, player, request))) .flatMap(ignored -> externalTypeToDocumentTypeMap); } private Mono<Map<String, DocumentType>> getExternalTypeToDocumentTypeMap(KycRequest request) { return documentTypeRepository.findByProviderType(request.getProvider()) .collect(toMap(DocumentType::getExternalType, identity())); } private Flux<PaymentAccount> getActivePaymentAccountsForPlayer(KycRequest request) { boolean noPaymentAccounts = request.getDocuments().stream() .noneMatch(d -> d.getExternalAccountId() != null && d.getMethodType() != null); if (noPaymentAccounts) { return Flux.empty(); } return paymentAccountRepository.findByPlayerIdAndAccountStatus( request.getPlayerId(), PaymentAccountStatus.ACTIVE); } private Mono<Void> validateRequestedDocuments(KycRequest request, Map<String, DocumentType> documentTypes, List<PaymentAccount> paymentAccounts) { return Flux.fromIterable(request.getDocuments()) .flatMap(d -> validateRequestedDocuments(d, documentTypes, paymentAccounts)).then(); } private Mono<Void> validateRequestedDocuments(DocumentRequest document, Map<String, DocumentType> documentTypes, List<PaymentAccount> paymentAccounts) { String externalType = document.getExternalType(); var type = documentTypes.get(externalType); if (type == null) { return Mono.error(new EntityNotFoundException( EXTERNAL_TYPE_NOT_FOUND, Map.of(EXTERNAL_TYPE, externalType))); } var documentGroup = type.getDocumentGroup(); if (documentGroup == DocumentGroup.PROOF_OF_PAYMENT) { return validatePaymentDocuments(document, type, paymentAccounts); } return validateNonPaymentDocuments(document, documentGroup); } private Mono<Void> validatePaymentDocuments(DocumentRequest document, DocumentType type, List<PaymentAccount> paymentAccounts) { String externalType = document.getExternalType(); String externalAccountId = document.getExternalAccountId(); if (externalAccountId == null) { return Mono.error(new InvalidStateException( EXTERNAL_ACCOUNT_ID_MISSING_FOR_EXTERNAL_TYPE, Map.of(EXTERNAL_TYPE, externalType))); } var methodType = document.getMethodType(); if (methodType == null) { return Mono.error(new InvalidStateException( METHOD_TYPE_MISSING_FOR_EXTERNAL_TYPE, Map.of(EXTERNAL_TYPE, externalType))); } if (!type.getMethodTypes().contains(methodType)) { return Mono.error(new InvalidStateException( METHOD_TYPE_DOES_NOT_MATCH_EXTERNAL_TYPE, Map.of(EXTERNAL_TYPE, externalType, METHOD_TYPE, methodType))); } return Flux.fromIterable(paymentAccounts) .filter(a -> a.getMethodType() == methodType && a.getExternalAccountId().equals(externalAccountId)) .switchIfEmpty(Mono.error(() -> new EntityNotFoundException( EXTERNAL_ACCOUNT_NOT_FOUND, Map.of(EXTERNAL_ACCOUNT_ID, externalAccountId, METHOD_TYPE, methodType)))) .then(); } private Mono<Void> validateNonPaymentDocuments(DocumentRequest document, DocumentGroup documentGroup) { String externalType = document.getExternalType(); if (document.getExternalAccountId() != null) { return Mono.error(new InvalidStateException( EXTERNAL_ACCOUNT_ID_IS_NOT_EXPECTED_FOR_DOCUMENT_GROUP, Map.of(EXTERNAL_TYPE, externalType, DOCUMENT_GROUP, documentGroup))); } var methodType2 = document.getMethodType(); if (methodType2 != null) { return Mono.error(new InvalidStateException( METHOD_TYPE_IS_NOT_EXPECTED_FOR_DOCUMENT_GROUP, Map.of(EXTERNAL_TYPE, externalType, DOCUMENT_GROUP, documentGroup))); } return Mono.empty(); } private Mono<List<DocumentResult>> toDocumentResults(KycRequest kycRequest, Map<String, DocumentType> documentTypeMap) { return Flux.zip(Mono.just(kycRequest.getCreated()).repeat(), Mono.just(DocumentStatus.REQUESTED).repeat(), Flux.fromIterable(kycRequest.getDocuments()), generateExternalTypeToDisplayNameMap(documentTypeMap).cache().repeat()) .map(function(documentResultMapper::toDocumentResult)) .collectList(); } private Mono<Map<String, String>> generateExternalTypeToDisplayNameMap(Map<String, DocumentType> documentTypeMap) { return Mono.just(documentTypeMap.entrySet()) .map(entries -> entries.stream() .collect(toMap(Map.Entry::getKey, entry -> entry.getValue().getDisplayName()))); } }