kyc

mail@pastecode.io avatarunknown
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())));
    }

}