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
This commit is contained in:
Anatolii Karlov 2024-10-29 17:49:03 +07:00 committed by GitHub
parent 0f39485afc
commit df25417b6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 982 additions and 213 deletions

View File

@ -48,7 +48,7 @@
Если при финальном статусе платежа `captured` создавать на провайдере диспут является не желательной ситуацией, можно Если при финальном статусе платежа `captured` создавать на провайдере диспут является не желательной ситуацией, можно
установить опцию в терминале `DISPUTE_FLOW_CAPTURED_BLOCKED` и пулять установить опцию в терминале `DISPUTE_FLOW_CAPTURED_BLOCKED` и пулять
состояние состояние
в топик\тг-провайдер-бот\filebeat на ручной разбор (`ManualParsing` module) в топик\тг-провайдер-бот\filebeat на ручной разбор (`AdminManagement` module)
Не все провайдеры на данный момент поддерживают работу с диспутами по `API`. Не все провайдеры на данный момент поддерживают работу с диспутами по `API`.
Предполагается такой способ действия при этой ситуации: Предполагается такой способ действия при этой ситуации:
@ -111,7 +111,7 @@
- если это captured платеж и выставлена опция `DISPUTE_FLOW_CAPTURED_BLOCKED` , то тоже отправляет на ручной разбор - если это captured платеж и выставлена опция `DISPUTE_FLOW_CAPTURED_BLOCKED` , то тоже отправляет на ручной разбор
Далее, через внутрений трифт-интерфейс саппорт получает способ манипулировать диспутом для его Далее, через внутрений трифт-интерфейс саппорт получает способ манипулировать диспутом для его
обработки (`ManualParsingDisputesService`) обработки (`AdminManagementDisputesService`)
- Перед переводом диспута в финальный статус саппорт должен будет забиндить айди созданного диспута в провайдере через - Перед переводом диспута в финальный статус саппорт должен будет забиндить айди созданного диспута в провайдере через
ручку `BindCreated()`. Здесь особенность, что этот метод фильтрует возможность биндить диспуты только созданные ручку `BindCreated()`. Здесь особенность, что этот метод фильтрует возможность биндить диспуты только созданные

20
pom.xml
View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>dev.vality</groupId> <groupId>dev.vality</groupId>
<artifactId>service-parent-pom</artifactId> <artifactId>service-parent-pom</artifactId>
<version>3.0.2</version> <version>3.0.4</version>
</parent> </parent>
<artifactId>disputes-api</artifactId> <artifactId>disputes-api</artifactId>
@ -47,7 +47,7 @@
<dependency> <dependency>
<groupId>dev.vality</groupId> <groupId>dev.vality</groupId>
<artifactId>disputes-proto</artifactId> <artifactId>disputes-proto</artifactId>
<version>1.23-37a5ad1</version> <version>1.26-fc8e34f</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>dev.vality</groupId> <groupId>dev.vality</groupId>
@ -62,6 +62,7 @@
<dependency> <dependency>
<groupId>dev.vality</groupId> <groupId>dev.vality</groupId>
<artifactId>damsel</artifactId> <artifactId>damsel</artifactId>
<version>1.648-ad715bd</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>dev.vality</groupId> <groupId>dev.vality</groupId>
@ -87,6 +88,16 @@
<artifactId>adapter-flow-lib</artifactId> <artifactId>adapter-flow-lib</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>dev.vality.woody</groupId>
<artifactId>woody-thrift</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>dev.vality.woody</groupId>
<artifactId>woody-api</artifactId>
<version>2.0.8</version>
</dependency>
<!--spring--> <!--spring-->
<dependency> <dependency>
@ -217,6 +228,11 @@
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>32.0.0-jre</version> <version>32.0.0-jre</version>
</dependency> </dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>1.29.0-alpha</version>
</dependency>
<!--test--> <!--test-->
<dependency> <dependency>

View File

@ -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<Dispute> disputes);
void sendDisputeFailedReviewRequired(Dispute dispute, String errorCode, String errorDescription);
}

View File

@ -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<Dispute> 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));
}
}

View File

@ -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<Dispute> 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());
}
}

View File

