From df25417b6b9b548d17ed04c4a2b33c4c29019b58 Mon Sep 17 00:00:00 2001 From: Anatolii Karlov Date: Tue, 29 Oct 2024 17:49:03 +0700 Subject: [PATCH] add disputes-tg-bot usage (#23) * rename AdminManagementServlet * add disputes-tg-bot usage * add dummy for tg bot usages * add mapping * remove texeption catcher * bumps deps, remove debug endpoint * rename * rename * disable isScheduleReadyForCreateAdjustmentsEnabled * fix tests * up pg versino * refactor ProviderRouting * add handleUnexpectedResultMapping tests cases * checkstyle * maven-site issue * bump workflow * bump workflow * bump workflow * review fixes --- README.md | 4 +- pom.xml | 20 ++++- .../admin/callback/CallbackNotifier.java | 17 ++++ .../DisputesTgBotCallbackNotifierImpl.java | 52 +++++++++++ .../callback/DummyCallbackNotifierImpl.java | 37 ++++++++ .../AdminManagementDisputesService.java} | 10 ++- .../management/AdminManagementHandler.java} | 14 +-- .../management/MdcTopicProducer.java} | 25 +++--- .../vality/disputes/api/CancelController.java | 30 ------- .../converter/Status200ResponseConverter.java | 4 +- .../disputes/config/ApplicationConfig.java | 22 +++++ .../vality/disputes/config/NetworkConfig.java | 4 +- .../vality/disputes/config/OtelConfig.java | 68 +++++++++++++++ .../config/properties/OtelProperties.java | 17 ++++ .../disputes/constant/ModerationPrefix.java | 7 ++ .../dev/vality/disputes/dao/DisputeDao.java | 19 ++-- .../merchant/MerchantDisputesHandler.java | 7 +- .../TaskReadyForCreateAdjustmentsService.java | 9 +- .../catcher/WRuntimeExceptionCatcher.java | 46 ++++++++++ .../schedule/client/DefaultRemoteClient.java | 16 ++++ .../client/DisputesTgBotRemoteClientImpl.java | 46 ++++++++++ .../client/DummyRemoteClientImpl.java | 37 ++++++++ .../schedule/client/RemoteClient.java | 16 ++-- .../handler/DisputeCreateResultHandler.java | 83 ++++++++++++++++++ .../handler/DisputeStatusResultHandler.java | 48 ++++++++++- .../disputes/schedule/model/ProviderData.java | 1 + .../service/CreatedDisputesService.java | 84 +++++++----------- .../service/ExternalGatewayChecker.java | 21 +++-- .../service/PendingDisputesService.java | 20 ++--- .../schedule/service/ProviderDataService.java | 2 +- .../service/ProviderIfaceBuilder.java | 7 +- .../schedule/service/ProviderRouting.java | 4 +- .../external/DisputesTgBotService.java | 24 ++++++ .../impl/DisputesTgBotServiceImpl.java | 75 ++++++++++++++++ ...rvlet.java => AdminManagementServlet.java} | 10 +-- .../vality/disputes/utils/ErrorFormatter.java | 7 ++ src/main/resources/application.yml | 22 +++-- .../db/migration/V3__add_mapping.sql | 2 + .../DebugAdminManagementController.java} | 28 +++--- .../DebugAdminManagementControllerTest.java} | 22 ++--- .../DebugAdminManagementHandlerTest.java} | 30 +++---- .../dev/vality/disputes/api/ServletTest.java | 8 +- .../service/CreatedDisputesServiceTest.java | 86 ++++++++++++++++++- .../service/PendingDisputesServiceTest.java | 33 +++++++ .../config/CallbackNotifierTestConfig.java | 13 +++ .../config/CreatedDisputesTestService.java | 2 +- .../config/DefaultRemoteClientTestConfig.java | 13 +++ .../dev/vality/disputes/util/MockUtil.java | 23 ++++- 48 files changed, 982 insertions(+), 213 deletions(-) create mode 100644 src/main/java/dev/vality/disputes/admin/callback/CallbackNotifier.java create mode 100644 src/main/java/dev/vality/disputes/admin/callback/DisputesTgBotCallbackNotifierImpl.java create mode 100644 src/main/java/dev/vality/disputes/admin/callback/DummyCallbackNotifierImpl.java rename src/main/java/dev/vality/disputes/{manualparsing/ManualParsingDisputesService.java => admin/management/AdminManagementDisputesService.java} (96%) rename src/main/java/dev/vality/disputes/{manualparsing/ManualParsingHandler.java => admin/management/AdminManagementHandler.java} (69%) rename src/main/java/dev/vality/disputes/{manualparsing/ManualParsingTopic.java => admin/management/MdcTopicProducer.java} (67%) delete mode 100644 src/main/java/dev/vality/disputes/api/CancelController.java create mode 100644 src/main/java/dev/vality/disputes/config/OtelConfig.java create mode 100644 src/main/java/dev/vality/disputes/config/properties/OtelProperties.java create mode 100644 src/main/java/dev/vality/disputes/constant/ModerationPrefix.java create mode 100644 src/main/java/dev/vality/disputes/schedule/catcher/WRuntimeExceptionCatcher.java create mode 100644 src/main/java/dev/vality/disputes/schedule/client/DefaultRemoteClient.java create mode 100644 src/main/java/dev/vality/disputes/schedule/client/DisputesTgBotRemoteClientImpl.java create mode 100644 src/main/java/dev/vality/disputes/schedule/client/DummyRemoteClientImpl.java create mode 100644 src/main/java/dev/vality/disputes/schedule/handler/DisputeCreateResultHandler.java create mode 100644 src/main/java/dev/vality/disputes/service/external/DisputesTgBotService.java create mode 100644 src/main/java/dev/vality/disputes/service/external/impl/DisputesTgBotServiceImpl.java rename src/main/java/dev/vality/disputes/servlet/{ManualParsingServlet.java => AdminManagementServlet.java} (66%) create mode 100644 src/main/resources/db/migration/V3__add_mapping.sql rename src/test/java/dev/vality/disputes/{manualparsing/DebugManualParsingController.java => admin/management/DebugAdminManagementController.java} (83%) rename src/test/java/dev/vality/disputes/{manualparsing/DebugManualParsingControllerTest.java => admin/management/DebugAdminManagementControllerTest.java} (79%) rename src/test/java/dev/vality/disputes/{manualparsing/DebugManualParsingHandlerTest.java => admin/management/DebugAdminManagementHandlerTest.java} (78%) create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/CallbackNotifierTestConfig.java create mode 100644 src/test/java/dev/vality/disputes/schedule/service/config/DefaultRemoteClientTestConfig.java diff --git a/README.md b/README.md index 2d0dc00..0fc997b 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Если при финальном статусе платежа `captured` создавать на провайдере диспут является не желательной ситуацией, можно установить опцию в терминале `DISPUTE_FLOW_CAPTURED_BLOCKED` и пулять состояние -в топик\тг-провайдер-бот\filebeat на ручной разбор (`ManualParsing` module) +в топик\тг-провайдер-бот\filebeat на ручной разбор (`AdminManagement` module) Не все провайдеры на данный момент поддерживают работу с диспутами по `API`. Предполагается такой способ действия при этой ситуации: @@ -111,7 +111,7 @@ - если это captured платеж и выставлена опция `DISPUTE_FLOW_CAPTURED_BLOCKED` , то тоже отправляет на ручной разбор Далее, через внутрений трифт-интерфейс саппорт получает способ манипулировать диспутом для его -обработки (`ManualParsingDisputesService`) +обработки (`AdminManagementDisputesService`) - Перед переводом диспута в финальный статус саппорт должен будет забиндить айди созданного диспута в провайдере через ручку `BindCreated()`. Здесь особенность, что этот метод фильтрует возможность биндить диспуты только созданные diff --git a/pom.xml b/pom.xml index b98d37f..ac549f1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ dev.vality service-parent-pom - 3.0.2 + 3.0.4 disputes-api @@ -47,7 +47,7 @@ dev.vality disputes-proto - 1.23-37a5ad1 + 1.26-fc8e34f dev.vality @@ -62,6 +62,7 @@ dev.vality damsel + 1.648-ad715bd dev.vality @@ -87,6 +88,16 @@ adapter-flow-lib 1.0.0 + + dev.vality.woody + woody-thrift + 2.0.8 + + + dev.vality.woody + woody-api + 2.0.8 + @@ -217,6 +228,11 @@ guava 32.0.0-jre + + io.opentelemetry + opentelemetry-semconv + 1.29.0-alpha + diff --git a/src/main/java/dev/vality/disputes/admin/callback/CallbackNotifier.java b/src/main/java/dev/vality/disputes/admin/callback/CallbackNotifier.java new file mode 100644 index 0000000..063f342 --- /dev/null +++ b/src/main/java/dev/vality/disputes/admin/callback/CallbackNotifier.java @@ -0,0 +1,17 @@ +package dev.vality.disputes.admin.callback; + +import dev.vality.disputes.domain.tables.pojos.Dispute; + +import java.util.List; + +public interface CallbackNotifier { + + void sendDisputeAlreadyCreated(Dispute dispute); + + void sendDisputePoolingExpired(Dispute dispute); + + void sendDisputeReadyForCreateAdjustment(List disputes); + + void sendDisputeFailedReviewRequired(Dispute dispute, String errorCode, String errorDescription); + +} diff --git a/src/main/java/dev/vality/disputes/admin/callback/DisputesTgBotCallbackNotifierImpl.java b/src/main/java/dev/vality/disputes/admin/callback/DisputesTgBotCallbackNotifierImpl.java new file mode 100644 index 0000000..6c9fdbd --- /dev/null +++ b/src/main/java/dev/vality/disputes/admin/callback/DisputesTgBotCallbackNotifierImpl.java @@ -0,0 +1,52 @@ +package dev.vality.disputes.admin.callback; + +import dev.vality.disputes.admin.DisputeAlreadyCreated; +import dev.vality.disputes.admin.DisputeFailedReviewRequired; +import dev.vality.disputes.admin.DisputePoolingExpired; +import dev.vality.disputes.admin.DisputeReadyForCreateAdjustment; +import dev.vality.disputes.domain.tables.pojos.Dispute; +import dev.vality.disputes.service.external.DisputesTgBotService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +@ConditionalOnProperty(value = "service.disputes-tg-bot.admin.enabled", havingValue = "true", matchIfMissing = true) +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings({"LineLength"}) +public class DisputesTgBotCallbackNotifierImpl implements CallbackNotifier { + + private final DisputesTgBotService disputesTgBotService; + + @Override + public void sendDisputeAlreadyCreated(Dispute dispute) { + disputesTgBotService.sendDisputeAlreadyCreated(new DisputeAlreadyCreated(dispute.getId().toString())); + } + + @Override + public void sendDisputePoolingExpired(Dispute dispute) { + disputesTgBotService.sendDisputePoolingExpired(new DisputePoolingExpired(dispute.getId().toString())); + } + + @Override + public void sendDisputeReadyForCreateAdjustment(List disputes) { + var disputeReadyForCreateAdjustments = disputes.stream() + .map(Dispute::getId) + .map(UUID::toString) + .map(DisputeReadyForCreateAdjustment::new) + .toList(); + disputesTgBotService.sendDisputeReadyForCreateAdjustment(disputeReadyForCreateAdjustments); + } + + @Override + public void sendDisputeFailedReviewRequired(Dispute dispute, String errorCode, String errorDescription) { + disputesTgBotService.sendDisputeFailedReviewRequired( + new DisputeFailedReviewRequired(dispute.getId().toString(), errorCode) + .setErrorDescription(errorDescription)); + } +} diff --git a/src/main/java/dev/vality/disputes/admin/callback/DummyCallbackNotifierImpl.java b/src/main/java/dev/vality/disputes/admin/callback/DummyCallbackNotifierImpl.java new file mode 100644 index 0000000..b09691b --- /dev/null +++ b/src/main/java/dev/vality/disputes/admin/callback/DummyCallbackNotifierImpl.java @@ -0,0 +1,37 @@ +package dev.vality.disputes.admin.callback; + +import dev.vality.disputes.domain.tables.pojos.Dispute; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@ConditionalOnProperty(value = "service.disputes-tg-bot.admin.enabled", havingValue = "false") +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings({"LineLength"}) +public class DummyCallbackNotifierImpl implements CallbackNotifier { + + @Override + public void sendDisputeAlreadyCreated(Dispute dispute) { + log.debug("Trying to call DummyCallbackNotifierImpl.sendDisputeAlreadyCreated() {}", dispute.getId()); + } + + @Override + public void sendDisputePoolingExpired(Dispute dispute) { + log.debug("Trying to call DummyCallbackNotifierImpl.sendDisputePoolingExpired() {}", dispute.getId()); + } + + @Override + public void sendDisputeReadyForCreateAdjustment(List disputes) { + log.debug("Trying to call DummyCallbackNotifierImpl.sendDisputeReadyForCreateAdjustment() {}", disputes.size()); + } + + @Override + public void sendDisputeFailedReviewRequired(Dispute dispute, String errorCode, String errorDescription) { + log.debug("Trying to call DummyCallbackNotifierImpl.sendDisputeFailedReviewRequired() {}", dispute.getId()); + } +} diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java b/src/main/java/dev/vality/disputes/admin/management/AdminManagementDisputesService.java similarity index 96% rename from src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java rename to src/main/java/dev/vality/disputes/admin/management/AdminManagementDisputesService.java index 60e3bd5..240ba7b 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java +++ b/src/main/java/dev/vality/disputes/admin/management/AdminManagementDisputesService.java @@ -1,4 +1,4 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; import dev.vality.disputes.admin.*; import dev.vality.disputes.dao.DisputeDao; @@ -31,7 +31,7 @@ import static dev.vality.disputes.api.service.ApiDisputesService.DISPUTE_PENDING @Service @RequiredArgsConstructor @SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) -public class ManualParsingDisputesService { +public class AdminManagementDisputesService { private final DisputeDao disputeDao; private final ProviderDisputeDao providerDisputeDao; @@ -48,11 +48,12 @@ public class ManualParsingDisputesService { return; } var cancelReason = cancelParams.getCancelReason().orElse(null); + var mapping = cancelParams.getMapping().orElse(null); log.debug("GetForUpdateSkipLocked has been found {}", dispute); if (DISPUTE_PENDING.contains(dispute.getStatus())) { // используется не failed, а cancelled чтоб можно было понять, что зафейлен по внешнему вызову - log.warn("Trying to set cancelled Dispute status {}, {}", dispute, cancelReason); - disputeDao.update(dispute.getId(), DisputeStatus.cancelled, cancelReason); + log.warn("Trying to set cancelled Dispute status {}, {}, {}", dispute, mapping, cancelReason); + disputeDao.update(dispute.getId(), DisputeStatus.cancelled, cancelReason, mapping); log.debug("Dispute status has been set to cancelled {}", dispute); } else { log.info("Request was skipped by inappropriate status {}", dispute); @@ -133,6 +134,7 @@ public class ManualParsingDisputesService { disputeResult.setProviderTrxId(dispute.getProviderTrxId()); disputeResult.setStatus(dispute.getStatus().name()); disputeResult.setErrorMessage(dispute.getErrorMessage()); + disputeResult.setMapping(dispute.getMapping()); disputeResult.setAmount(String.valueOf(dispute.getAmount())); disputeResult.setChangedAmount(Optional.ofNullable(dispute.getChangedAmount()) .map(String::valueOf) diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java b/src/main/java/dev/vality/disputes/admin/management/AdminManagementHandler.java similarity index 69% rename from src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java rename to src/main/java/dev/vality/disputes/admin/management/AdminManagementHandler.java index 3122fbb..3170c45 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java +++ b/src/main/java/dev/vality/disputes/admin/management/AdminManagementHandler.java @@ -1,4 +1,4 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; import dev.vality.disputes.admin.*; import lombok.RequiredArgsConstructor; @@ -12,28 +12,28 @@ import java.util.ArrayList; @RequiredArgsConstructor @Slf4j @SuppressWarnings({"ParameterName", "LineLength"}) -public class ManualParsingHandler implements ManualParsingServiceSrv.Iface { +public class AdminManagementHandler implements AdminManagementServiceSrv.Iface { - private final ManualParsingDisputesService manualParsingDisputesService; + private final AdminManagementDisputesService adminManagementDisputesService; @Override public void cancelPending(CancelParamsRequest cancelParamsRequest) throws TException { for (var cancelParam : cancelParamsRequest.getCancelParams()) { - manualParsingDisputesService.cancelPendingDispute(cancelParam); + adminManagementDisputesService.cancelPendingDispute(cancelParam); } } @Override public void approvePending(ApproveParamsRequest approveParamsRequest) throws TException { for (var approveParam : approveParamsRequest.getApproveParams()) { - manualParsingDisputesService.approvePendingDispute(approveParam); + adminManagementDisputesService.approvePendingDispute(approveParam); } } @Override public void bindCreated(BindParamsRequest bindParamsRequest) throws TException { for (var bindParam : bindParamsRequest.getBindParams()) { - manualParsingDisputesService.bindCreatedDispute(bindParam); + adminManagementDisputesService.bindCreatedDispute(bindParam); } } @@ -41,7 +41,7 @@ public class ManualParsingHandler implements ManualParsingServiceSrv.Iface { public DisputeResult getDisputes(DisputeParamsRequest disputeParamsRequest) throws TException { var disputeResult = new DisputeResult(new ArrayList<>()); for (var disputeParams : disputeParamsRequest.getDisputeParams()) { - var dispute = manualParsingDisputesService.getDispute(disputeParams, disputeParamsRequest.isWithAttachments()); + var dispute = adminManagementDisputesService.getDispute(disputeParams, disputeParamsRequest.isWithAttachments()); if (dispute != null) { disputeResult.getDisputes().add(dispute); } diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java b/src/main/java/dev/vality/disputes/admin/management/MdcTopicProducer.java similarity index 67% rename from src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java rename to src/main/java/dev/vality/disputes/admin/management/MdcTopicProducer.java index 1b6cb66..a065798 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java +++ b/src/main/java/dev/vality/disputes/admin/management/MdcTopicProducer.java @@ -1,8 +1,7 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.Dispute; -import dev.vality.disputes.provider.Attachment; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; @@ -11,26 +10,28 @@ import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Slf4j @SuppressWarnings({"ParameterName", "LineLength"}) -public class ManualParsingTopic { +public class MdcTopicProducer { - @Value("${manual-parsing-topic.enabled}") + @Value("${service.mdc-topic-producer.enabled}") private boolean enabled; - public void sendCreated(Dispute dispute, List attachments, DisputeStatus disputeStatus) { + public void sendCreated(Dispute dispute, DisputeStatus disputeStatus, String errorMessage) { if (!enabled) { return; } - var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap() : MDC.getCopyOfContextMap(); + var contextMap = getContextMap(); contextMap.put("dispute_id", dispute.getId().toString()); - var attachmentsCollect = attachments.stream().map(Attachment::toString).collect(Collectors.joining(", ")); - contextMap.put("dispute_attachments", attachmentsCollect); contextMap.put("dispute_status", disputeStatus.name()); + if (errorMessage != null) { + contextMap.put("dispute_error_message", errorMessage); + } MDC.setContextMap(contextMap); log.warn("Manual parsing case"); MDC.clear(); @@ -40,7 +41,7 @@ public class ManualParsingTopic { if (!enabled) { return; } - var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap() : MDC.getCopyOfContextMap(); + var contextMap = getContextMap(); contextMap.put("dispute_id", dispute.getId().toString()); contextMap.put("dispute_status", DisputeStatus.manual_pending.name()); MDC.setContextMap(contextMap); @@ -52,11 +53,15 @@ public class ManualParsingTopic { if (!enabled || disputes.isEmpty()) { return; } - var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap() : MDC.getCopyOfContextMap(); + var contextMap = getContextMap(); contextMap.put("dispute_ids", disputes.stream().map(Dispute::getId).map(String::valueOf).collect(Collectors.joining(", "))); contextMap.put("dispute_status", DisputeStatus.create_adjustment.name()); MDC.setContextMap(contextMap); log.warn("Ready for CreateAdjustments case"); MDC.clear(); } + + private Map getContextMap() { + return MDC.getCopyOfContextMap() == null ? new HashMap<>() : MDC.getCopyOfContextMap(); + } } diff --git a/src/main/java/dev/vality/disputes/api/CancelController.java b/src/main/java/dev/vality/disputes/api/CancelController.java deleted file mode 100644 index 180a1f4..0000000 --- a/src/main/java/dev/vality/disputes/api/CancelController.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.vality.disputes.api; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import dev.vality.disputes.admin.CancelParamsRequest; -import dev.vality.disputes.admin.ManualParsingServiceSrv; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping({"/disputes"}) -@Slf4j -public class CancelController { - - private final ManualParsingServiceSrv.Iface manualParsingHandler; - private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jdk8Module()); - - @PostMapping("/cancel") - @SneakyThrows - public void cancelPending(@RequestBody String body) { - log.debug("cancelPending {}", body); - manualParsingHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class)); - } -} diff --git a/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java b/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java index 694d3e8..4ececbc 100644 --- a/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java +++ b/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java @@ -12,8 +12,8 @@ public class Status200ResponseConverter { public Status200Response convert(Dispute dispute) { var body = new Status200Response(); body.setStatus(getStatus(dispute)); - if (!StringUtils.isBlank(dispute.getErrorMessage())) { - body.setReason(new GeneralError(dispute.getErrorMessage())); + if (!StringUtils.isBlank(dispute.getMapping())) { + body.setReason(new GeneralError(dispute.getMapping())); } if (dispute.getChangedAmount() != null) { body.setChangedAmount(dispute.getChangedAmount()); diff --git a/src/main/java/dev/vality/disputes/config/ApplicationConfig.java b/src/main/java/dev/vality/disputes/config/ApplicationConfig.java index 3364f95..d244bb3 100644 --- a/src/main/java/dev/vality/disputes/config/ApplicationConfig.java +++ b/src/main/java/dev/vality/disputes/config/ApplicationConfig.java @@ -5,6 +5,8 @@ import dev.vality.bouncer.decisions.ArbiterSrv; import dev.vality.damsel.domain_config.RepositoryClientSrv; import dev.vality.damsel.payment_processing.InvoicingSrv; import dev.vality.damsel.payment_processing.PartyManagementSrv; +import dev.vality.disputes.admin.AdminCallbackServiceSrv; +import dev.vality.disputes.provider.ProviderDisputesServiceSrv; import dev.vality.file.storage.FileStorageSrv; import dev.vality.token.keeper.TokenAuthenticatorSrv; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; @@ -81,6 +83,26 @@ public class ApplicationConfig { .build(PartyManagementSrv.Iface.class); } + @Bean + public ProviderDisputesServiceSrv.Iface providerDisputesTgBotClient( + @Value("${service.disputes-tg-bot.provider.url}") Resource resource, + @Value("${service.disputes-tg-bot.provider.networkTimeout}") int networkTimeout) throws IOException { + return new THSpawnClientBuilder() + .withNetworkTimeout(networkTimeout) + .withAddress(resource.getURI()) + .build(ProviderDisputesServiceSrv.Iface.class); + } + + @Bean + public AdminCallbackServiceSrv.Iface adminCallbackDisputesTgBotClient( + @Value("${service.disputes-tg-bot.admin.url}") Resource resource, + @Value("${service.disputes-tg-bot.admin.networkTimeout}") int networkTimeout) throws IOException { + return new THSpawnClientBuilder() + .withNetworkTimeout(networkTimeout) + .withAddress(resource.getURI()) + .build(AdminCallbackServiceSrv.Iface.class); + } + @Bean public ExecutorService disputesThreadPool(@Value("${dispute.batchSize}") int threadPoolSize) { final var threadFactory = new ThreadFactoryBuilder() diff --git a/src/main/java/dev/vality/disputes/config/NetworkConfig.java b/src/main/java/dev/vality/disputes/config/NetworkConfig.java index 06b4672..1bc8610 100644 --- a/src/main/java/dev/vality/disputes/config/NetworkConfig.java +++ b/src/main/java/dev/vality/disputes/config/NetworkConfig.java @@ -24,7 +24,7 @@ public class NetworkConfig { public static final String HEALTH = "/actuator/health"; public static final String MERCHANT = "/disputes-api/v1/merchant"; - public static final String MANUAL = "/disputes-api/v1/manual-parsing"; + public static final String ADMIN_MANAGEMENT = "/disputes-api/v1/admin-management"; public static final String CALLBACK = "/disputes-api/v1/callback"; @Bean @@ -39,7 +39,7 @@ public class NetworkConfig { var enabledPaths = servletPath.startsWith(restEndpoint) || servletPath.startsWith(HEALTH) || servletPath.startsWith(MERCHANT) - || servletPath.startsWith(MANUAL) + || servletPath.startsWith(ADMIN_MANAGEMENT) || servletPath.startsWith(CALLBACK); if ((request.getLocalPort() == restPort) && !enabledPaths) { response.sendError(404, "Unknown address"); diff --git a/src/main/java/dev/vality/disputes/config/OtelConfig.java b/src/main/java/dev/vality/disputes/config/OtelConfig.java new file mode 100644 index 0000000..16978e2 --- /dev/null +++ b/src/main/java/dev/vality/disputes/config/OtelConfig.java @@ -0,0 +1,68 @@ +package dev.vality.disputes.config; + +import dev.vality.disputes.config.properties.OtelProperties; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class OtelConfig { + + private final OtelProperties otelProperties; + + @Value("${spring.application.name}") + private String applicationName; + + @Bean + public OpenTelemetry openTelemetryConfig() { + var resource = Resource.getDefault() + .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); + var sdkTracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(OtlpHttpSpanExporter.builder() + .setEndpoint(otelProperties.getResource()) + .setTimeout(Duration.ofMillis(otelProperties.getTimeout())) + .build()) + .build()) + .setSampler(Sampler.alwaysOn()) + .setResource(resource) + .build(); + var openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .build(); + registerGlobalOpenTelemetry(openTelemetrySdk); + return openTelemetrySdk; + } + + private static void registerGlobalOpenTelemetry(OpenTelemetry openTelemetry) { + try { + GlobalOpenTelemetry.set(openTelemetry); + } catch (Exception e) { + log.warn("please initialize the ObservabilitySdk before starting the application"); + GlobalOpenTelemetry.resetForTest(); + try { + GlobalOpenTelemetry.set(openTelemetry); + } catch (Exception ex) { + log.warn("unable to set GlobalOpenTelemetry", ex); + } + } + } +} diff --git a/src/main/java/dev/vality/disputes/config/properties/OtelProperties.java b/src/main/java/dev/vality/disputes/config/properties/OtelProperties.java new file mode 100644 index 0000000..1aedd3d --- /dev/null +++ b/src/main/java/dev/vality/disputes/config/properties/OtelProperties.java @@ -0,0 +1,17 @@ +package dev.vality.disputes.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "otel") +public class OtelProperties { + + private String resource; + private Long timeout; + +} diff --git a/src/main/java/dev/vality/disputes/constant/ModerationPrefix.java b/src/main/java/dev/vality/disputes/constant/ModerationPrefix.java new file mode 100644 index 0000000..081eaa2 --- /dev/null +++ b/src/main/java/dev/vality/disputes/constant/ModerationPrefix.java @@ -0,0 +1,7 @@ +package dev.vality.disputes.constant; + +public class ModerationPrefix { + + public static final String DISPUTES_UNKNOWN_MAPPING = "disputes_unknown_mapping"; + +} diff --git a/src/main/java/dev/vality/disputes/dao/DisputeDao.java b/src/main/java/dev/vality/disputes/dao/DisputeDao.java index 6c5bd39..bff8e24 100644 --- a/src/main/java/dev/vality/disputes/dao/DisputeDao.java +++ b/src/main/java/dev/vality/disputes/dao/DisputeDao.java @@ -113,28 +113,32 @@ public class DisputeDao extends AbstractGenericDao { } public UUID update(UUID disputeId, DisputeStatus status) { - return update(disputeId, status, null, null, null, null); + return update(disputeId, status, null, null, null, null, null); } public UUID update(UUID disputeId, DisputeStatus status, LocalDateTime nextCheckAfter) { - return update(disputeId, status, nextCheckAfter, null, null, null); + return update(disputeId, status, nextCheckAfter, null, null, null, null); } public UUID update(UUID disputeId, DisputeStatus status, String errorMessage) { - return update(disputeId, status, null, errorMessage, null, null); + return update(disputeId, status, null, errorMessage, null, null, null); + } + + public UUID update(UUID disputeId, DisputeStatus status, String errorMessage, String mapping) { + return update(disputeId, status, null, errorMessage, null, null, mapping); } public UUID update(UUID disputeId, DisputeStatus status, Long changedAmount) { - return update(disputeId, status, null, null, changedAmount, null); + return update(disputeId, status, null, null, changedAmount, null, null); } public UUID update(UUID disputeId, DisputeStatus status, Long changedAmount, Boolean skipCallHgForCreateAdjustment) { - return update(disputeId, status, null, null, changedAmount, skipCallHgForCreateAdjustment); + return update(disputeId, status, null, null, changedAmount, skipCallHgForCreateAdjustment, null); } private UUID update(UUID disputeId, DisputeStatus status, LocalDateTime nextCheckAfter, String errorMessage, - Long changedAmount, Boolean skipCallHgForCreateAdjustment) { + Long changedAmount, Boolean skipCallHgForCreateAdjustment, String mapping) { var set = getDslContext().update(DISPUTE) .set(DISPUTE.STATUS, status); if (nextCheckAfter != null) { @@ -143,6 +147,9 @@ public class DisputeDao extends AbstractGenericDao { if (errorMessage != null) { set = set.set(DISPUTE.ERROR_MESSAGE, errorMessage); } + if (mapping != null) { + set = set.set(DISPUTE.MAPPING, mapping); + } if (changedAmount != null) { set = set.set(DISPUTE.CHANGED_AMOUNT, changedAmount); } diff --git a/src/main/java/dev/vality/disputes/merchant/MerchantDisputesHandler.java b/src/main/java/dev/vality/disputes/merchant/MerchantDisputesHandler.java index 0fd9068..1b6f40f 100644 --- a/src/main/java/dev/vality/disputes/merchant/MerchantDisputesHandler.java +++ b/src/main/java/dev/vality/disputes/merchant/MerchantDisputesHandler.java @@ -31,11 +31,12 @@ public class MerchantDisputesHandler implements MerchantDisputesServiceSrv.Iface } @Override - public DisputeStatusResult checkDisputeStatus(DisputeContext disputeContext) throws DisputeNotFound, TException { + public DisputeStatusResult checkDisputeStatus(DisputeContext disputeContext) throws TException { var response = disputesApiDelegate.status(getRequestID(), disputeContext.getDisputeId(), false).getBody(); return switch (response.getStatus()) { case PENDING -> DisputeStatusResult.statusPending(new DisputeStatusPendingResult()); - case FAILED -> DisputeStatusResult.statusFail(new DisputeStatusFailResult(getErrorMessage(response))); + case FAILED -> + DisputeStatusResult.statusFail(new DisputeStatusFailResult().setMapping(getMapping(response))); case SUCCEEDED -> DisputeStatusResult.statusSuccess(new DisputeStatusSuccessResult()); }; } @@ -44,7 +45,7 @@ public class MerchantDisputesHandler implements MerchantDisputesServiceSrv.Iface return UUID.randomUUID().toString(); } - private String getErrorMessage(Status200Response response) { + private String getMapping(Status200Response response) { return Optional.ofNullable(response.getReason()) .map(GeneralError::getMessage) .orElse(null); diff --git a/src/main/java/dev/vality/disputes/schedule/TaskReadyForCreateAdjustmentsService.java b/src/main/java/dev/vality/disputes/schedule/TaskReadyForCreateAdjustmentsService.java index b890e39..f9cb83a 100644 --- a/src/main/java/dev/vality/disputes/schedule/TaskReadyForCreateAdjustmentsService.java +++ b/src/main/java/dev/vality/disputes/schedule/TaskReadyForCreateAdjustmentsService.java @@ -1,6 +1,7 @@ package dev.vality.disputes.schedule; -import dev.vality.disputes.manualparsing.ManualParsingTopic; +import dev.vality.disputes.admin.callback.CallbackNotifier; +import dev.vality.disputes.admin.management.MdcTopicProducer; import dev.vality.disputes.schedule.service.CreateAdjustmentsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,13 +17,15 @@ import org.springframework.stereotype.Service; public class TaskReadyForCreateAdjustmentsService { private final CreateAdjustmentsService createAdjustmentsService; - private final ManualParsingTopic manualParsingTopic; + private final CallbackNotifier callbackNotifier; + private final MdcTopicProducer mdcTopicProducer; @Scheduled(fixedDelayString = "${dispute.fixedDelayReadyForCreateAdjustments}", initialDelayString = "${dispute.initialDelayReadyForCreateAdjustments}") public void processPending() { log.debug("Processing ReadyForCreateAdjustments get started"); var disputes = createAdjustmentsService.getReadyDisputesForCreateAdjustment(); - manualParsingTopic.sendReadyForCreateAdjustments(disputes); + mdcTopicProducer.sendReadyForCreateAdjustments(disputes); + callbackNotifier.sendDisputeReadyForCreateAdjustment(disputes); log.info("ReadyForCreateAdjustments were processed"); } } diff --git a/src/main/java/dev/vality/disputes/schedule/catcher/WRuntimeExceptionCatcher.java b/src/main/java/dev/vality/disputes/schedule/catcher/WRuntimeExceptionCatcher.java new file mode 100644 index 0000000..d8690dc --- /dev/null +++ b/src/main/java/dev/vality/disputes/schedule/catcher/WRuntimeExceptionCatcher.java @@ -0,0 +1,46 @@ +package dev.vality.disputes.schedule.catcher; + +import dev.vality.disputes.schedule.model.ProviderData; +import dev.vality.disputes.schedule.service.ExternalGatewayChecker; +import dev.vality.woody.api.flow.error.WRuntimeException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.function.Consumer; + +@Slf4j +@Service +@RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) +public class WRuntimeExceptionCatcher { + + private final ExternalGatewayChecker externalGatewayChecker; + + public void catchProvidersDisputesApiNotExist(ProviderData providerData, Runnable runnable, Runnable defaultRemoteClientRunnable) { + try { + runnable.run(); + } catch (WRuntimeException e) { + if (externalGatewayChecker.isProvidersDisputesApiNotExist(providerData, e)) { + // отправлять на ручной разбор, если API диспутов на провайдере не реализовано + // (тогда при тесте соединения вернется 404) + log.warn("Trying to call defaultRemoteClient.createDispute(), externalGatewayChecker", e); + defaultRemoteClientRunnable.run(); + return; + } + throw e; + } + } + + public void catchUnexpectedResultMapping(Runnable runnable, Consumer unexpectedResultMappingHandler) { + try { + runnable.run(); + } catch (WRuntimeException e) { + if (externalGatewayChecker.isProvidersDisputesUnexpectedResultMapping(e)) { + unexpectedResultMappingHandler.accept(e); + return; + } + throw e; + } + } +} diff --git a/src/main/java/dev/vality/disputes/schedule/client/DefaultRemoteClient.java b/src/main/java/dev/vality/disputes/schedule/client/DefaultRemoteClient.java new file mode 100644 index 0000000..35c1c91 --- /dev/null +++ b/src/main/java/dev/vality/disputes/schedule/client/DefaultRemoteClient.java @@ -0,0 +1,16 @@ +package dev.vality.disputes.schedule.client; + +import dev.vality.disputes.domain.tables.pojos.Dispute; +import dev.vality.disputes.provider.Attachment; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.schedule.model.ProviderData; + +import java.util.List; + +public interface DefaultRemoteClient { + + Boolean routeUrlEquals(ProviderData providerData); + + DisputeCreatedResult createDispute(Dispute dispute, List attachments, ProviderData providerData); + +} diff --git a/src/main/java/dev/vality/disputes/schedule/client/DisputesTgBotRemoteClientImpl.java b/src/main/java/dev/vality/disputes/schedule/client/DisputesTgBotRemoteClientImpl.java new file mode 100644 index 0000000..1128d79 --- /dev/null +++ b/src/main/java/dev/vality/disputes/schedule/client/DisputesTgBotRemoteClientImpl.java @@ -0,0 +1,46 @@ +package dev.vality.disputes.schedule.client; + +import dev.vality.disputes.domain.tables.pojos.Dispute; +import dev.vality.disputes.provider.Attachment; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.schedule.converter.DisputeParamsConverter; +import dev.vality.disputes.schedule.model.ProviderData; +import dev.vality.disputes.service.external.DisputesTgBotService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@ConditionalOnProperty(value = "service.disputes-tg-bot.provider.enabled", havingValue = "true", matchIfMissing = true) +@RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength"}) +public class DisputesTgBotRemoteClientImpl implements DefaultRemoteClient { + + private final DisputesTgBotService disputesTgBotService; + private final DisputeParamsConverter disputeParamsConverter; + + @Value("${service.disputes-tg-bot.provider.url}") + private String routeUrl; + + @Override + public Boolean routeUrlEquals(ProviderData providerData) { + return StringUtils.equalsIgnoreCase(providerData.getRouteUrl(), routeUrl); + } + + @Override + public DisputeCreatedResult createDispute(Dispute dispute, List attachments, ProviderData providerData) { + log.debug("Trying to build disputeParams {}", dispute.getId()); + var disputeParams = disputeParamsConverter.convert(dispute, attachments, providerData.getOptions()); + providerData.setRouteUrl(routeUrl); + log.debug("Trying to disputesTgBotService.createDispute() call {}", dispute.getId()); + var result = disputesTgBotService.createDispute(disputeParams); + log.info("disputesTgBotService.createDispute() has been called {} {}", dispute.getId(), result); + return result; + } +} diff --git a/src/main/java/dev/vality/disputes/schedule/client/DummyRemoteClientImpl.java b/src/main/java/dev/vality/disputes/schedule/client/DummyRemoteClientImpl.java new file mode 100644 index 0000000..a514239 --- /dev/null +++ b/src/main/java/dev/vality/disputes/schedule/client/DummyRemoteClientImpl.java @@ -0,0 +1,37 @@ +package dev.vality.disputes.schedule.client; + +import dev.vality.disputes.domain.tables.pojos.Dispute; +import dev.vality.disputes.provider.Attachment; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.provider.DisputeCreatedSuccessResult; +import dev.vality.disputes.schedule.model.ProviderData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Service +@ConditionalOnProperty(value = "service.disputes-tg-bot.provider.enabled", havingValue = "false") +@RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength"}) +public class DummyRemoteClientImpl implements DefaultRemoteClient { + + private final String routeUrl = "tg-bot"; + + @Override + public Boolean routeUrlEquals(ProviderData providerData) { + return StringUtils.equalsIgnoreCase(providerData.getRouteUrl(), routeUrl); + } + + @Override + public DisputeCreatedResult createDispute(Dispute dispute, List attachments, ProviderData providerData) { + log.debug("Trying to call DummyRemoteClientImpl.createDispute() {}", dispute.getId()); + providerData.setRouteUrl(routeUrl); + return DisputeCreatedResult.successResult(new DisputeCreatedSuccessResult(UUID.randomUUID().toString())); + } +} diff --git a/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java b/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java index a463192..76cf036 100644 --- a/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java +++ b/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java @@ -9,6 +9,7 @@ import dev.vality.disputes.schedule.converter.DisputeContextConverter; import dev.vality.disputes.schedule.converter.DisputeParamsConverter; import dev.vality.disputes.schedule.model.ProviderData; import dev.vality.disputes.schedule.service.ProviderIfaceBuilder; +import dev.vality.disputes.schedule.service.ProviderRouting; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -22,17 +23,18 @@ import java.util.List; @SuppressWarnings({"ParameterName", "LineLength"}) public class RemoteClient { + private final ProviderRouting providerRouting; private final ProviderIfaceBuilder providerIfaceBuilder; - private final DisputeContextConverter disputeContextConverter; private final DisputeParamsConverter disputeParamsConverter; + private final DisputeContextConverter disputeContextConverter; @SneakyThrows public DisputeCreatedResult createDispute(Dispute dispute, List attachments, ProviderData providerData) { - log.debug("Trying to call dominant for RemoteClient {}", dispute.getId()); + providerRouting.initRouteUrl(providerData); + log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); + var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData.getRouteUrl()); log.debug("Trying to build disputeParams {}", dispute.getId()); var disputeParams = disputeParamsConverter.convert(dispute, attachments, providerData.getOptions()); - log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); - var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData); log.debug("Trying to routed remote provider's createDispute() call {}", dispute.getId()); var result = remoteClient.createDispute(disputeParams); log.info("Routed remote provider's createDispute() has been called {} {}", dispute.getId(), result); @@ -41,11 +43,11 @@ public class RemoteClient { @SneakyThrows public DisputeStatusResult checkDisputeStatus(Dispute dispute, ProviderDispute providerDispute, ProviderData providerData) { - log.debug("Trying to call dominant for RemoteClient {}", dispute.getId()); + providerRouting.initRouteUrl(providerData); + log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); + var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData.getRouteUrl()); log.debug("Trying to build disputeContext {}", dispute.getId()); var disputeContext = disputeContextConverter.convert(dispute, providerDispute, providerData.getOptions()); - log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); - var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData); log.debug("Trying to routed remote provider's checkDisputeStatus() call {}", dispute.getId()); var result = remoteClient.checkDisputeStatus(disputeContext); log.info("Routed remote provider's checkDisputeStatus() has been called {} {}", dispute.getId(), result); diff --git a/src/main/java/dev/vality/disputes/schedule/handler/DisputeCreateResultHandler.java b/src/main/java/dev/vality/disputes/schedule/handler/DisputeCreateResultHandler.java new file mode 100644 index 0000000..9fa3ffc --- /dev/null +++ b/src/main/java/dev/vality/disputes/schedule/handler/DisputeCreateResultHandler.java @@ -0,0 +1,83 @@ +package dev.vality.disputes.schedule.handler; + +import dev.vality.disputes.admin.callback.CallbackNotifier; +import dev.vality.disputes.admin.management.MdcTopicProducer; +import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.dao.ProviderDisputeDao; +import dev.vality.disputes.domain.enums.DisputeStatus; +import dev.vality.disputes.domain.tables.pojos.Dispute; +import dev.vality.disputes.domain.tables.pojos.ProviderDispute; +import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.schedule.client.DefaultRemoteClient; +import dev.vality.disputes.schedule.model.ProviderData; +import dev.vality.disputes.utils.ErrorFormatter; +import dev.vality.woody.api.flow.error.WRuntimeException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING; + +@Slf4j +@Service +@RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) +public class DisputeCreateResultHandler { + + private final DisputeDao disputeDao; + private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; + private final DefaultRemoteClient defaultRemoteClient; + private final ProviderDisputeDao providerDisputeDao; + private final CallbackNotifier callbackNotifier; + private final MdcTopicProducer mdcTopicProducer; + + @Transactional(propagation = Propagation.REQUIRED) + public void handleSuccessResult(Dispute dispute, DisputeCreatedResult result, ProviderData providerData) { + var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, providerData.getOptions()); + providerDisputeDao.save(new ProviderDispute(result.getSuccessResult().getProviderDisputeId(), dispute.getId())); + log.info("Trying to set pending Dispute status {}, {}", dispute, result); + var isDefaultRouteUrl = defaultRemoteClient.routeUrlEquals(providerData); + disputeDao.update(dispute.getId(), !isDefaultRouteUrl ? DisputeStatus.pending : DisputeStatus.manual_pending, nextCheckAfter); + log.debug("Dispute status has been set to pending {}", dispute.getId()); + } + + @Transactional(propagation = Propagation.REQUIRED) + public void handleFailResult(Dispute dispute, DisputeCreatedResult result) { + var failure = result.getFailResult().getFailure(); + var errorMessage = ErrorFormatter.getErrorMessage(failure); + if (errorMessage.startsWith(DISPUTES_UNKNOWN_MAPPING)) { + handleUnexpectedResultMapping(dispute, failure.getCode(), failure.getReason()); + } else { + log.warn("Trying to set failed Dispute status {}, {}", dispute.getId(), errorMessage); + disputeDao.update(dispute.getId(), DisputeStatus.failed, errorMessage, failure.getCode()); + log.debug("Dispute status has been set to failed {}", dispute.getId()); + } + } + + @Transactional(propagation = Propagation.REQUIRED) + public void handleAlreadyExistResult(Dispute dispute) { + callbackNotifier.sendDisputeAlreadyCreated(dispute); + mdcTopicProducer.sendCreated(dispute, DisputeStatus.already_exist_created, "dispute already exist"); + log.info("Trying to set {} Dispute status {}", DisputeStatus.already_exist_created, dispute); + disputeDao.update(dispute.getId(), DisputeStatus.already_exist_created); + log.debug("Dispute status has been set to {} {}", DisputeStatus.already_exist_created, dispute.getId()); + } + + @Transactional(propagation = Propagation.REQUIRED) + public void handleUnexpectedResultMapping(Dispute dispute, WRuntimeException e) { + var errorMessage = e.getErrorDefinition().getErrorReason(); + handleUnexpectedResultMapping(dispute, errorMessage, null); + } + + private void handleUnexpectedResultMapping(Dispute dispute, String errorCode, String errorDescription) { + callbackNotifier.sendDisputeFailedReviewRequired(dispute, errorCode, errorDescription); + var errorMessage = ErrorFormatter.getErrorMessage(errorCode, errorDescription); + mdcTopicProducer.sendCreated(dispute, DisputeStatus.manual_created, errorMessage); + log.warn("Trying to set manual_created Dispute status {}, {}", dispute.getId(), errorMessage); + disputeDao.update(dispute.getId(), DisputeStatus.manual_created, errorMessage); + log.debug("Dispute status has been set to manual_created {}", dispute.getId()); + } +} diff --git a/src/main/java/dev/vality/disputes/schedule/handler/DisputeStatusResultHandler.java b/src/main/java/dev/vality/disputes/schedule/handler/DisputeStatusResultHandler.java index 1d681de..a5a40c3 100644 --- a/src/main/java/dev/vality/disputes/schedule/handler/DisputeStatusResultHandler.java +++ b/src/main/java/dev/vality/disputes/schedule/handler/DisputeStatusResultHandler.java @@ -1,19 +1,26 @@ package dev.vality.disputes.schedule.handler; +import dev.vality.disputes.admin.callback.CallbackNotifier; +import dev.vality.disputes.admin.management.MdcTopicProducer; +import dev.vality.disputes.constant.ErrorReason; import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; import dev.vality.disputes.provider.DisputeStatusResult; import dev.vality.disputes.utils.ErrorFormatter; +import dev.vality.woody.api.flow.error.WRuntimeException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Map; +import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING; + @Slf4j @Service @RequiredArgsConstructor @@ -22,6 +29,8 @@ public class DisputeStatusResultHandler { private final DisputeDao disputeDao; private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; + private final CallbackNotifier callbackNotifier; + private final MdcTopicProducer mdcTopicProducer; @Transactional(propagation = Propagation.REQUIRED) public void handleStatusPending(Dispute dispute, DisputeStatusResult result, Map options) { @@ -36,17 +45,48 @@ public class DisputeStatusResultHandler { @Transactional(propagation = Propagation.REQUIRED) public void handleStatusFail(Dispute dispute, DisputeStatusResult result) { - var errorMessage = ErrorFormatter.getErrorMessage(result.getStatusFail().getFailure()); - log.warn("Trying to set failed Dispute status {}, {}", dispute.getId(), errorMessage); - disputeDao.update(dispute.getId(), DisputeStatus.failed, errorMessage); - log.debug("Dispute status has been set to failed {}", dispute.getId()); + var failure = result.getStatusFail().getFailure(); + var errorMessage = ErrorFormatter.getErrorMessage(failure); + if (errorMessage.startsWith(DISPUTES_UNKNOWN_MAPPING)) { + handleUnexpectedResultMapping(dispute, failure.getCode(), failure.getReason()); + } else { + log.warn("Trying to set failed Dispute status {}, {}", dispute.getId(), errorMessage); + disputeDao.update(dispute.getId(), DisputeStatus.failed, errorMessage, failure.getCode()); + log.debug("Dispute status has been set to failed {}", dispute.getId()); + } } @Transactional(propagation = Propagation.REQUIRED) public void handleStatusSuccess(Dispute dispute, DisputeStatusResult result) { + callbackNotifier.sendDisputeReadyForCreateAdjustment(List.of(dispute)); + mdcTopicProducer.sendReadyForCreateAdjustments(List.of(dispute)); var changedAmount = result.getStatusSuccess().getChangedAmount().orElse(null); log.info("Trying to set create_adjustment Dispute status {}, {}", dispute, result); disputeDao.update(dispute.getId(), DisputeStatus.create_adjustment, changedAmount); log.debug("Dispute status has been set to create_adjustment {}", dispute.getId()); } + + @Transactional(propagation = Propagation.REQUIRED) + public void handlePoolingExpired(Dispute dispute) { + callbackNotifier.sendDisputePoolingExpired(dispute); + mdcTopicProducer.sendPoolingExpired(dispute); + log.warn("Trying to set manual_pending Dispute status with POOLING_EXPIRED error reason {}", dispute.getId()); + disputeDao.update(dispute.getId(), DisputeStatus.manual_pending, ErrorReason.POOLING_EXPIRED); + log.debug("Dispute status has been set to manual_pending {}", dispute.getId()); + } + + @Transactional(propagation = Propagation.REQUIRED) + public void handleUnexpectedResultMapping(Dispute dispute, WRuntimeException e) { + var errorMessage = e.getErrorDefinition().getErrorReason(); + handleUnexpectedResultMapping(dispute, errorMessage, null); + } + + private void handleUnexpectedResultMapping(Dispute dispute, String errorCode, String errorDescription) { + callbackNotifier.sendDisputeFailedReviewRequired(dispute, errorCode, errorDescription); + var errorMessage = ErrorFormatter.getErrorMessage(errorCode, errorDescription); + mdcTopicProducer.sendCreated(dispute, DisputeStatus.manual_pending, errorMessage); + log.warn("Trying to set manual_pending Dispute status {}, {}", dispute.getId(), errorMessage); + disputeDao.update(dispute.getId(), DisputeStatus.manual_pending, errorMessage); + log.debug("Dispute status has been set to manual_pending {}", dispute.getId()); + } } diff --git a/src/main/java/dev/vality/disputes/schedule/model/ProviderData.java b/src/main/java/dev/vality/disputes/schedule/model/ProviderData.java index 2c21e56..028e463 100644 --- a/src/main/java/dev/vality/disputes/schedule/model/ProviderData.java +++ b/src/main/java/dev/vality/disputes/schedule/model/ProviderData.java @@ -11,5 +11,6 @@ public class ProviderData { private Map options; private String defaultProviderUrl; + private String routeUrl; } diff --git a/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java b/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java index cd957f5..484ba33 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java @@ -3,18 +3,15 @@ package dev.vality.disputes.schedule.service; import dev.vality.damsel.payment_processing.InvoicePayment; import dev.vality.disputes.constant.ErrorReason; import dev.vality.disputes.dao.DisputeDao; -import dev.vality.disputes.dao.ProviderDisputeDao; import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.Dispute; -import dev.vality.disputes.domain.tables.pojos.ProviderDispute; -import dev.vality.disputes.manualparsing.ManualParsingTopic; -import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; -import dev.vality.disputes.provider.Attachment; import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.schedule.catcher.WRuntimeExceptionCatcher; +import dev.vality.disputes.schedule.client.DefaultRemoteClient; import dev.vality.disputes.schedule.client.RemoteClient; +import dev.vality.disputes.schedule.handler.DisputeCreateResultHandler; +import dev.vality.disputes.schedule.model.ProviderData; import dev.vality.disputes.service.external.InvoicingService; -import dev.vality.disputes.utils.ErrorFormatter; -import dev.vality.woody.api.flow.error.WRuntimeException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -31,18 +28,17 @@ import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_PRO @Slf4j @Service @RequiredArgsConstructor -@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) +@SuppressWarnings({"MemberName", "ParameterName", "LineLength", "MissingSwitchDefault"}) public class CreatedDisputesService { private final RemoteClient remoteClient; private final DisputeDao disputeDao; - private final ProviderDisputeDao providerDisputeDao; private final CreatedAttachmentsService createdAttachmentsService; private final InvoicingService invoicingService; - private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; private final ProviderDataService providerDataService; - private final ExternalGatewayChecker externalGatewayChecker; - private final ManualParsingTopic manualParsingTopic; + private final DefaultRemoteClient defaultRemoteClient; + private final DisputeCreateResultHandler disputeCreateResultHandler; + private final WRuntimeExceptionCatcher wRuntimeExceptionCatcher; @Transactional(propagation = Propagation.REQUIRED) public List getCreatedDisputesForUpdateSkipLocked(int batchSize) { @@ -87,54 +83,40 @@ public class CreatedDisputesService { || isNotProvidersDisputesApiExist(options)) { // отправлять на ручной разбор, если выставлена опция // DISPUTE_FLOW_CAPTURED_BLOCKED или не выставлена DISPUTE_FLOW_PROVIDERS_API_EXIST - log.warn("finishTaskWithManualParsingFlowActivation, options capt={}, apiExist={}", isCapturedBlockedForDispute(options), isNotProvidersDisputesApiExist(options)); - finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); + log.warn("Trying to call defaultRemoteClient.createDispute(), options capt={}, apiExist={}", isCapturedBlockedForDispute(options), isNotProvidersDisputesApiExist(options)); + wRuntimeExceptionCatcher.catchUnexpectedResultMapping( + () -> { + var result = defaultRemoteClient.createDispute(dispute, attachments, providerData); + finishTask(dispute, result, providerData); + }, + e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e)); return; } - try { - var result = remoteClient.createDispute(dispute, attachments, providerData); - finishTask(dispute, attachments, result, options); - } catch (WRuntimeException e) { - if (externalGatewayChecker.isNotProvidersDisputesApiExist(providerData, e)) { - // отправлять на ручной разбор, если API диспутов на провайдере не реализовано - // (тогда при тесте соединения вернется 404) - log.warn("finishTaskWithManualParsingFlowActivation with externalGatewayChecker", e); - finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); - return; - } - throw e; - } + wRuntimeExceptionCatcher.catchUnexpectedResultMapping( + () -> wRuntimeExceptionCatcher.catchProvidersDisputesApiNotExist( + providerData, + () -> { + var result = remoteClient.createDispute(dispute, attachments, providerData); + finishTask(dispute, result, providerData); + }, + () -> wRuntimeExceptionCatcher.catchUnexpectedResultMapping( + () -> { + var result = defaultRemoteClient.createDispute(dispute, attachments, providerData); + finishTask(dispute, result, providerData); + }, + e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e))), + e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e)); } @Transactional(propagation = Propagation.REQUIRED) - void finishTask(Dispute dispute, List attachments, DisputeCreatedResult result, Map options) { + void finishTask(Dispute dispute, DisputeCreatedResult result, ProviderData providerData) { switch (result.getSetField()) { - case SUCCESS_RESULT -> { - var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options); - log.info("Trying to set pending Dispute status {}, {}", dispute, result); - providerDisputeDao.save(new ProviderDispute(result.getSuccessResult().getProviderDisputeId(), dispute.getId())); - disputeDao.update(dispute.getId(), DisputeStatus.pending, nextCheckAfter); - log.debug("Dispute status has been set to pending {}", dispute.getId()); - } - case FAIL_RESULT -> { - var errorMessage = ErrorFormatter.getErrorMessage(result.getFailResult().getFailure()); - log.warn("Trying to set failed Dispute status {}, {}", dispute.getId(), errorMessage); - disputeDao.update(dispute.getId(), DisputeStatus.failed, errorMessage); - log.debug("Dispute status has been set to failed {}", dispute.getId()); - } - case ALREADY_EXIST_RESULT -> - finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.already_exist_created); + case SUCCESS_RESULT -> disputeCreateResultHandler.handleSuccessResult(dispute, result, providerData); + case FAIL_RESULT -> disputeCreateResultHandler.handleFailResult(dispute, result); + case ALREADY_EXIST_RESULT -> disputeCreateResultHandler.handleAlreadyExistResult(dispute); } } - @Transactional(propagation = Propagation.REQUIRED) - void finishTaskWithManualParsingFlowActivation(Dispute dispute, List attachments, DisputeStatus disputeStatus) { - manualParsingTopic.sendCreated(dispute, attachments, disputeStatus); - log.info("Trying to set {} Dispute status {}", disputeStatus, dispute); - disputeDao.update(dispute.getId(), disputeStatus); - log.debug("Dispute status has been set to {} {}", disputeStatus, dispute.getId()); - } - private boolean isCapturedBlockedForDispute(Map options) { return options.containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED); } diff --git a/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java b/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java index 7c43c12..ee0c8d0 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java @@ -22,23 +22,32 @@ public class ExternalGatewayChecker { private final CloseableHttpClient httpClient; private final ProviderRouting providerRouting; - public boolean isNotProvidersDisputesApiExist(ProviderData providerData, WRuntimeException e) { + public boolean isProvidersDisputesUnexpectedResultMapping(WRuntimeException e) { return e.getErrorDefinition() != null && e.getErrorDefinition().getGenerationSource() == WErrorSource.EXTERNAL && e.getErrorDefinition().getErrorType() == WErrorType.UNEXPECTED_ERROR && e.getErrorDefinition().getErrorSource() == WErrorSource.INTERNAL - && isNotFoundProvidersDisputesApi(providerData); + && e.getErrorDefinition().getErrorReason() != null + && e.getErrorDefinition().getErrorReason().contains("Unexpected result, code = "); + } + + public boolean isProvidersDisputesApiNotExist(ProviderData providerData, WRuntimeException e) { + return e.getErrorDefinition() != null + && e.getErrorDefinition().getGenerationSource() == WErrorSource.EXTERNAL + && e.getErrorDefinition().getErrorType() == WErrorType.UNEXPECTED_ERROR + && e.getErrorDefinition().getErrorSource() == WErrorSource.INTERNAL + && isProvidersDisputesApiNotFound(providerData); } @SneakyThrows - private Boolean isNotFoundProvidersDisputesApi(ProviderData providerData) { + private Boolean isProvidersDisputesApiNotFound(ProviderData providerData) { return httpClient.execute(new HttpGet(getRouteUrl(providerData)), isNotFoundResponse()); } private String getRouteUrl(ProviderData providerData) { - var routeUrl = providerRouting.getRouteUrl(providerData); - log.debug("Check adapter connection, routeUrl={}", routeUrl); - return routeUrl; + providerRouting.initRouteUrl(providerData); + log.debug("Check adapter connection, routeUrl={}", providerData.getRouteUrl()); + return providerData.getRouteUrl(); } private HttpClientResponseHandler isNotFoundResponse() { diff --git a/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java b/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java index c15b0e9..6ed4c7c 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java @@ -1,14 +1,13 @@ package dev.vality.disputes.schedule.service; -import dev.vality.disputes.constant.ErrorReason; import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.dao.ProviderDisputeDao; import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.Dispute; -import dev.vality.disputes.manualparsing.ManualParsingTopic; import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; import dev.vality.disputes.polling.PollingInfoService; import dev.vality.disputes.provider.DisputeStatusResult; +import dev.vality.disputes.schedule.catcher.WRuntimeExceptionCatcher; import dev.vality.disputes.schedule.client.RemoteClient; import dev.vality.disputes.schedule.handler.DisputeStatusResultHandler; import lombok.RequiredArgsConstructor; @@ -24,7 +23,7 @@ import java.util.Map; @Slf4j @Service @RequiredArgsConstructor -@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) +@SuppressWarnings({"MemberName", "ParameterName", "LineLength", "MissingSwitchDefault"}) public class PendingDisputesService { private final RemoteClient remoteClient; @@ -34,7 +33,7 @@ public class PendingDisputesService { private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; private final ProviderDataService providerDataService; private final DisputeStatusResultHandler disputeStatusResultHandler; - private final ManualParsingTopic manualParsingTopic; + private final WRuntimeExceptionCatcher wRuntimeExceptionCatcher; @Transactional(propagation = Propagation.REQUIRED) public List getPendingDisputesForUpdateSkipLocked(int batchSize) { @@ -65,15 +64,16 @@ public class PendingDisputesService { return; } if (pollingInfoService.isDeadline(dispute)) { - manualParsingTopic.sendPoolingExpired(dispute); - log.error("Trying to set manual_pending Dispute status with POOLING_EXPIRED error reason {}", dispute.getId()); - disputeDao.update(dispute.getId(), DisputeStatus.manual_pending, ErrorReason.POOLING_EXPIRED); - log.debug("Dispute status has been set to manual_pending {}", dispute.getId()); + disputeStatusResultHandler.handlePoolingExpired(dispute); return; } log.debug("ProviderDispute has been found {}", dispute.getId()); - var result = remoteClient.checkDisputeStatus(dispute, providerDispute, providerData); - finishTask(dispute, result, providerData.getOptions()); + wRuntimeExceptionCatcher.catchUnexpectedResultMapping( + () -> { + var result = remoteClient.checkDisputeStatus(dispute, providerDispute, providerData); + finishTask(dispute, result, providerData.getOptions()); + }, + e -> disputeStatusResultHandler.handleUnexpectedResultMapping(dispute, e)); } @Transactional(propagation = Propagation.REQUIRED) diff --git a/src/main/java/dev/vality/disputes/schedule/service/ProviderDataService.java b/src/main/java/dev/vality/disputes/schedule/service/ProviderDataService.java index ad74176..3464948 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ProviderDataService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ProviderDataService.java @@ -38,7 +38,7 @@ public class ProviderDataService { var terminal = dominantAsyncService.getTerminal(payment.getRoute().getTerminal()); var proxy = dominantAsyncService.getProxy(provider.get().getProxy().getRef()); return ProviderData.builder() - .options(OptionsExtractors.mergeOptions(provider.get(), proxy.get(), terminal.get())) + .options(OptionsExtractors.mergeOptions(provider.get(), proxy.get(), terminal.get())) .defaultProviderUrl(proxy.get().getUrl()) .build(); } diff --git a/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java b/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java index c92cfb8..2b59b84 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java @@ -2,7 +2,6 @@ package dev.vality.disputes.schedule.service; import dev.vality.disputes.config.properties.AdaptersConnectionProperties; import dev.vality.disputes.provider.ProviderDisputesServiceSrv; -import dev.vality.disputes.schedule.model.ProviderData; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,12 +17,10 @@ import java.util.concurrent.TimeUnit; @SuppressWarnings({"AbbreviationAsWordInName", "LineLength"}) public class ProviderIfaceBuilder { - private final ProviderRouting providerRouting; private final AdaptersConnectionProperties adaptersConnectionProperties; - @Cacheable(value = "adapters", key = "#providerData.defaultProviderUrl", cacheManager = "adaptersCacheManager") - public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(ProviderData providerData) { - var routeUrl = providerRouting.getRouteUrl(providerData); + @Cacheable(value = "adapters", key = "#root.args[0]", cacheManager = "adaptersCacheManager") + public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(String routeUrl) { log.info("Creating new client for url: {}", routeUrl); return new THSpawnClientBuilder() .withNetworkTimeout((int) TimeUnit.SECONDS.toMillis(adaptersConnectionProperties.getTimeoutSec())) diff --git a/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java b/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java index 6627094..82a5288 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java @@ -18,12 +18,12 @@ public class ProviderRouting { private static final String DISPUTES_URL_POSTFIX_DEFAULT = "disputes"; private static final String OPTION_DISPUTES_URL_FIELD_NAME = "disputes_url"; - public String getRouteUrl(ProviderData providerData) { + public void initRouteUrl(ProviderData providerData) { var url = providerData.getOptions().get(OPTION_DISPUTES_URL_FIELD_NAME); if (ObjectUtils.isEmpty(url)) { url = createDefaultRouteUrl(providerData.getDefaultProviderUrl()); } - return url; + providerData.setRouteUrl(url); } private String createDefaultRouteUrl(String defaultProviderUrl) { diff --git a/src/main/java/dev/vality/disputes/service/external/DisputesTgBotService.java b/src/main/java/dev/vality/disputes/service/external/DisputesTgBotService.java new file mode 100644 index 0000000..2019d77 --- /dev/null +++ b/src/main/java/dev/vality/disputes/service/external/DisputesTgBotService.java @@ -0,0 +1,24 @@ +package dev.vality.disputes.service.external; + +import dev.vality.disputes.admin.DisputeAlreadyCreated; +import dev.vality.disputes.admin.DisputeFailedReviewRequired; +import dev.vality.disputes.admin.DisputePoolingExpired; +import dev.vality.disputes.admin.DisputeReadyForCreateAdjustment; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.provider.DisputeParams; + +import java.util.List; + +public interface DisputesTgBotService { + + DisputeCreatedResult createDispute(DisputeParams disputeParams); + + void sendDisputeAlreadyCreated(DisputeAlreadyCreated disputeAlreadyCreated); + + void sendDisputePoolingExpired(DisputePoolingExpired disputePoolingExpired); + + void sendDisputeReadyForCreateAdjustment(List disputeReadyForCreateAdjustments); + + void sendDisputeFailedReviewRequired(DisputeFailedReviewRequired disputeFailedReviewRequired); + +} diff --git a/src/main/java/dev/vality/disputes/service/external/impl/DisputesTgBotServiceImpl.java b/src/main/java/dev/vality/disputes/service/external/impl/DisputesTgBotServiceImpl.java new file mode 100644 index 0000000..d4c4bd7 --- /dev/null +++ b/src/main/java/dev/vality/disputes/service/external/impl/DisputesTgBotServiceImpl.java @@ -0,0 +1,75 @@ +package dev.vality.disputes.service.external.impl; + +import dev.vality.disputes.admin.*; +import dev.vality.disputes.provider.DisputeCreatedResult; +import dev.vality.disputes.provider.DisputeParams; +import dev.vality.disputes.provider.ProviderDisputesServiceSrv; +import dev.vality.disputes.service.external.DisputesTgBotService; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@SuppressWarnings({"LineLength"}) +public class DisputesTgBotServiceImpl implements DisputesTgBotService { + + public final ProviderDisputesServiceSrv.Iface providerDisputesTgBotClient; + public final AdminCallbackServiceSrv.Iface adminCallbackDisputesTgBotClient; + + @Override + @SneakyThrows + public DisputeCreatedResult createDispute(DisputeParams disputeParams) { + log.debug("Trying to call providerDisputesTgBotClient.createDispute() {} {}", disputeParams.getDisputeId(), disputeParams.getTransactionContext().getInvoiceId()); + var invoice = providerDisputesTgBotClient.createDispute(disputeParams); + log.debug("providerDisputesTgBotClient.createDispute() has been called {} {}", disputeParams.getDisputeId(), disputeParams.getTransactionContext().getInvoiceId()); + return invoice; + } + + @Override + @SneakyThrows + public void sendDisputeAlreadyCreated(DisputeAlreadyCreated disputeAlreadyCreated) { + log.debug("Trying to call adminCallbackDisputesTgBotClient.sendDisputeAlreadyCreated() {}", disputeAlreadyCreated.getId()); + adminCallbackDisputesTgBotClient.notify( + new NotificationParamsRequest(List.of(Notification.disputeAlreadyCreated(disputeAlreadyCreated)))); + log.debug("adminCallbackDisputesTgBotClient.sendDisputeAlreadyCreated() has been called {}", disputeAlreadyCreated.getId()); + } + + @Override + @SneakyThrows + public void sendDisputePoolingExpired(DisputePoolingExpired disputePoolingExpired) { + log.debug("Trying to call adminCallbackDisputesTgBotClient.sendDisputePoolingExpired() {}", disputePoolingExpired.getId()); + adminCallbackDisputesTgBotClient.notify( + new NotificationParamsRequest(List.of(Notification.disputePoolingExpired(disputePoolingExpired)))); + log.debug("adminCallbackDisputesTgBotClient.sendDisputePoolingExpired() has been called {}", disputePoolingExpired.getId()); + } + + @Override + @SneakyThrows + public void sendDisputeReadyForCreateAdjustment(List disputeReadyForCreateAdjustments) { + var ids = disputeReadyForCreateAdjustments.stream() + .map(DisputeReadyForCreateAdjustment::getId) + .map(String::valueOf) + .collect(Collectors.joining(", ")); + log.debug("Trying to call adminCallbackDisputesTgBotClient.sendDisputeReadyForCreateAdjustment() {}", ids); + var notifications = disputeReadyForCreateAdjustments.stream() + .map(Notification::disputeReadyForCreateAdjustment) + .collect(Collectors.toList()); + adminCallbackDisputesTgBotClient.notify(new NotificationParamsRequest(notifications)); + log.debug("adminCallbackDisputesTgBotClient.sendDisputeReadyForCreateAdjustment() has been called {}", ids); + } + + @Override + @SneakyThrows + public void sendDisputeFailedReviewRequired(DisputeFailedReviewRequired disputeFailedReviewRequired) { + log.debug("Trying to call adminCallbackDisputesTgBotClient.sendDisputeFailedReviewRequired() {}", disputeFailedReviewRequired.getId()); + adminCallbackDisputesTgBotClient.notify( + new NotificationParamsRequest(List.of(Notification.disputeFailedReviewRequired(disputeFailedReviewRequired)))); + log.debug("adminCallbackDisputesTgBotClient.sendDisputeFailedReviewRequired() has been called {}", disputeFailedReviewRequired.getId()); + } +} diff --git a/src/main/java/dev/vality/disputes/servlet/ManualParsingServlet.java b/src/main/java/dev/vality/disputes/servlet/AdminManagementServlet.java similarity index 66% rename from src/main/java/dev/vality/disputes/servlet/ManualParsingServlet.java rename to src/main/java/dev/vality/disputes/servlet/AdminManagementServlet.java index 502770c..352cdd9 100644 --- a/src/main/java/dev/vality/disputes/servlet/ManualParsingServlet.java +++ b/src/main/java/dev/vality/disputes/servlet/AdminManagementServlet.java @@ -1,6 +1,6 @@ package dev.vality.disputes.servlet; -import dev.vality.disputes.admin.ManualParsingServiceSrv; +import dev.vality.disputes.admin.AdminManagementServiceSrv; import dev.vality.woody.thrift.impl.http.THServiceBuilder; import jakarta.servlet.*; import jakarta.servlet.annotation.WebServlet; @@ -8,11 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; -@WebServlet("/disputes-api/v1/manual-parsing") -public class ManualParsingServlet extends GenericServlet { +@WebServlet("/disputes-api/v1/admin-management") +public class AdminManagementServlet extends GenericServlet { @Autowired - private ManualParsingServiceSrv.Iface manualParsingHandler; + private AdminManagementServiceSrv.Iface adminManagementHandler; private Servlet servlet; @@ -20,7 +20,7 @@ public class ManualParsingServlet extends GenericServlet { public void init(ServletConfig config) throws ServletException { super.init(config); servlet = new THServiceBuilder() - .build(ManualParsingServiceSrv.Iface.class, manualParsingHandler); + .build(AdminManagementServiceSrv.Iface.class, adminManagementHandler); } @Override diff --git a/src/main/java/dev/vality/disputes/utils/ErrorFormatter.java b/src/main/java/dev/vality/disputes/utils/ErrorFormatter.java index 878734a..7096664 100644 --- a/src/main/java/dev/vality/disputes/utils/ErrorFormatter.java +++ b/src/main/java/dev/vality/disputes/utils/ErrorFormatter.java @@ -12,4 +12,11 @@ public class ErrorFormatter { } return TErrorUtil.toStringVal(failure); } + + public static String getErrorMessage(String errorCode, String errorDescription) { + if (!StringUtils.isBlank(errorDescription)) { + return errorCode + ": " + errorDescription; + } + return errorCode; + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7557c87..e61cc7e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,6 +64,17 @@ service: shops: poolSize: 10 ttlSec: 86400 + disputes-tg-bot: + provider: + url: http://localhost:8022/change_it + networkTimeout: 5000 + enabled: false + admin: + url: http://localhost:8022/change_it + networkTimeout: 5000 + enabled: false + mdc-topic-producer: + enabled: true adapters: connection: timeoutSec: 30 @@ -106,18 +117,15 @@ dispute: isScheduleCreatedEnabled: true isSchedulePendingEnabled: true isScheduleCreateAdjustmentsEnabled: true - isScheduleReadyForCreateAdjustmentsEnabled: true + isScheduleReadyForCreateAdjustmentsEnabled: false time: config: max-time-polling-min: 600 -manual-parsing-topic: - enabled: true - testcontainers: postgresql: - tag: '11.4' + tag: '14.12' http-client: requestTimeout: 60000 @@ -125,3 +133,7 @@ http-client: connectionTimeout: 10000 maxTotalPooling: 200 defaultMaxPerRoute: 200 + +otel: + resource: http://localhost:4318/v1/traces + timeout: 60000 diff --git a/src/main/resources/db/migration/V3__add_mapping.sql b/src/main/resources/db/migration/V3__add_mapping.sql new file mode 100644 index 0000000..f5cd381 --- /dev/null +++ b/src/main/resources/db/migration/V3__add_mapping.sql @@ -0,0 +1,2 @@ +ALTER TABLE dspt.dispute + ADD COLUMN "mapping" CHARACTER VARYING; diff --git a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementController.java similarity index 83% rename from src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java rename to src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementController.java index a0f8200..355be84 100644 --- a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java +++ b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementController.java @@ -1,4 +1,4 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -16,10 +16,8 @@ import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.ArrayList; @@ -28,43 +26,49 @@ import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping({"/debug/disputes-api/manual-parsing"}) +@RequestMapping({"/debug/disputes-api/admin-management"}) @Slf4j -public class DebugManualParsingController { +public class DebugAdminManagementController { - private final ManualParsingServiceSrv.Iface manualParsingHandler; + private final AdminManagementServiceSrv.Iface adminManagementHandler; private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jdk8Module()); @PostMapping("/cancel") @SneakyThrows public void cancelPending(@RequestBody String body) { log.debug("cancelPending {}", body); - manualParsingHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class)); + adminManagementHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class)); } @PostMapping("/approve") @SneakyThrows public void approvePending(@RequestBody String body) { log.debug("approvePending {}", body); - manualParsingHandler.approvePending(objectMapper.readValue(body, ApproveParamsRequest.class)); + adminManagementHandler.approvePending(objectMapper.readValue(body, ApproveParamsRequest.class)); } @PostMapping("/bind") @SneakyThrows public void bindCreated(@RequestBody String body) { log.debug("bindCreated {}", body); - manualParsingHandler.bindCreated(objectMapper.readValue(body, BindParamsRequest.class)); + adminManagementHandler.bindCreated(objectMapper.readValue(body, BindParamsRequest.class)); } @PostMapping("/get") @SneakyThrows public DisputeResult getDisputes(@RequestBody String body) { log.debug("getDispute {}", body); - var dispute = manualParsingHandler.getDisputes(objectMapper.readValue(body, DisputeParamsRequest.class)); + var dispute = adminManagementHandler.getDisputes(objectMapper.readValue(body, DisputeParamsRequest.class)); return objectMapper.convertValue(dispute, new TypeReference<>() { }); } + @GetMapping("/disputes") + @ResponseStatus(HttpStatus.NOT_FOUND) + public void defaultRouteUrl() { + log.info("hi"); + } + @Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementControllerTest.java similarity index 79% rename from src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java rename to src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementControllerTest.java index 0489b6e..40c9ad2 100644 --- a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingControllerTest.java +++ b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementControllerTest.java @@ -1,9 +1,9 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; +import dev.vality.disputes.admin.AdminManagementServiceSrv; import dev.vality.disputes.admin.Attachment; import dev.vality.disputes.admin.Dispute; import dev.vality.disputes.admin.DisputeResult; -import dev.vality.disputes.admin.ManualParsingServiceSrv; import dev.vality.disputes.config.SpringBootUTest; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; @@ -19,17 +19,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @SpringBootUTest -public class DebugManualParsingControllerTest { +public class DebugAdminManagementControllerTest { @MockBean - private ManualParsingServiceSrv.Iface manualParsingHandler; + private AdminManagementServiceSrv.Iface adminManagementHandler; @Autowired - private DebugManualParsingController debugManualParsingController; + private DebugAdminManagementController debugAdminManagementController; @Test @SneakyThrows public void checkSerialization() { - debugManualParsingController.approvePending(""" + debugAdminManagementController.approvePending(""" { "approveParams": [ { @@ -39,7 +39,7 @@ public class DebugManualParsingControllerTest { ] } """); - debugManualParsingController.cancelPending(""" + debugAdminManagementController.cancelPending(""" { "cancelParams": [ { @@ -49,7 +49,7 @@ public class DebugManualParsingControllerTest { ] } """); - debugManualParsingController.cancelPending(""" + debugAdminManagementController.cancelPending(""" { "cancelParams": [ { @@ -59,7 +59,7 @@ public class DebugManualParsingControllerTest { ] } """); - debugManualParsingController.bindCreated(""" + debugAdminManagementController.bindCreated(""" { "bindParams": [ { @@ -77,9 +77,9 @@ public class DebugManualParsingControllerTest { randomed.setDisputes(List.of( randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(b))), randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(a))))); - given(manualParsingHandler.getDisputes(any())) + given(adminManagementHandler.getDisputes(any())) .willReturn(randomed); - var disputes = debugManualParsingController.getDisputes(""" + var disputes = debugAdminManagementController.getDisputes(""" { "disputeParams": [ { diff --git a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementHandlerTest.java similarity index 78% rename from src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java rename to src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementHandlerTest.java index b76358d..bbabae1 100644 --- a/src/test/java/dev/vality/disputes/manualparsing/DebugManualParsingHandlerTest.java +++ b/src/test/java/dev/vality/disputes/admin/management/DebugAdminManagementHandlerTest.java @@ -1,4 +1,4 @@ -package dev.vality.disputes.manualparsing; +package dev.vality.disputes.admin.management; import dev.vality.disputes.config.WireMockSpringBootITest; import dev.vality.disputes.dao.DisputeDao; @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @WireMockSpringBootITest @Import({PendingDisputesTestService.class}) -public class DebugManualParsingHandlerTest { +public class DebugAdminManagementHandlerTest { @Autowired private DisputeDao disputeDao; @@ -31,19 +31,19 @@ public class DebugManualParsingHandlerTest { @Autowired private PendingDisputesTestService pendingDisputesTestService; @Autowired - private DebugManualParsingController debugManualParsingController; + private DebugAdminManagementController debugAdminManagementController; @Test public void testCancelCreateAdjustment() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); - debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); + debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString())); assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus()); } @Test public void testCancelPending() { var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); - debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); + debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString())); assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus()); } @@ -51,14 +51,14 @@ public class DebugManualParsingHandlerTest { public void testCancelFailed() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); disputeDao.update(disputeId, DisputeStatus.failed); - debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); + debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString())); assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); } @Test public void testApproveCreateAdjustmentWithCallHg() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); - debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), false)); + debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), false)); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -66,7 +66,7 @@ public class DebugManualParsingHandlerTest { @Test public void testApproveCreateAdjustmentWithSkipHg() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); - debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), true)); + debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), true)); assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -74,7 +74,7 @@ public class DebugManualParsingHandlerTest { @Test public void testApprovePendingWithSkipHg() { var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); - debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), true)); + debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), true)); assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -83,7 +83,7 @@ public class DebugManualParsingHandlerTest { public void testApproveFailed() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); disputeDao.update(disputeId, DisputeStatus.failed); - debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), true)); + debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), true)); assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); } @@ -91,7 +91,7 @@ public class DebugManualParsingHandlerTest { public void testBindCreatedCreateAdjustment() { var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var providerDisputeId = generateId(); - debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); + debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -100,7 +100,7 @@ public class DebugManualParsingHandlerTest { public void testBindCreatedPending() { var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); var providerDisputeId = generateId(); - debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); + debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -112,7 +112,7 @@ public class DebugManualParsingHandlerTest { var providerDisputeId = generateId(); var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.manual_created); - debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); assertEquals(DisputeStatus.manual_pending, disputeDao.get(UUID.fromString(disputeId)).get().getStatus()); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed); } @@ -124,7 +124,7 @@ public class DebugManualParsingHandlerTest { var providerDisputeId = generateId(); var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.already_exist_created); - debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); + debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId, providerDisputeId)); assertEquals(DisputeStatus.pending, disputeDao.get(UUID.fromString(disputeId)).get().getStatus()); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed); } @@ -134,7 +134,7 @@ public class DebugManualParsingHandlerTest { public void testGetDispute() { WiremockUtils.mockS3AttachmentDownload(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); - var disputes = debugManualParsingController.getDisputes(getGetDisputeRequest(disputeId.toString(), true)); + var disputes = debugAdminManagementController.getDisputes(getGetDisputeRequest(disputeId.toString(), true)); assertEquals(1, disputes.getDisputes().size()); disputeDao.update(disputeId, DisputeStatus.failed); } diff --git a/src/test/java/dev/vality/disputes/api/ServletTest.java b/src/test/java/dev/vality/disputes/api/ServletTest.java index 0793b60..ca38b64 100644 --- a/src/test/java/dev/vality/disputes/api/ServletTest.java +++ b/src/test/java/dev/vality/disputes/api/ServletTest.java @@ -1,8 +1,8 @@ package dev.vality.disputes.api; import dev.vality.damsel.payment_processing.InvoicingSrv; +import dev.vality.disputes.admin.AdminManagementServiceSrv; import dev.vality.disputes.admin.CancelParamsRequest; -import dev.vality.disputes.admin.ManualParsingServiceSrv; import dev.vality.disputes.callback.DisputeCallbackParams; import dev.vality.disputes.callback.ProviderDisputesCallbackServiceSrv; import dev.vality.disputes.config.WireMockSpringBootITest; @@ -53,11 +53,11 @@ public class ServletTest { @Test @SneakyThrows - public void manualServletTest() { + public void adminManagementServletTest() { var iface = new THSpawnClientBuilder() - .withAddress(new URI("http://127.0.0.1:" + serverPort + MANUAL)) + .withAddress(new URI("http://127.0.0.1:" + serverPort + ADMIN_MANAGEMENT)) .withNetworkTimeout(5000) - .build(ManualParsingServiceSrv.Iface.class); + .build(AdminManagementServiceSrv.Iface.class); var request = DamselUtil.fillRequiredTBaseObject( new CancelParamsRequest(), CancelParamsRequest.class diff --git a/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java index f68db3b..389598c 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java +++ b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java @@ -17,12 +17,15 @@ import dev.vality.file.storage.FileStorageSrv; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; import java.util.UUID; +import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING; import static dev.vality.disputes.util.MockUtil.*; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -49,6 +52,8 @@ public class CreatedDisputesServiceTest { private WiremockAddressesHolder wiremockAddressesHolder; @Autowired private CreatedDisputesTestService createdDisputesTestService; + @LocalServerPort + private int serverPort; @Test @SneakyThrows @@ -92,7 +97,7 @@ public class CreatedDisputesServiceTest { @Test @SneakyThrows - public void testManualCreatedWhenIsNotProvidersDisputesApiExist() { + public void testManualPendingWhenIsNotProvidersDisputesApiExist() { var invoiceId = "20McecNnWoy"; var paymentId = "1"; var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId()); @@ -105,7 +110,7 @@ public class CreatedDisputesServiceTest { when(dominantService.getProxy(any())).thenReturn(createProxy().get()); var dispute = disputeDao.get(disputeId); createdDisputesService.callCreateDisputeRemotely(dispute.get()); - assertEquals(DisputeStatus.manual_created, disputeDao.get(disputeId).get().getStatus()); + assertEquals(DisputeStatus.manual_pending, disputeDao.get(disputeId).get().getStatus()); disputeDao.update(disputeId, DisputeStatus.failed); } @@ -138,6 +143,83 @@ public class CreatedDisputesServiceTest { assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); } + @Test + @SneakyThrows + public void testManualCreatedWhenDisputeCreatedFailResultWithDisputesUnknownMapping() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId()); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + when(dominantService.getProxy(any())).thenReturn(createProxy().get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + var disputeCreatedFailResult = createDisputeCreatedFailResult(); + disputeCreatedFailResult.getFailResult().getFailure().setCode(DISPUTES_UNKNOWN_MAPPING); + when(providerMock.createDispute(any())).thenReturn(disputeCreatedFailResult); + when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock); + var dispute = disputeDao.get(disputeId); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_created, disputeDao.get(disputeId).get().getStatus()); + assertTrue(disputeDao.get(disputeId).get().getErrorMessage().contains(DISPUTES_UNKNOWN_MAPPING)); + disputeDao.update(disputeId, DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testManualCreatedWhenUnexpectedResultMapping() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId()); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + // routeUrl = "http://127.0.0.1:8023/disputes" == exist api + when(dominantService.getProxy(any())).thenReturn(createProxyWithRealAddress(serverPort).get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.createDispute(any())).thenThrow(getUnexpectedResultWException()); + when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock); + var dispute = disputeDao.get(disputeId); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_created, disputeDao.get(disputeId).get().getStatus()); + assertTrue(disputeDao.get(disputeId).get().getErrorMessage().contains("Unexpected result")); + disputeDao.update(disputeId, DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testManualPendingWhenUnexpectedResult() { + var invoiceId = "20McecNnWoy"; + var paymentId = "1"; + var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId()); + var invoicePayment = MockUtil.createInvoicePayment(paymentId); + invoicePayment.getPayment().setStatus(InvoicePaymentStatus.captured(new InvoicePaymentCaptured())); + when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); + when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl()); + var terminal = createTerminal().get(); + terminal.getOptions().putAll(getOptions()); + when(dominantService.getTerminal(any())).thenReturn(terminal); + when(dominantService.getProvider(any())).thenReturn(createProvider().get()); + when(dominantService.getProxy(any())).thenReturn(createProxyNotFoundCase(serverPort).get()); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.createDispute(any())).thenThrow(getUnexpectedResultWException()); + when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock); + var dispute = disputeDao.get(disputeId); + createdDisputesService.callCreateDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_pending, disputeDao.get(disputeId).get().getStatus()); + disputeDao.update(disputeId, DisputeStatus.failed); + } + @Test @SneakyThrows public void testDisputeCreatedAlreadyExistResult() { diff --git a/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java index f6dace7..77e80f2 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java +++ b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java @@ -15,8 +15,10 @@ import org.springframework.context.annotation.Import; import java.util.UUID; +import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING; import static dev.vality.disputes.util.MockUtil.*; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -77,6 +79,37 @@ public class PendingDisputesServiceTest { assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); } + @Test + @SneakyThrows + public void testManualPendingWhenStatusFailResultWithDisputesUnknownMapping() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + var disputeStatusFailResult = createDisputeStatusFailResult(); + disputeStatusFailResult.getStatusFail().getFailure().setCode(DISPUTES_UNKNOWN_MAPPING); + when(providerMock.checkDisputeStatus(any())).thenReturn(disputeStatusFailResult); + when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock); + var dispute = disputeDao.get(disputeId); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_pending, disputeDao.get(disputeId).get().getStatus()); + assertTrue(disputeDao.get(disputeId).get().getErrorMessage().contains(DISPUTES_UNKNOWN_MAPPING)); + disputeDao.update(disputeId, DisputeStatus.failed); + } + + @Test + @SneakyThrows + public void testManualPendingWhenUnexpectedResultMapping() { + var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); + var providerMock = mock(ProviderDisputesServiceSrv.Client.class); + when(providerMock.checkDisputeStatus(any())).thenThrow(getUnexpectedResultWException()); + when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock); + var dispute = disputeDao.get(disputeId); + pendingDisputesService.callPendingDisputeRemotely(dispute.get()); + assertEquals(DisputeStatus.manual_pending, disputeDao.get(disputeId).get().getStatus()); + assertTrue(disputeDao.get(disputeId).get().getErrorMessage().contains("Unexpected result")); + disputeDao.update(disputeId, DisputeStatus.failed); + } + + @Test @SneakyThrows public void testDisputeStatusPendingResult() { diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/CallbackNotifierTestConfig.java b/src/test/java/dev/vality/disputes/schedule/service/config/CallbackNotifierTestConfig.java new file mode 100644 index 0000000..45d6b36 --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/CallbackNotifierTestConfig.java @@ -0,0 +1,13 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.disputes.admin.AdminCallbackServiceSrv; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +@TestConfiguration +public class CallbackNotifierTestConfig { + + @MockBean + private AdminCallbackServiceSrv.Iface adminCallbackDisputesTgBotClient; + +} diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java b/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java index f1bb00a..45d9f2a 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java +++ b/src/test/java/dev/vality/disputes/schedule/service/config/CreatedDisputesTestService.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @TestComponent -@Import({DisputeApiTestService.class, RemoteClientTestConfig.class}) +@Import({DisputeApiTestService.class, RemoteClientTestConfig.class, DefaultRemoteClientTestConfig.class, CallbackNotifierTestConfig.class}) @SuppressWarnings({"ParameterName", "LineLength"}) public class CreatedDisputesTestService { diff --git a/src/test/java/dev/vality/disputes/schedule/service/config/DefaultRemoteClientTestConfig.java b/src/test/java/dev/vality/disputes/schedule/service/config/DefaultRemoteClientTestConfig.java new file mode 100644 index 0000000..9dde8bc --- /dev/null +++ b/src/test/java/dev/vality/disputes/schedule/service/config/DefaultRemoteClientTestConfig.java @@ -0,0 +1,13 @@ +package dev.vality.disputes.schedule.service.config; + +import dev.vality.disputes.provider.ProviderDisputesServiceSrv; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +@TestConfiguration +public class DefaultRemoteClientTestConfig { + + @MockBean + private ProviderDisputesServiceSrv.Iface providerDisputesTgBotClient; + +} diff --git a/src/test/java/dev/vality/disputes/util/MockUtil.java b/src/test/java/dev/vality/disputes/util/MockUtil.java index abf4967..652ce86 100644 --- a/src/test/java/dev/vality/disputes/util/MockUtil.java +++ b/src/test/java/dev/vality/disputes/util/MockUtil.java @@ -14,6 +14,10 @@ import dev.vality.file.storage.NewFileResult; import dev.vality.geck.common.util.TypeUtil; import dev.vality.token.keeper.AuthData; import dev.vality.token.keeper.AuthDataStatus; +import dev.vality.woody.api.flow.error.WErrorDefinition; +import dev.vality.woody.api.flow.error.WErrorSource; +import dev.vality.woody.api.flow.error.WErrorType; +import dev.vality.woody.api.flow.error.WRuntimeException; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.apache.thrift.TSerializer; @@ -81,8 +85,16 @@ public class MockUtil { .setProxy(new Proxy().setRef(new ProxyRef().setId(1)))); } + public static CompletableFuture createProxyNotFoundCase(Integer port) { + return createProxy("http://127.0.0.1:" + port + "/debug/disputes-api/admin-management"); + } + + public static CompletableFuture createProxyWithRealAddress(Integer port) { + return createProxy("http://127.0.0.1:" + port); + } + public static CompletableFuture createProxy() { - return createProxy("http://ya.ru"); + return createProxy("http://127.0.0.1:8023"); } public static CompletableFuture createProxy(String url) { @@ -173,4 +185,13 @@ public class MockUtil { failure.setSub(new SubFailure("some_suberror")); return failure; } + + public static WRuntimeException getUnexpectedResultWException() { + var errorDefinition = new WErrorDefinition(WErrorSource.EXTERNAL); + errorDefinition.setErrorReason("Unexpected result, code = resp_status_error, description = " + + "Tek seferde en fazla 4,000.00 işem yapılabilir."); + errorDefinition.setErrorType(WErrorType.UNEXPECTED_ERROR); + errorDefinition.setErrorSource(WErrorSource.INTERNAL); + return new WRuntimeException(errorDefinition); + } }