@ -1,4 +1,4 @@
package dev.vality.disputes.manualparsing; package dev.vality.disputes.admin.management;
import dev.vality.disputes.admin.*; import dev.vality.disputes.admin.*;
import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.dao.DisputeDao;
@ -31,7 +31,7 @@ import static dev.vality.disputes.api.service.ApiDisputesService.DISPUTE_PENDING
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) @SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"})
public class ManualParsingDisputesService { public class AdminManagementDisputesService {
private final DisputeDao disputeDao; private final DisputeDao disputeDao;
private final ProviderDisputeDao providerDisputeDao; private final ProviderDisputeDao providerDisputeDao;
@ -48,11 +48,12 @@ public class ManualParsingDisputesService {
return; return;
} }
var cancelReason = cancelParams.getCancelReason().orElse(null); var cancelReason = cancelParams.getCancelReason().orElse(null);
var mapping = cancelParams.getMapping().orElse(null);
log.debug("GetForUpdateSkipLocked has been found {}", dispute); log.debug("GetForUpdateSkipLocked has been found {}", dispute);
if (DISPUTE_PENDING.contains(dispute.getStatus())) { if (DISPUTE_PENDING.contains(dispute.getStatus())) {
// используется не failed, а cancelled чтоб можно было понять, что зафейлен по внешнему вызову // используется не failed, а cancelled чтоб можно было понять, что зафейлен по внешнему вызову
log.warn("Trying to set cancelled Dispute status {}, {}", dispute, cancelReason); log.warn("Trying to set cancelled Dispute status {}, {}, {}", dispute, mapping, cancelReason);
disputeDao.update(dispute.getId(), DisputeStatus.cancelled, cancelReason); disputeDao.update(dispute.getId(), DisputeStatus.cancelled, cancelReason, mapping);
log.debug("Dispute status has been set to cancelled {}", dispute); log.debug("Dispute status has been set to cancelled {}", dispute);
} else { } else {
log.info("Request was skipped by inappropriate status {}", dispute); log.info("Request was skipped by inappropriate status {}", dispute);
@ -133,6 +134,7 @@ public class ManualParsingDisputesService {
disputeResult.setProviderTrxId(dispute.getProviderTrxId()); disputeResult.setProviderTrxId(dispute.getProviderTrxId());
disputeResult.setStatus(dispute.getStatus().name()); disputeResult.setStatus(dispute.getStatus().name());
disputeResult.setErrorMessage(dispute.getErrorMessage()); disputeResult.setErrorMessage(dispute.getErrorMessage());
disputeResult.setMapping(dispute.getMapping());
disputeResult.setAmount(String.valueOf(dispute.getAmount())); disputeResult.setAmount(String.valueOf(dispute.getAmount()));
disputeResult.setChangedAmount(Optional.ofNullable(dispute.getChangedAmount()) disputeResult.setChangedAmount(Optional.ofNullable(dispute.getChangedAmount())
.map(String::valueOf) .map(String::valueOf)

View File

@ -1,4 +1,4 @@
package dev.vality.disputes.manualparsing; package dev.vality.disputes.admin.management;
import dev.vality.disputes.admin.*; import dev.vality.disputes.admin.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -12,28 +12,28 @@ import java.util.ArrayList;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@SuppressWarnings({"ParameterName", "LineLength"}) @SuppressWarnings({"ParameterName", "LineLength"})
public class ManualParsingHandler implements ManualParsingServiceSrv.Iface { public class AdminManagementHandler implements AdminManagementServiceSrv.Iface {
private final ManualParsingDisputesService manualParsingDisputesService; private final AdminManagementDisputesService adminManagementDisputesService;
@Override @Override
public void cancelPending(CancelParamsRequest cancelParamsRequest) throws TException { public void cancelPending(CancelParamsRequest cancelParamsRequest) throws TException {
for (var cancelParam : cancelParamsRequest.getCancelParams()) { for (var cancelParam : cancelParamsRequest.getCancelParams()) {
manualParsingDisputesService.cancelPendingDispute(cancelParam); adminManagementDisputesService.cancelPendingDispute(cancelParam);
} }
} }
@Override @Override
public void approvePending(ApproveParamsRequest approveParamsRequest) throws TException { public void approvePending(ApproveParamsRequest approveParamsRequest) throws TException {
for (var approveParam : approveParamsRequest.getApproveParams()) { for (var approveParam : approveParamsRequest.getApproveParams()) {
manualParsingDisputesService.approvePendingDispute(approveParam); adminManagementDisputesService.approvePendingDispute(approveParam);
} }
} }
@Override @Override
public void bindCreated(BindParamsRequest bindParamsRequest) throws TException { public void bindCreated(BindParamsRequest bindParamsRequest) throws TException {
for (var bindParam : bindParamsRequest.getBindParams()) { 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 { public DisputeResult getDisputes(DisputeParamsRequest disputeParamsRequest) throws TException {
var disputeResult = new DisputeResult(new ArrayList<>()); var disputeResult = new DisputeResult(new ArrayList<>());
for (var disputeParams : disputeParamsRequest.getDisputeParams()) { for (var disputeParams : disputeParamsRequest.getDisputeParams()) {
var dispute = manualParsingDisputesService.getDispute(disputeParams, disputeParamsRequest.isWithAttachments()); var dispute = adminManagementDisputesService.getDispute(disputeParams, disputeParamsRequest.isWithAttachments());
if (dispute != null) { if (dispute != null) {
disputeResult.getDisputes().add(dispute); disputeResult.getDisputes().add(dispute);
} }

View File

@ -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.enums.DisputeStatus;
import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.domain.tables.pojos.Dispute;
import dev.vality.disputes.provider.Attachment;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC; import org.slf4j.MDC;
@ -11,26 +10,28 @@ import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@SuppressWarnings({"ParameterName", "LineLength"}) @SuppressWarnings({"ParameterName", "LineLength"})
public class ManualParsingTopic { public class MdcTopicProducer {
@Value("${manual-parsing-topic.enabled}") @Value("${service.mdc-topic-producer.enabled}")
private boolean enabled; private boolean enabled;
public void sendCreated(Dispute dispute, List<Attachment> attachments, DisputeStatus disputeStatus) { public void sendCreated(Dispute dispute, DisputeStatus disputeStatus, String errorMessage) {
if (!enabled) { if (!enabled) {
return; return;
} }
var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap<String, String>() : MDC.getCopyOfContextMap(); var contextMap = getContextMap();
contextMap.put("dispute_id", dispute.getId().toString()); 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()); contextMap.put("dispute_status", disputeStatus.name());
if (errorMessage != null) {
contextMap.put("dispute_error_message", errorMessage);
}
MDC.setContextMap(contextMap); MDC.setContextMap(contextMap);
log.warn("Manual parsing case"); log.warn("Manual parsing case");
MDC.clear(); MDC.clear();
@ -40,7 +41,7 @@ public class ManualParsingTopic {
if (!enabled) { if (!enabled) {
return; return;
} }
var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap<String, String>() : MDC.getCopyOfContextMap(); var contextMap = getContextMap();
contextMap.put("dispute_id", dispute.getId().toString()); contextMap.put("dispute_id", dispute.getId().toString());
contextMap.put("dispute_status", DisputeStatus.manual_pending.name()); contextMap.put("dispute_status", DisputeStatus.manual_pending.name());
MDC.setContextMap(contextMap); MDC.setContextMap(contextMap);
@ -52,11 +53,15 @@ public class ManualParsingTopic {
if (!enabled || disputes.isEmpty()) { if (!enabled || disputes.isEmpty()) {
return; return;
} }
var contextMap = MDC.getCopyOfContextMap() == null ? new HashMap<String, String>() : MDC.getCopyOfContextMap(); var contextMap = getContextMap();
contextMap.put("dispute_ids", disputes.stream().map(Dispute::getId).map(String::valueOf).collect(Collectors.joining(", "))); contextMap.put("dispute_ids", disputes.stream().map(Dispute::getId).map(String::valueOf).collect(Collectors.joining(", ")));
contextMap.put("dispute_status", DisputeStatus.create_adjustment.name()); contextMap.put("dispute_status", DisputeStatus.create_adjustment.name());
MDC.setContextMap(contextMap); MDC.setContextMap(contextMap);
log.warn("Ready for CreateAdjustments case"); log.warn("Ready for CreateAdjustments case");
MDC.clear(); MDC.clear();
} }
private Map<String, String> getContextMap() {
return MDC.getCopyOfContextMap() == null ? new HashMap<>() : MDC.getCopyOfContextMap();
}
} }

View File

@ -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));
}
}

View File

@ -12,8 +12,8 @@ public class Status200ResponseConverter {
public Status200Response convert(Dispute dispute) { public Status200Response convert(Dispute dispute) {
var body = new Status200Response(); var body = new Status200Response();
body.setStatus(getStatus(dispute)); body.setStatus(getStatus(dispute));
if (!StringUtils.isBlank(dispute.getErrorMessage())) { if (!StringUtils.isBlank(dispute.getMapping())) {
body.setReason(new GeneralError(dispute.getErrorMessage())); body.setReason(new GeneralError(dispute.getMapping()));
} }
if (dispute.getChangedAmount() != null) { if (dispute.getChangedAmount() != null) {
body.setChangedAmount(dispute.getChangedAmount()); body.setChangedAmount(dispute.getChangedAmount());

View File

@ -5,6 +5,8 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.domain_config.RepositoryClientSrv; import dev.vality.damsel.domain_config.RepositoryClientSrv;
import dev.vality.damsel.payment_processing.InvoicingSrv; import dev.vality.damsel.payment_processing.InvoicingSrv;
import dev.vality.damsel.payment_processing.PartyManagementSrv; 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.file.storage.FileStorageSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv; import dev.vality.token.keeper.TokenAuthenticatorSrv;
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
@ -81,6 +83,26 @@ public class ApplicationConfig {
.build(PartyManagementSrv.Iface.class); .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 @Bean
public ExecutorService disputesThreadPool(@Value("${dispute.batchSize}") int threadPoolSize) { public ExecutorService disputesThreadPool(@Value("${dispute.batchSize}") int threadPoolSize) {
final var threadFactory = new ThreadFactoryBuilder() final var threadFactory = new ThreadFactoryBuilder()

View File

@ -24,7 +24,7 @@ public class NetworkConfig {
public static final String HEALTH = "/actuator/health"; public static final String HEALTH = "/actuator/health";
public static final String MERCHANT = "/disputes-api/v1/merchant"; 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"; public static final String CALLBACK = "/disputes-api/v1/callback";
@Bean @Bean
@ -39,7 +39,7 @@ public class NetworkConfig {
var enabledPaths = servletPath.startsWith(restEndpoint) var enabledPaths = servletPath.startsWith(restEndpoint)
|| servletPath.startsWith(HEALTH) || servletPath.startsWith(HEALTH)
|| servletPath.startsWith(MERCHANT) || servletPath.startsWith(MERCHANT)
|| servletPath.startsWith(MANUAL) || servletPath.startsWith(ADMIN_MANAGEMENT)
|| servletPath.startsWith(CALLBACK); || servletPath.startsWith(CALLBACK);
if ((request.getLocalPort() == restPort) && !enabledPaths) { if ((request.getLocalPort() == restPort) && !enabledPaths) {
response.sendError(404, "Unknown address"); response.sendError(404, "Unknown address");

View File

@ -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);
}
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
package dev.vality.disputes.constant;
public class ModerationPrefix {
public static final String DISPUTES_UNKNOWN_MAPPING = "disputes_unknown_mapping";
}

View File

@ -113,28 +113,32 @@ public class DisputeDao extends AbstractGenericDao {
} }
public UUID update(UUID disputeId, DisputeStatus status) { 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) { 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) { 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) { 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, public UUID update(UUID disputeId, DisputeStatus status, Long changedAmount,
Boolean skipCallHgForCreateAdjustment) { 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, 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) var set = getDslContext().update(DISPUTE)
.set(DISPUTE.STATUS, status); .set(DISPUTE.STATUS, status);
if (nextCheckAfter != null) { if (nextCheckAfter != null) {
@ -143,6 +147,9 @@ public class DisputeDao extends AbstractGenericDao {
if (errorMessage != null) { if (errorMessage != null) {
set = set.set(DISPUTE.ERROR_MESSAGE, errorMessage); set = set.set(DISPUTE.ERROR_MESSAGE, errorMessage);
} }
if (mapping != null) {
set = set.set(DISPUTE.MAPPING, mapping);
}
if (changedAmount != null) { if (changedAmount != null) {
set = set.set(DISPUTE.CHANGED_AMOUNT, changedAmount); set = set.set(DISPUTE.CHANGED_AMOUNT, changedAmount);
} }

View File

@ -31,11 +31,12 @@ public class MerchantDisputesHandler implements MerchantDisputesServiceSrv.Iface
} }
@Override @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(); var response = disputesApiDelegate.status(getRequestID(), disputeContext.getDisputeId(), false).getBody();
return switch (response.getStatus()) { return switch (response.getStatus()) {
case PENDING -> DisputeStatusResult.statusPending(new DisputeStatusPendingResult()); 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()); case SUCCEEDED -> DisputeStatusResult.statusSuccess(new DisputeStatusSuccessResult());
}; };
} }
@ -44,7 +45,7 @@ public class MerchantDisputesHandler implements MerchantDisputesServiceSrv.Iface
return UUID.randomUUID().toString(); return UUID.randomUUID().toString();
} }
private String getErrorMessage(Status200Response response) { private String getMapping(Status200Response response) {
return Optional.ofNullable(response.getReason()) return Optional.ofNullable(response.getReason())
.map(GeneralError::getMessage) .map(GeneralError::getMessage)
.orElse(null); .orElse(null);

View File

@ -1,6 +1,7 @@
package dev.vality.disputes.schedule; 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 dev.vality.disputes.schedule.service.CreateAdjustmentsService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -16,13 +17,15 @@ import org.springframework.stereotype.Service;
public class TaskReadyForCreateAdjustmentsService { public class TaskReadyForCreateAdjustmentsService {
private final CreateAdjustmentsService createAdjustmentsService; private final CreateAdjustmentsService createAdjustmentsService;
private final ManualParsingTopic manualParsingTopic; private final CallbackNotifier callbackNotifier;
private final MdcTopicProducer mdcTopicProducer;
@Scheduled(fixedDelayString = "${dispute.fixedDelayReadyForCreateAdjustments}", initialDelayString = "${dispute.initialDelayReadyForCreateAdjustments}") @Scheduled(fixedDelayString = "${dispute.fixedDelayReadyForCreateAdjustments}", initialDelayString = "${dispute.initialDelayReadyForCreateAdjustments}")
public void processPending() { public void processPending() {
log.debug("Processing ReadyForCreateAdjustments get started"); log.debug("Processing ReadyForCreateAdjustments get started");
var disputes = createAdjustmentsService.getReadyDisputesForCreateAdjustment(); var disputes = createAdjustmentsService.getReadyDisputesForCreateAdjustment();
manualParsingTopic.sendReadyForCreateAdjustments(disputes); mdcTopicProducer.sendReadyForCreateAdjustments(disputes);
callbackNotifier.sendDisputeReadyForCreateAdjustment(disputes);
log.info("ReadyForCreateAdjustments were processed"); log.info("ReadyForCreateAdjustments were processed");
} }
} }

View File

@ -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<WRuntimeException> unexpectedResultMappingHandler) {
try {
runnable.run();
} catch (WRuntimeException e) {
if (externalGatewayChecker.isProvidersDisputesUnexpectedResultMapping(e)) {
unexpectedResultMappingHandler.accept(e);
return;
}
throw e;
}
}
}

View File

@ -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<Attachment> attachments, ProviderData providerData);
}

View File

@ -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<Attachment> 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;
}
}

View File

@ -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<Attachment> attachments, ProviderData providerData) {
log.debug("Trying to call DummyRemoteClientImpl.createDispute() {}", dispute.getId());
providerData.setRouteUrl(routeUrl);
return DisputeCreatedResult.successResult(new DisputeCreatedSuccessResult(UUID.randomUUID().toString()));
}
}

View File

@ -9,6 +9,7 @@ import dev.vality.disputes.schedule.converter.DisputeContextConverter;
import dev.vality.disputes.schedule.converter.DisputeParamsConverter; import dev.vality.disputes.schedule.converter.DisputeParamsConverter;
import dev.vality.disputes.schedule.model.ProviderData; import dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.disputes.schedule.service.ProviderIfaceBuilder; import dev.vality.disputes.schedule.service.ProviderIfaceBuilder;
import dev.vality.disputes.schedule.service.ProviderRouting;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -22,17 +23,18 @@ import java.util.List;
@SuppressWarnings({"ParameterName", "LineLength"}) @SuppressWarnings({"ParameterName", "LineLength"})
public class RemoteClient { public class RemoteClient {
private final ProviderRouting providerRouting;
private final ProviderIfaceBuilder providerIfaceBuilder; private final ProviderIfaceBuilder providerIfaceBuilder;
private final DisputeContextConverter disputeContextConverter;
private final DisputeParamsConverter disputeParamsConverter; private final DisputeParamsConverter disputeParamsConverter;
private final DisputeContextConverter disputeContextConverter;
@SneakyThrows @SneakyThrows
public DisputeCreatedResult createDispute(Dispute dispute, List<Attachment> attachments, ProviderData providerData) { public DisputeCreatedResult createDispute(Dispute dispute, List<Attachment> 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()); log.debug("Trying to build disputeParams {}", dispute.getId());
var disputeParams = disputeParamsConverter.convert(dispute, attachments, providerData.getOptions()); 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()); log.debug("Trying to routed remote provider's createDispute() call {}", dispute.getId());
var result = remoteClient.createDispute(disputeParams); var result = remoteClient.createDispute(disputeParams);
log.info("Routed remote provider's createDispute() has been called {} {}", dispute.getId(), result); log.info("Routed remote provider's createDispute() has been called {} {}", dispute.getId(), result);
@ -41,11 +43,11 @@ public class RemoteClient {
@SneakyThrows @SneakyThrows
public DisputeStatusResult checkDisputeStatus(Dispute dispute, ProviderDispute providerDispute, ProviderData providerData) { 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()); log.debug("Trying to build disputeContext {}", dispute.getId());
var disputeContext = disputeContextConverter.convert(dispute, providerDispute, providerData.getOptions()); 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()); log.debug("Trying to routed remote provider's checkDisputeStatus() call {}", dispute.getId());
var result = remoteClient.checkDisputeStatus(disputeContext); var result = remoteClient.checkDisputeStatus(disputeContext);
log.info("Routed remote provider's checkDisputeStatus() has been called {} {}", dispute.getId(), result); log.info("Routed remote provider's checkDisputeStatus() has been called {} {}", dispute.getId(), result);

View File

@ -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());
}
}

View File

@ -1,19 +1,26 @@
package dev.vality.disputes.schedule.handler; 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.dao.DisputeDao;
import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.enums.DisputeStatus;
import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.domain.tables.pojos.Dispute;
import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper;
import dev.vality.disputes.provider.DisputeStatusResult; import dev.vality.disputes.provider.DisputeStatusResult;
import dev.vality.disputes.utils.ErrorFormatter; import dev.vality.disputes.utils.ErrorFormatter;
import dev.vality.woody.api.flow.error.WRuntimeException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map; import java.util.Map;
import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -22,6 +29,8 @@ public class DisputeStatusResultHandler {
private final DisputeDao disputeDao; private final DisputeDao disputeDao;
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
private final CallbackNotifier callbackNotifier;
private final MdcTopicProducer mdcTopicProducer;
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public void handleStatusPending(Dispute dispute, DisputeStatusResult result, Map<String, String> options) { public void handleStatusPending(Dispute dispute, DisputeStatusResult result, Map<String, String> options) {
@ -36,17 +45,48 @@ public class DisputeStatusResultHandler {
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public void handleStatusFail(Dispute dispute, DisputeStatusResult result) { public void handleStatusFail(Dispute dispute, DisputeStatusResult result) {
var errorMessage = ErrorFormatter.getErrorMessage(result.getStatusFail().getFailure()); var failure = result.getStatusFail().getFailure();
log.warn("Trying to set failed Dispute status {}, {}", dispute.getId(), errorMessage); var errorMessage = ErrorFormatter.getErrorMessage(failure);
disputeDao.update(dispute.getId(), DisputeStatus.failed, errorMessage); if (errorMessage.startsWith(DISPUTES_UNKNOWN_MAPPING)) {
log.debug("Dispute status has been set to failed {}", dispute.getId()); 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) @Transactional(propagation = Propagation.REQUIRED)
public void handleStatusSuccess(Dispute dispute, DisputeStatusResult result) { public void handleStatusSuccess(Dispute dispute, DisputeStatusResult result) {
callbackNotifier.sendDisputeReadyForCreateAdjustment(List.of(dispute));
mdcTopicProducer.sendReadyForCreateAdjustments(List.of(dispute));
var changedAmount = result.getStatusSuccess().getChangedAmount().orElse(null); var changedAmount = result.getStatusSuccess().getChangedAmount().orElse(null);
log.info("Trying to set create_adjustment Dispute status {}, {}", dispute, result); log.info("Trying to set create_adjustment Dispute status {}, {}", dispute, result);
disputeDao.update(dispute.getId(), DisputeStatus.create_adjustment, changedAmount); disputeDao.update(dispute.getId(), DisputeStatus.create_adjustment, changedAmount);
log.debug("Dispute status has been set to create_adjustment {}", dispute.getId()); 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());
}
} }

View File

@ -11,5 +11,6 @@ public class ProviderData {
private Map<String, String> options; private Map<String, String> options;
private String defaultProviderUrl; private String defaultProviderUrl;
private String routeUrl;
} }

View File

@ -3,18 +3,15 @@ package dev.vality.disputes.schedule.service;
import dev.vality.damsel.payment_processing.InvoicePayment; import dev.vality.damsel.payment_processing.InvoicePayment;
import dev.vality.disputes.constant.ErrorReason; import dev.vality.disputes.constant.ErrorReason;
import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.dao.DisputeDao;
import dev.vality.disputes.dao.ProviderDisputeDao;
import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.enums.DisputeStatus;
import dev.vality.disputes.domain.tables.pojos.Dispute; 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.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.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.service.external.InvoicingService;
import dev.vality.disputes.utils.ErrorFormatter;
import dev.vality.woody.api.flow.error.WRuntimeException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,18 +28,17 @@ import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_PRO
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) @SuppressWarnings({"MemberName", "ParameterName", "LineLength", "MissingSwitchDefault"})
public class CreatedDisputesService { public class CreatedDisputesService {
private final RemoteClient remoteClient; private final RemoteClient remoteClient;
private final DisputeDao disputeDao; private final DisputeDao disputeDao;
private final ProviderDisputeDao providerDisputeDao;
private final CreatedAttachmentsService createdAttachmentsService; private final CreatedAttachmentsService createdAttachmentsService;
private final InvoicingService invoicingService; private final InvoicingService invoicingService;
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
private final ProviderDataService providerDataService; private final ProviderDataService providerDataService;
private final ExternalGatewayChecker externalGatewayChecker; private final DefaultRemoteClient defaultRemoteClient;
private final ManualParsingTopic manualParsingTopic; private final DisputeCreateResultHandler disputeCreateResultHandler;
private final WRuntimeExceptionCatcher wRuntimeExceptionCatcher;
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public List<Dispute> getCreatedDisputesForUpdateSkipLocked(int batchSize) { public List<Dispute> getCreatedDisputesForUpdateSkipLocked(int batchSize) {
@ -87,54 +83,40 @@ public class CreatedDisputesService {
|| isNotProvidersDisputesApiExist(options)) { || isNotProvidersDisputesApiExist(options)) {
// отправлять на ручной разбор, если выставлена опция // отправлять на ручной разбор, если выставлена опция
// DISPUTE_FLOW_CAPTURED_BLOCKED или не выставлена DISPUTE_FLOW_PROVIDERS_API_EXIST // DISPUTE_FLOW_CAPTURED_BLOCKED или не выставлена DISPUTE_FLOW_PROVIDERS_API_EXIST
log.warn("finishTaskWithManualParsingFlowActivation, options capt={}, apiExist={}", isCapturedBlockedForDispute(options), isNotProvidersDisputesApiExist(options)); log.warn("Trying to call defaultRemoteClient.createDispute(), options capt={}, apiExist={}", isCapturedBlockedForDispute(options), isNotProvidersDisputesApiExist(options));
finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); wRuntimeExceptionCatcher.catchUnexpectedResultMapping(
() -> {
var result = defaultRemoteClient.createDispute(dispute, attachments, providerData);
finishTask(dispute, result, providerData);
},
e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e));
return; return;
} }
try { wRuntimeExceptionCatcher.catchUnexpectedResultMapping(
var result = remoteClient.createDispute(dispute, attachments, providerData); () -> wRuntimeExceptionCatcher.catchProvidersDisputesApiNotExist(
finishTask(dispute, attachments, result, options); providerData,
} catch (WRuntimeException e) { () -> {
if (externalGatewayChecker.isNotProvidersDisputesApiExist(providerData, e)) { var result = remoteClient.createDispute(dispute, attachments, providerData);
// отправлять на ручной разбор, если API диспутов на провайдере не реализовано finishTask(dispute, result, providerData);
// (тогда при тесте соединения вернется 404) },
log.warn("finishTaskWithManualParsingFlowActivation with externalGatewayChecker", e); () -> wRuntimeExceptionCatcher.catchUnexpectedResultMapping(
finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); () -> {
return; var result = defaultRemoteClient.createDispute(dispute, attachments, providerData);
} finishTask(dispute, result, providerData);
throw e; },
} e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e))),
e -> disputeCreateResultHandler.handleUnexpectedResultMapping(dispute, e));
} }
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
void finishTask(Dispute dispute, List<Attachment> attachments, DisputeCreatedResult result, Map<String, String> options) { void finishTask(Dispute dispute, DisputeCreatedResult result, ProviderData providerData) {
switch (result.getSetField()) { switch (result.getSetField()) {
case SUCCESS_RESULT -> { case SUCCESS_RESULT -> disputeCreateResultHandler.handleSuccessResult(dispute, result, providerData);
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options); case FAIL_RESULT -> disputeCreateResultHandler.handleFailResult(dispute, result);
log.info("Trying to set pending Dispute status {}, {}", dispute, result); case ALREADY_EXIST_RESULT -> disputeCreateResultHandler.handleAlreadyExistResult(dispute);
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);
} }
} }
@Transactional(propagation = Propagation.REQUIRED)
void finishTaskWithManualParsingFlowActivation(Dispute dispute, List<Attachment> 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<String, String> options) { private boolean isCapturedBlockedForDispute(Map<String, String> options) {
return options.containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED); return options.containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED);
} }

View File

@ -22,23 +22,32 @@ public class ExternalGatewayChecker {
private final CloseableHttpClient httpClient; private final CloseableHttpClient httpClient;
private final ProviderRouting providerRouting; private final ProviderRouting providerRouting;
public boolean isNotProvidersDisputesApiExist(ProviderData providerData, WRuntimeException e) { public boolean isProvidersDisputesUnexpectedResultMapping(WRuntimeException e) {
return e.getErrorDefinition() != null return e.getErrorDefinition() != null
&& e.getErrorDefinition().getGenerationSource() == WErrorSource.EXTERNAL && e.getErrorDefinition().getGenerationSource() == WErrorSource.EXTERNAL
&& e.getErrorDefinition().getErrorType() == WErrorType.UNEXPECTED_ERROR && e.getErrorDefinition().getErrorType() == WErrorType.UNEXPECTED_ERROR
&& e.getErrorDefinition().getErrorSource() == WErrorSource.INTERNAL && 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 @SneakyThrows
private Boolean isNotFoundProvidersDisputesApi(ProviderData providerData) { private Boolean isProvidersDisputesApiNotFound(ProviderData providerData) {
return httpClient.execute(new HttpGet(getRouteUrl(providerData)), isNotFoundResponse()); return httpClient.execute(new HttpGet(getRouteUrl(providerData)), isNotFoundResponse());
} }
private String getRouteUrl(ProviderData providerData) { private String getRouteUrl(ProviderData providerData) {
var routeUrl = providerRouting.getRouteUrl(providerData); providerRouting.initRouteUrl(providerData);
log.debug("Check adapter connection, routeUrl={}", routeUrl); log.debug("Check adapter connection, routeUrl={}", providerData.getRouteUrl());
return routeUrl; return providerData.getRouteUrl();
} }
private HttpClientResponseHandler<Boolean> isNotFoundResponse() { private HttpClientResponseHandler<Boolean> isNotFoundResponse() {

View File

@ -1,14 +1,13 @@
package dev.vality.disputes.schedule.service; package dev.vality.disputes.schedule.service;
import dev.vality.disputes.constant.ErrorReason;
import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.dao.DisputeDao;
import dev.vality.disputes.dao.ProviderDisputeDao; import dev.vality.disputes.dao.ProviderDisputeDao;
import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.enums.DisputeStatus;
import dev.vality.disputes.domain.tables.pojos.Dispute; 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.ExponentialBackOffPollingServiceWrapper;
import dev.vality.disputes.polling.PollingInfoService; import dev.vality.disputes.polling.PollingInfoService;
import dev.vality.disputes.provider.DisputeStatusResult; 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.client.RemoteClient;
import dev.vality.disputes.schedule.handler.DisputeStatusResultHandler; import dev.vality.disputes.schedule.handler.DisputeStatusResultHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -24,7 +23,7 @@ import java.util.Map;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) @SuppressWarnings({"MemberName", "ParameterName", "LineLength", "MissingSwitchDefault"})
public class PendingDisputesService { public class PendingDisputesService {
private final RemoteClient remoteClient; private final RemoteClient remoteClient;
@ -34,7 +33,7 @@ public class PendingDisputesService {
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
private final ProviderDataService providerDataService; private final ProviderDataService providerDataService;
private final DisputeStatusResultHandler disputeStatusResultHandler; private final DisputeStatusResultHandler disputeStatusResultHandler;
private final ManualParsingTopic manualParsingTopic; private final WRuntimeExceptionCatcher wRuntimeExceptionCatcher;
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public List<Dispute> getPendingDisputesForUpdateSkipLocked(int batchSize) { public List<Dispute> getPendingDisputesForUpdateSkipLocked(int batchSize) {
@ -65,15 +64,16 @@ public class PendingDisputesService {
return; return;
} }
if (pollingInfoService.isDeadline(dispute)) { if (pollingInfoService.isDeadline(dispute)) {
manualParsingTopic.sendPoolingExpired(dispute); disputeStatusResultHandler.handlePoolingExpired(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());
return; return;
} }
log.debug("ProviderDispute has been found {}", dispute.getId()); log.debug("ProviderDispute has been found {}", dispute.getId());
var result = remoteClient.checkDisputeStatus(dispute, providerDispute, providerData); wRuntimeExceptionCatcher.catchUnexpectedResultMapping(
finishTask(dispute, result, providerData.getOptions()); () -> {
var result = remoteClient.checkDisputeStatus(dispute, providerDispute, providerData);
finishTask(dispute, result, providerData.getOptions());
},
e -> disputeStatusResultHandler.handleUnexpectedResultMapping(dispute, e));
} }
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)

View File

@ -38,7 +38,7 @@ public class ProviderDataService {
var terminal = dominantAsyncService.getTerminal(payment.getRoute().getTerminal()); var terminal = dominantAsyncService.getTerminal(payment.getRoute().getTerminal());
var proxy = dominantAsyncService.getProxy(provider.get().getProxy().getRef()); var proxy = dominantAsyncService.getProxy(provider.get().getProxy().getRef());
return ProviderData.builder() 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()) .defaultProviderUrl(proxy.get().getUrl())
.build(); .build();
} }

View File

@ -2,7 +2,6 @@ package dev.vality.disputes.schedule.service;
import dev.vality.disputes.config.properties.AdaptersConnectionProperties; import dev.vality.disputes.config.properties.AdaptersConnectionProperties;
import dev.vality.disputes.provider.ProviderDisputesServiceSrv; import dev.vality.disputes.provider.ProviderDisputesServiceSrv;
import dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -18,12 +17,10 @@ import java.util.concurrent.TimeUnit;
@SuppressWarnings({"AbbreviationAsWordInName", "LineLength"}) @SuppressWarnings({"AbbreviationAsWordInName", "LineLength"})
public class ProviderIfaceBuilder { public class ProviderIfaceBuilder {
private final ProviderRouting providerRouting;
private final AdaptersConnectionProperties adaptersConnectionProperties; private final AdaptersConnectionProperties adaptersConnectionProperties;
@Cacheable(value = "adapters", key = "#providerData.defaultProviderUrl", cacheManager = "adaptersCacheManager") @Cacheable(value = "adapters", key = "#root.args[0]", cacheManager = "adaptersCacheManager")
public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(ProviderData providerData) { public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(String routeUrl) {
var routeUrl = providerRouting.getRouteUrl(providerData);
log.info("Creating new client for url: {}", routeUrl); log.info("Creating new client for url: {}", routeUrl);
return new THSpawnClientBuilder() return new THSpawnClientBuilder()
.withNetworkTimeout((int) TimeUnit.SECONDS.toMillis(adaptersConnectionProperties.getTimeoutSec())) .withNetworkTimeout((int) TimeUnit.SECONDS.toMillis(adaptersConnectionProperties.getTimeoutSec()))

View File

@ -18,12 +18,12 @@ public class ProviderRouting {
private static final String DISPUTES_URL_POSTFIX_DEFAULT = "disputes"; private static final String DISPUTES_URL_POSTFIX_DEFAULT = "disputes";
private static final String OPTION_DISPUTES_URL_FIELD_NAME = "disputes_url"; 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); var url = providerData.getOptions().get(OPTION_DISPUTES_URL_FIELD_NAME);
if (ObjectUtils.isEmpty(url)) { if (ObjectUtils.isEmpty(url)) {
url = createDefaultRouteUrl(providerData.getDefaultProviderUrl()); url = createDefaultRouteUrl(providerData.getDefaultProviderUrl());
} }
return url; providerData.setRouteUrl(url);
} }
private String createDefaultRouteUrl(String defaultProviderUrl) { private String createDefaultRouteUrl(String defaultProviderUrl) {

View File

@ -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<DisputeReadyForCreateAdjustment> disputeReadyForCreateAdjustments);
void sendDisputeFailedReviewRequired(DisputeFailedReviewRequired disputeFailedReviewRequired);
}

View File

@ -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<DisputeReadyForCreateAdjustment> 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());
}
}

View File

@ -1,6 +1,6 @@
package dev.vality.disputes.servlet; 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 dev.vality.woody.thrift.impl.http.THServiceBuilder;
import jakarta.servlet.*; import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.annotation.WebServlet;
@ -8,11 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException; import java.io.IOException;
@WebServlet("/disputes-api/v1/manual-parsing") @WebServlet("/disputes-api/v1/admin-management")
public class ManualParsingServlet extends GenericServlet { public class AdminManagementServlet extends GenericServlet {
@Autowired @Autowired
private ManualParsingServiceSrv.Iface manualParsingHandler; private AdminManagementServiceSrv.Iface adminManagementHandler;
private Servlet servlet; private Servlet servlet;
@ -20,7 +20,7 @@ public class ManualParsingServlet extends GenericServlet {
public void init(ServletConfig config) throws ServletException { public void init(ServletConfig config) throws ServletException {
super.init(config); super.init(config);
servlet = new THServiceBuilder() servlet = new THServiceBuilder()
.build(ManualParsingServiceSrv.Iface.class, manualParsingHandler); .build(AdminManagementServiceSrv.Iface.class, adminManagementHandler);
} }
@Override @Override

View File

@ -12,4 +12,11 @@ public class ErrorFormatter {
} }
return TErrorUtil.toStringVal(failure); return TErrorUtil.toStringVal(failure);
} }
public static String getErrorMessage(String errorCode, String errorDescription) {
if (!StringUtils.isBlank(errorDescription)) {
return errorCode + ": " + errorDescription;
}
return errorCode;
}
} }

View File

@ -64,6 +64,17 @@ service:
shops: shops:
poolSize: 10 poolSize: 10
ttlSec: 86400 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: adapters:
connection: connection:
timeoutSec: 30 timeoutSec: 30
@ -106,18 +117,15 @@ dispute:
isScheduleCreatedEnabled: true isScheduleCreatedEnabled: true
isSchedulePendingEnabled: true isSchedulePendingEnabled: true
isScheduleCreateAdjustmentsEnabled: true isScheduleCreateAdjustmentsEnabled: true
isScheduleReadyForCreateAdjustmentsEnabled: true isScheduleReadyForCreateAdjustmentsEnabled: false
time: time:
config: config:
max-time-polling-min: 600 max-time-polling-min: 600
manual-parsing-topic:
enabled: true
testcontainers: testcontainers:
postgresql: postgresql:
tag: '11.4' tag: '14.12'
http-client: http-client:
requestTimeout: 60000 requestTimeout: 60000
@ -125,3 +133,7 @@ http-client:
connectionTimeout: 10000 connectionTimeout: 10000
maxTotalPooling: 200 maxTotalPooling: 200
defaultMaxPerRoute: 200 defaultMaxPerRoute: 200
otel:
resource: http://localhost:4318/v1/traces
timeout: 60000

View File

@ -0,0 +1,2 @@
ALTER TABLE dspt.dispute
ADD COLUMN "mapping" CHARACTER VARYING;

View File

@ -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.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@ -16,10 +16,8 @@ import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,43 +26,49 @@ import java.util.List;
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping({"/debug/disputes-api/manual-parsing"}) @RequestMapping({"/debug/disputes-api/admin-management"})
@Slf4j @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()); private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jdk8Module());
@PostMapping("/cancel") @PostMapping("/cancel")
@SneakyThrows @SneakyThrows
public void cancelPending(@RequestBody String body) { public void cancelPending(@RequestBody String body) {
log.debug("cancelPending {}", body); log.debug("cancelPending {}", body);
manualParsingHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class)); adminManagementHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class));
} }
@PostMapping("/approve") @PostMapping("/approve")
@SneakyThrows @SneakyThrows
public void approvePending(@RequestBody String body) { public void approvePending(@RequestBody String body) {
log.debug("approvePending {}", body); log.debug("approvePending {}", body);
manualParsingHandler.approvePending(objectMapper.readValue(body, ApproveParamsRequest.class)); adminManagementHandler.approvePending(objectMapper.readValue(body, ApproveParamsRequest.class));
} }
@PostMapping("/bind") @PostMapping("/bind")
@SneakyThrows @SneakyThrows
public void bindCreated(@RequestBody String body) { public void bindCreated(@RequestBody String body) {
log.debug("bindCreated {}", body); log.debug("bindCreated {}", body);
manualParsingHandler.bindCreated(objectMapper.readValue(body, BindParamsRequest.class)); adminManagementHandler.bindCreated(objectMapper.readValue(body, BindParamsRequest.class));
} }
@PostMapping("/get") @PostMapping("/get")
@SneakyThrows @SneakyThrows
public DisputeResult getDisputes(@RequestBody String body) { public DisputeResult getDisputes(@RequestBody String body) {
log.debug("getDispute {}", 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<>() { return objectMapper.convertValue(dispute, new TypeReference<>() {
}); });
} }
@GetMapping("/disputes")
@ResponseStatus(HttpStatus.NOT_FOUND)
public void defaultRouteUrl() {
log.info("hi");
}
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)

View File

@ -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.Attachment;
import dev.vality.disputes.admin.Dispute; import dev.vality.disputes.admin.Dispute;
import dev.vality.disputes.admin.DisputeResult; import dev.vality.disputes.admin.DisputeResult;
import dev.vality.disputes.admin.ManualParsingServiceSrv;
import dev.vality.disputes.config.SpringBootUTest; import dev.vality.disputes.config.SpringBootUTest;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -19,17 +19,17 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@SpringBootUTest @SpringBootUTest
public class DebugManualParsingControllerTest { public class DebugAdminManagementControllerTest {
@MockBean @MockBean
private ManualParsingServiceSrv.Iface manualParsingHandler; private AdminManagementServiceSrv.Iface adminManagementHandler;
@Autowired @Autowired
private DebugManualParsingController debugManualParsingController; private DebugAdminManagementController debugAdminManagementController;
@Test @Test
@SneakyThrows @SneakyThrows
public void checkSerialization() { public void checkSerialization() {
debugManualParsingController.approvePending(""" debugAdminManagementController.approvePending("""
{ {
"approveParams": [ "approveParams": [
{ {
@ -39,7 +39,7 @@ public class DebugManualParsingControllerTest {
] ]
} }
"""); """);
debugManualParsingController.cancelPending(""" debugAdminManagementController.cancelPending("""
{ {
"cancelParams": [ "cancelParams": [
{ {
@ -49,7 +49,7 @@ public class DebugManualParsingControllerTest {
] ]
} }
"""); """);
debugManualParsingController.cancelPending(""" debugAdminManagementController.cancelPending("""
{ {
"cancelParams": [ "cancelParams": [
{ {
@ -59,7 +59,7 @@ public class DebugManualParsingControllerTest {
] ]
} }
"""); """);
debugManualParsingController.bindCreated(""" debugAdminManagementController.bindCreated("""
{ {
"bindParams": [ "bindParams": [
{ {
@ -77,9 +77,9 @@ public class DebugManualParsingControllerTest {
randomed.setDisputes(List.of( randomed.setDisputes(List.of(
randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(b))), randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(b))),
randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(a))))); randomThrift(Dispute.class).setAttachments(List.of(new Attachment().setData(a)))));
given(manualParsingHandler.getDisputes(any())) given(adminManagementHandler.getDisputes(any()))
.willReturn(randomed); .willReturn(randomed);
var disputes = debugManualParsingController.getDisputes(""" var disputes = debugAdminManagementController.getDisputes("""
{ {
"disputeParams": [ "disputeParams": [
{ {

View File

@ -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.config.WireMockSpringBootITest;
import dev.vality.disputes.dao.DisputeDao; import dev.vality.disputes.dao.DisputeDao;
@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@WireMockSpringBootITest @WireMockSpringBootITest
@Import({PendingDisputesTestService.class}) @Import({PendingDisputesTestService.class})
public class DebugManualParsingHandlerTest { public class DebugAdminManagementHandlerTest {
@Autowired @Autowired
private DisputeDao disputeDao; private DisputeDao disputeDao;
@ -31,19 +31,19 @@ public class DebugManualParsingHandlerTest {
@Autowired @Autowired
private PendingDisputesTestService pendingDisputesTestService; private PendingDisputesTestService pendingDisputesTestService;
@Autowired @Autowired
private DebugManualParsingController debugManualParsingController; private DebugAdminManagementController debugAdminManagementController;
@Test @Test
public void testCancelCreateAdjustment() { public void testCancelCreateAdjustment() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely();
debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString()));
assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus());
} }
@Test @Test
public void testCancelPending() { public void testCancelPending() {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString()));
assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.cancelled, disputeDao.get(disputeId).get().getStatus());
} }
@ -51,14 +51,14 @@ public class DebugManualParsingHandlerTest {
public void testCancelFailed() { public void testCancelFailed() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely();
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
debugManualParsingController.cancelPending(getCancelRequest(disputeId.toString())); debugAdminManagementController.cancelPending(getCancelRequest(disputeId.toString()));
assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus());
} }
@Test @Test
public void testApproveCreateAdjustmentWithCallHg() { public void testApproveCreateAdjustmentWithCallHg() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); 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()); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -66,7 +66,7 @@ public class DebugManualParsingHandlerTest {
@Test @Test
public void testApproveCreateAdjustmentWithSkipHg() { public void testApproveCreateAdjustmentWithSkipHg() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely();
debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), true)); debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), true));
assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -74,7 +74,7 @@ public class DebugManualParsingHandlerTest {
@Test @Test
public void testApprovePendingWithSkipHg() { public void testApprovePendingWithSkipHg() {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
debugManualParsingController.approvePending(getApproveRequest(disputeId.toString(), true)); debugAdminManagementController.approvePending(getApproveRequest(disputeId.toString(), true));
assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.succeeded, disputeDao.get(disputeId).get().getStatus());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -83,7 +83,7 @@ public class DebugManualParsingHandlerTest {
public void testApproveFailed() { public void testApproveFailed() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely();
disputeDao.update(disputeId, DisputeStatus.failed); 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()); assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus());
} }
@ -91,7 +91,7 @@ public class DebugManualParsingHandlerTest {
public void testBindCreatedCreateAdjustment() { public void testBindCreatedCreateAdjustment() {
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); var disputeId = pendingDisputesTestService.callPendingDisputeRemotely();
var providerDisputeId = generateId(); var providerDisputeId = generateId();
debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId));
assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -100,7 +100,7 @@ public class DebugManualParsingHandlerTest {
public void testBindCreatedPending() { public void testBindCreatedPending() {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely(); var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
var providerDisputeId = generateId(); var providerDisputeId = generateId();
debugManualParsingController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId)); debugAdminManagementController.bindCreated(getBindCreatedRequest(disputeId.toString(), providerDisputeId));
assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).get().getStatus()); assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).get().getStatus());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -112,7 +112,7 @@ public class DebugManualParsingHandlerTest {
var providerDisputeId = generateId(); var providerDisputeId = generateId();
var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId();
disputeDao.update(UUID.fromString(disputeId), DisputeStatus.manual_created); 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()); assertEquals(DisputeStatus.manual_pending, disputeDao.get(UUID.fromString(disputeId)).get().getStatus());
disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed);
} }
@ -124,7 +124,7 @@ public class DebugManualParsingHandlerTest {
var providerDisputeId = generateId(); var providerDisputeId = generateId();
var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId(); var disputeId = disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId();
disputeDao.update(UUID.fromString(disputeId), DisputeStatus.already_exist_created); 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()); assertEquals(DisputeStatus.pending, disputeDao.get(UUID.fromString(disputeId)).get().getStatus());
disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed); disputeDao.update(UUID.fromString(disputeId), DisputeStatus.failed);
} }
@ -134,7 +134,7 @@ public class DebugManualParsingHandlerTest {
public void testGetDispute() { public void testGetDispute() {
WiremockUtils.mockS3AttachmentDownload(); WiremockUtils.mockS3AttachmentDownload();
var disputeId = pendingDisputesTestService.callPendingDisputeRemotely(); 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()); assertEquals(1, disputes.getDisputes().size());
disputeDao.update(disputeId, DisputeStatus.failed); disputeDao.update(disputeId, DisputeStatus.failed);
} }

View File

@ -1,8 +1,8 @@
package dev.vality.disputes.api; package dev.vality.disputes.api;
import dev.vality.damsel.payment_processing.InvoicingSrv; import dev.vality.damsel.payment_processing.InvoicingSrv;
import dev.vality.disputes.admin.AdminManagementServiceSrv;
import dev.vality.disputes.admin.CancelParamsRequest; import dev.vality.disputes.admin.CancelParamsRequest;
import dev.vality.disputes.admin.ManualParsingServiceSrv;
import dev.vality.disputes.callback.DisputeCallbackParams; import dev.vality.disputes.callback.DisputeCallbackParams;
import dev.vality.disputes.callback.ProviderDisputesCallbackServiceSrv; import dev.vality.disputes.callback.ProviderDisputesCallbackServiceSrv;
import dev.vality.disputes.config.WireMockSpringBootITest; import dev.vality.disputes.config.WireMockSpringBootITest;
@ -53,11 +53,11 @@ public class ServletTest {
@Test @Test
@SneakyThrows @SneakyThrows
public void manualServletTest() { public void adminManagementServletTest() {
var iface = new THSpawnClientBuilder() 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) .withNetworkTimeout(5000)
.build(ManualParsingServiceSrv.Iface.class); .build(AdminManagementServiceSrv.Iface.class);
var request = DamselUtil.fillRequiredTBaseObject( var request = DamselUtil.fillRequiredTBaseObject(
new CancelParamsRequest(), new CancelParamsRequest(),
CancelParamsRequest.class CancelParamsRequest.class

View File

@ -17,12 +17,15 @@ import dev.vality.file.storage.FileStorageSrv;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import java.util.UUID; import java.util.UUID;
import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING;
import static dev.vality.disputes.util.MockUtil.*; import static dev.vality.disputes.util.MockUtil.*;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -49,6 +52,8 @@ public class CreatedDisputesServiceTest {
private WiremockAddressesHolder wiremockAddressesHolder; private WiremockAddressesHolder wiremockAddressesHolder;
@Autowired @Autowired
private CreatedDisputesTestService createdDisputesTestService; private CreatedDisputesTestService createdDisputesTestService;
@LocalServerPort
private int serverPort;
@Test @Test
@SneakyThrows @SneakyThrows
@ -92,7 +97,7 @@ public class CreatedDisputesServiceTest {
@Test @Test
@SneakyThrows @SneakyThrows
public void testManualCreatedWhenIsNotProvidersDisputesApiExist() { public void testManualPendingWhenIsNotProvidersDisputesApiExist() {
var invoiceId = "20McecNnWoy"; var invoiceId = "20McecNnWoy";
var paymentId = "1"; var paymentId = "1";
var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId()); var disputeId = UUID.fromString(disputeApiTestService.createDisputeViaApi(invoiceId, paymentId).getDisputeId());
@ -105,7 +110,7 @@ public class CreatedDisputesServiceTest {
when(dominantService.getProxy(any())).thenReturn(createProxy().get()); when(dominantService.getProxy(any())).thenReturn(createProxy().get());
var dispute = disputeDao.get(disputeId); var dispute = disputeDao.get(disputeId);
createdDisputesService.callCreateDisputeRemotely(dispute.get()); 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); disputeDao.update(disputeId, DisputeStatus.failed);
} }
@ -138,6 +143,83 @@ public class CreatedDisputesServiceTest {
assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); 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 @Test
@SneakyThrows @SneakyThrows
public void testDisputeCreatedAlreadyExistResult() { public void testDisputeCreatedAlreadyExistResult() {

View File

@ -15,8 +15,10 @@ import org.springframework.context.annotation.Import;
import java.util.UUID; import java.util.UUID;
import static dev.vality.disputes.constant.ModerationPrefix.DISPUTES_UNKNOWN_MAPPING;
import static dev.vality.disputes.util.MockUtil.*; import static dev.vality.disputes.util.MockUtil.*;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -77,6 +79,37 @@ public class PendingDisputesServiceTest {
assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus()); 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 @Test
@SneakyThrows @SneakyThrows
public void testDisputeStatusPendingResult() { public void testDisputeStatusPendingResult() {

View File

@ -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;
}

View File

@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@TestComponent @TestComponent
@Import({DisputeApiTestService.class, RemoteClientTestConfig.class}) @Import({DisputeApiTestService.class, RemoteClientTestConfig.class, DefaultRemoteClientTestConfig.class, CallbackNotifierTestConfig.class})
@SuppressWarnings({"ParameterName", "LineLength"}) @SuppressWarnings({"ParameterName", "LineLength"})
public class CreatedDisputesTestService { public class CreatedDisputesTestService {

View File

@ -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;
}

View File

@ -14,6 +14,10 @@ import dev.vality.file.storage.NewFileResult;
import dev.vality.geck.common.util.TypeUtil; import dev.vality.geck.common.util.TypeUtil;
import dev.vality.token.keeper.AuthData; import dev.vality.token.keeper.AuthData;
import dev.vality.token.keeper.AuthDataStatus; 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.SneakyThrows;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.apache.thrift.TSerializer; import org.apache.thrift.TSerializer;
@ -81,8 +85,16 @@ public class MockUtil {
.setProxy(new Proxy().setRef(new ProxyRef().setId(1)))); .setProxy(new Proxy().setRef(new ProxyRef().setId(1))));
} }
public static CompletableFuture<ProxyDefinition> createProxyNotFoundCase(Integer port) {
return createProxy("http://127.0.0.1:" + port + "/debug/disputes-api/admin-management");
}
public static CompletableFuture<ProxyDefinition> createProxyWithRealAddress(Integer port) {
return createProxy("http://127.0.0.1:" + port);
}
public static CompletableFuture<ProxyDefinition> createProxy() { public static CompletableFuture<ProxyDefinition> createProxy() {
return createProxy("http://ya.ru"); return createProxy("http://127.0.0.1:8023");
} }
public static CompletableFuture<ProxyDefinition> createProxy(String url) { public static CompletableFuture<ProxyDefinition> createProxy(String url) {
@ -173,4 +185,13 @@ public class MockUtil {
failure.setSub(new SubFailure("some_suberror")); failure.setSub(new SubFailure("some_suberror"));
return failure; 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);
}
} }