diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5cbf745..06c4260 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,8 @@ on: push: branches: - 'master' + - 'main' + - 'epic/**' jobs: build-and-deploy: diff --git a/README.md b/README.md index f7177ed..2d0dc00 100644 --- a/README.md +++ b/README.md @@ -115,15 +115,15 @@ - Перед переводом диспута в финальный статус саппорт должен будет забиндить айди созданного диспута в провайдере через ручку `BindCreated()`. Здесь особенность, что этот метод фильтрует возможность биндить диспуты только созданные - вручную (из `manual_parsing_created`) + вручную (из `manual_created`) Далее, в режиме ручного разбора есть опция финализации диспута в фейл (`CancelPending()`) либо в успех (`ApprovePending()`). Здесь особенность, что в фейл можно перевести любой диспут имеющий не финальный статус, а в успех можно перевести, только если гарантировано создан внешний диспут у провайдера ( -из `pending`,`manual_parsing_binded_pending`) +из `pending`,`manual_pending`) - Из за того, что для ручных диспутов добавлены отдельные - статусы `manual_parsing_binded_pending` ,`manual_parsing_created` не происходит ситуации, что такие диспуты попадут в + статусы `manual_pending` ,`manual_created` не происходит ситуации, что такие диспуты попадут в таску `PendingDisputesService` которая автоматически вызывает апи провайдера для проверки статуса # Схема аппрува корректировок diff --git a/pom.xml b/pom.xml index 677b135..250b0ab 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ dev.vality provider-disputes-proto - 1.16-61eff7c + 1.18-6602d79 dev.vality @@ -89,10 +89,6 @@ - - org.springframework.boot - spring-boot-starter-aop - org.springframework.boot spring-boot-starter-jdbc @@ -193,6 +189,10 @@ 3.0.2 provided + + com.fasterxml.jackson.core + jackson-databind + org.openapitools jackson-databind-nullable diff --git a/src/main/java/dev/vality/disputes/DisputesApiApplication.java b/src/main/java/dev/vality/disputes/DisputesApiApplication.java index 6dbf7c0..ae298a3 100644 --- a/src/main/java/dev/vality/disputes/DisputesApiApplication.java +++ b/src/main/java/dev/vality/disputes/DisputesApiApplication.java @@ -4,8 +4,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; @EnableAsync +@EnableScheduling @ServletComponentScan @SpringBootApplication(scanBasePackages = {"dev.vality.disputes", "dev.vality.swag"}) public class DisputesApiApplication extends SpringApplication { diff --git a/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java b/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java index 5eb0754..694d3e8 100644 --- a/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java +++ b/src/main/java/dev/vality/disputes/api/converter/Status200ResponseConverter.java @@ -23,7 +23,7 @@ public class Status200ResponseConverter { private Status200Response.StatusEnum getStatus(Dispute dispute) { return switch (dispute.getStatus()) { - case created, pending, manual_created, manual_pending, create_adjustment -> + case created, pending, manual_created, manual_pending, create_adjustment, already_exist_created -> Status200Response.StatusEnum.PENDING; case succeeded -> Status200Response.StatusEnum.SUCCEEDED; case cancelled, failed -> Status200Response.StatusEnum.FAILED; diff --git a/src/main/java/dev/vality/disputes/api/model/PaymentParams.java b/src/main/java/dev/vality/disputes/api/model/PaymentParams.java index cafca86..ca9e3aa 100644 --- a/src/main/java/dev/vality/disputes/api/model/PaymentParams.java +++ b/src/main/java/dev/vality/disputes/api/model/PaymentParams.java @@ -1,15 +1,13 @@ package dev.vality.disputes.api.model; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; import lombok.ToString; import java.util.Map; @Builder -@Getter -@Setter +@Data public class PaymentParams { private String invoiceId; diff --git a/src/main/java/dev/vality/disputes/api/service/ApiAttachmentsService.java b/src/main/java/dev/vality/disputes/api/service/ApiAttachmentsService.java index 64c19f2..386c2f1 100644 --- a/src/main/java/dev/vality/disputes/api/service/ApiAttachmentsService.java +++ b/src/main/java/dev/vality/disputes/api/service/ApiAttachmentsService.java @@ -4,7 +4,6 @@ import dev.vality.disputes.dao.FileMetaDao; import dev.vality.disputes.domain.tables.pojos.FileMeta; import dev.vality.disputes.service.external.FileStorageService; import dev.vality.swag.disputes.model.CreateRequest; -import dev.vality.swag.disputes.model.CreateRequestAttachmentsInner; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -24,13 +23,13 @@ public class ApiAttachmentsService { @Transactional(propagation = Propagation.REQUIRED) public void createAttachments(CreateRequest req, Long disputeId) { log.debug("Trying to save Attachments {}", disputeId); - for (CreateRequestAttachmentsInner attachment : req.getAttachments()) { + for (var attachment : req.getAttachments()) { // validate MediaType.valueOf(attachment.getMimeType()); // http 500 var fileId = fileStorageService.saveFile(attachment.getData()); var fileMeta = new FileMeta(fileId, disputeId, attachment.getMimeType()); - log.debug("Trying to save Attachment {}", fileMeta.getFileId()); + log.debug("Trying to save Attachment {}", fileMeta); // http 500 fileMetaDao.save(fileMeta); } diff --git a/src/main/java/dev/vality/disputes/api/service/ApiDisputesService.java b/src/main/java/dev/vality/disputes/api/service/ApiDisputesService.java index 68c72a4..a200593 100644 --- a/src/main/java/dev/vality/disputes/api/service/ApiDisputesService.java +++ b/src/main/java/dev/vality/disputes/api/service/ApiDisputesService.java @@ -74,6 +74,7 @@ public class ApiDisputesService { DisputeStatus.pending, DisputeStatus.manual_created, DisputeStatus.manual_pending, - DisputeStatus.create_adjustment); + DisputeStatus.create_adjustment, + DisputeStatus.already_exist_created); } } diff --git a/src/main/java/dev/vality/disputes/api/service/PaymentParamsBuilder.java b/src/main/java/dev/vality/disputes/api/service/PaymentParamsBuilder.java index defedce..9cbf28b 100644 --- a/src/main/java/dev/vality/disputes/api/service/PaymentParamsBuilder.java +++ b/src/main/java/dev/vality/disputes/api/service/PaymentParamsBuilder.java @@ -5,20 +5,21 @@ import dev.vality.damsel.domain.TransactionInfo; import dev.vality.damsel.payment_processing.InvoicePayment; import dev.vality.disputes.api.model.PaymentParams; import dev.vality.disputes.security.AccessData; -import dev.vality.disputes.service.external.DominantService; +import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Optional; +import java.util.concurrent.CompletableFuture; @Slf4j @Service @RequiredArgsConstructor public class PaymentParamsBuilder { - private final DominantService dominantService; + private final DominantAsyncService dominantAsyncService; @SneakyThrows public PaymentParams buildGeneralPaymentContext(AccessData accessData) { @@ -26,28 +27,42 @@ public class PaymentParamsBuilder { log.debug("Start building PaymentParams id={}", invoice.getInvoice().getId()); var payment = accessData.getPayment(); // http 500 - var terminal = dominantService.getTerminal(payment.getRoute().getTerminal()); + var terminal = dominantAsyncService.getTerminal(payment.getRoute().getTerminal()); var currency = Optional.of(payment) .filter(p -> p.getPayment().isSetCost()) .map(p -> p.getPayment().getCost()) // http 500 - .map(cost -> dominantService.getCurrency(cost.getCurrency())); + .map(cost -> dominantAsyncService.getCurrency(cost.getCurrency())); var paymentParams = PaymentParams.builder() .invoiceId(invoice.getInvoice().getId()) .paymentId(payment.getPayment().getId()) .terminalId(payment.getRoute().getTerminal().getId()) .providerId(payment.getRoute().getProvider().getId()) .providerTrxId(getProviderTrxId(payment)) - .currencyName(currency.map(Currency::getName).orElse(null)) - .currencySymbolicCode(currency.map(Currency::getSymbolicCode).orElse(null)) - .currencyNumericCode(currency.map(Currency::getNumericCode).map(Short::intValue).orElse(null)) - .currencyExponent(currency.map(Currency::getExponent).map(Short::intValue).orElse(null)) + .currencyName(getCurrency(currency) + .map(Currency::getName).orElse(null)) + .currencySymbolicCode(getCurrency(currency) + .map(Currency::getSymbolicCode).orElse(null)) + .currencyNumericCode(getCurrency(currency) + .map(Currency::getNumericCode).map(Short::intValue).orElse(null)) + .currencyExponent(getCurrency(currency) + .map(Currency::getExponent).map(Short::intValue).orElse(null)) .options(terminal.get().getOptions()) .build(); log.debug("Finish building PaymentParams {}", paymentParams); return paymentParams; } + private Optional getCurrency(Optional> currency) { + return currency.map(currencyCompletableFuture -> { + try { + return currencyCompletableFuture.get(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } + private String getProviderTrxId(InvoicePayment payment) { return Optional.ofNullable(payment.getLastTransactionInfo()) .map(TransactionInfo::getId) diff --git a/src/main/java/dev/vality/disputes/config/ApplicationConfig.java b/src/main/java/dev/vality/disputes/config/ApplicationConfig.java index 46417f9..c01b3b1 100644 --- a/src/main/java/dev/vality/disputes/config/ApplicationConfig.java +++ b/src/main/java/dev/vality/disputes/config/ApplicationConfig.java @@ -7,8 +7,6 @@ import dev.vality.damsel.payment_processing.InvoicingSrv; import dev.vality.file.storage.FileStorageSrv; import dev.vality.token.keeper.TokenAuthenticatorSrv; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -71,11 +69,6 @@ public class ApplicationConfig { .build(FileStorageSrv.Iface.class); } - @Bean - public CloseableHttpClient httpClient() { - return HttpClients.createDefault(); - } - @Bean public ExecutorService disputesThreadPool(@Value("${dispute.batchSize}") int threadPoolSize) { final var threadFactory = new ThreadFactoryBuilder() diff --git a/src/main/java/dev/vality/disputes/config/AsyncMDCConfiguration.java b/src/main/java/dev/vality/disputes/config/AsyncMDCConfiguration.java index 34c4b86..4c767cf 100644 --- a/src/main/java/dev/vality/disputes/config/AsyncMDCConfiguration.java +++ b/src/main/java/dev/vality/disputes/config/AsyncMDCConfiguration.java @@ -11,11 +11,12 @@ import java.util.concurrent.Executor; @SuppressWarnings("AbbreviationAsWordInName") public class AsyncMDCConfiguration { - @Bean - public Executor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + @Bean("dominantAsyncServiceExecutor") + public Executor dominantAsyncServiceExecutor() { + var executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new MDCTaskDecorator()); executor.initialize(); + executor.setThreadNamePrefix("dominantAsyncService-thread-"); return executor; } } diff --git a/src/main/java/dev/vality/disputes/config/HttpClientConfig.java b/src/main/java/dev/vality/disputes/config/HttpClientConfig.java new file mode 100644 index 0000000..13a739c --- /dev/null +++ b/src/main/java/dev/vality/disputes/config/HttpClientConfig.java @@ -0,0 +1,73 @@ +package dev.vality.disputes.config; + +import dev.vality.disputes.config.properties.HttpClientProperties; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.util.Timeout; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class HttpClientConfig { + + private final HttpClientProperties httpClientProperties; + + @Bean + public CloseableHttpClient httpClient( + PoolingHttpClientConnectionManager manager, + RequestConfig requestConfig) { + return HttpClients.custom() + .setConnectionManager(manager) + .setDefaultRequestConfig(requestConfig) + .disableAutomaticRetries() + .setConnectionManagerShared(true) + .build(); + } + + @Bean + public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() { + var connectionManager = new PoolingHttpClientConnectionManager(connectionSocketFactory()); + connectionManager.setMaxTotal(httpClientProperties.getMaxTotalPooling()); + connectionManager.setDefaultMaxPerRoute(httpClientProperties.getDefaultMaxPerRoute()); + connectionManager.setDefaultConnectionConfig(connectionConfig(httpClientProperties)); + return connectionManager; + } + + @Bean + public RequestConfig requestConfig() { + return RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofMilliseconds(httpClientProperties.getPoolTimeout())) + .build(); + } + + @SneakyThrows + private Registry connectionSocketFactory() { + var sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustAllStrategy()).build(); + var sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + return RegistryBuilder.create() + .register("https", sslConnectionSocketFactory) + .register("http", new PlainConnectionSocketFactory()) + .build(); + } + + private ConnectionConfig connectionConfig(HttpClientProperties httpClientProperties) { + return ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(httpClientProperties.getConnectionTimeout())) + .setSocketTimeout(Timeout.ofMilliseconds(httpClientProperties.getRequestTimeout())) + .build(); + } +} diff --git a/src/main/java/dev/vality/disputes/config/properties/HttpClientProperties.java b/src/main/java/dev/vality/disputes/config/properties/HttpClientProperties.java new file mode 100644 index 0000000..c55a6d8 --- /dev/null +++ b/src/main/java/dev/vality/disputes/config/properties/HttpClientProperties.java @@ -0,0 +1,28 @@ +package dev.vality.disputes.config.properties; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +@Getter +@Setter +@Validated +@Configuration +@ConfigurationProperties("http-client") +public class HttpClientProperties { + + @NotNull + private int maxTotalPooling; + @NotNull + private int defaultMaxPerRoute; + @NotNull + private int requestTimeout; + @NotNull + private int poolTimeout; + @NotNull + private int connectionTimeout; + +} diff --git a/src/main/java/dev/vality/disputes/dao/DisputeDao.java b/src/main/java/dev/vality/disputes/dao/DisputeDao.java index fa1a39e..a31f156 100644 --- a/src/main/java/dev/vality/disputes/dao/DisputeDao.java +++ b/src/main/java/dev/vality/disputes/dao/DisputeDao.java @@ -62,15 +62,18 @@ public class DisputeDao extends AbstractGenericDao { .orElse(List.of()); } - public Dispute getForUpdateSkipLocked(long disputeId) { + public Optional get(long disputeId) { + var query = getDslContext().selectFrom(DISPUTE) + .where(DISPUTE.ID.eq(disputeId)); + return Optional.ofNullable(fetchOne(query, disputeRowMapper)); + } + + public Optional getForUpdateSkipLocked(long disputeId) { var query = getDslContext().selectFrom(DISPUTE) .where(DISPUTE.ID.eq(disputeId)) .forUpdate() .skipLocked(); - return Optional.ofNullable(fetchOne(query, disputeRowMapper)) - .orElseThrow( - () -> new NotFoundException( - String.format("Dispute not found, disputeId='%s'", disputeId))); + return Optional.ofNullable(fetchOne(query, disputeRowMapper)); } public List getDisputesForUpdateSkipLocked(int limit, DisputeStatus disputeStatus) { diff --git a/src/main/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java b/src/main/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java new file mode 100644 index 0000000..e68e205 --- /dev/null +++ b/src/main/java/dev/vality/disputes/manualparsing/DebugManualParsingController.java @@ -0,0 +1,128 @@ +package dev.vality.disputes.manualparsing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import dev.vality.disputes.admin.*; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Base64; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping({"/debug/disputes-api/manual-parsing"}) +@Slf4j +public class DebugManualParsingController { + + private final ManualParsingServiceSrv.Iface manualParsingHandler; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @PostMapping("/cancel") + @SneakyThrows + public void cancelPending(@RequestBody String body) { + log.debug("cancelPending {}", body); + manualParsingHandler.cancelPending(objectMapper.readValue(body, CancelParamsRequest.class)); + } + + @PostMapping("/approve") + @SneakyThrows + public void approvePending(@RequestBody String body) { + log.debug("approvePending {}", body); + manualParsingHandler.approvePending(objectMapper.readValue(body, ApproveParamsRequest.class)); + } + + @PostMapping("/bind") + @SneakyThrows + public void bindCreated(@RequestBody String body) { + log.debug("bindCreated {}", body); + manualParsingHandler.bindCreated(objectMapper.readValue(body, BindParamsRequest.class)); + } + + @GetMapping("/get") + @SneakyThrows + public DisputeResult getDisputes(@RequestBody String body) { + log.debug("getDispute {}", body); + var dispute = manualParsingHandler.getDispute(objectMapper.readValue(body, DisputeParamsRequest.class)); + return objectMapper.convertValue(dispute, new TypeReference<>() { + }); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DisputeResult { + + @JsonProperty("disputes") + private List disputes; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Dispute { + + @JsonProperty("disputeId") + private String disputeId; // required + @JsonProperty("providerDisputeId") + private String providerDisputeId; // optional + @JsonProperty("invoiceId") + private String invoiceId; // required + @JsonProperty("paymentId") + private String paymentId; // required + @JsonProperty("providerTrxId") + private String providerTrxId; // required + @JsonProperty("status") + private String status; // required + @JsonProperty("errorMessage") + private String errorMessage; // optional + @JsonProperty("amount") + private String amount; // required + @JsonProperty("changedAmount") + private String changedAmount; // optional + @JsonProperty("skipCallHgForCreateAdjustment") + private boolean skipCallHgForCreateAdjustment; // required + @JsonProperty("attachments") + @JsonDeserialize(using = AttachmentDeserializer.class) + public List attachments; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Attachment { + + private String data; + + } + + public static class AttachmentDeserializer extends JsonDeserializer { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public Attachment deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { + var node = (JsonNode) parser.getCodec().readTree(parser); + if (node.isObject()) { + var attachment = mapper.convertValue(node, new TypeReference() { + }); + var attachmentResult = new Attachment(); + attachmentResult.setData(Base64.getEncoder().encodeToString(attachment.getData())); + return attachmentResult; + } + return null; + } + } +} diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java index 7421072..fd90a7f 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java +++ b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingDisputesService.java @@ -1,19 +1,30 @@ package dev.vality.disputes.manualparsing; -import dev.vality.disputes.admin.ApproveParams; -import dev.vality.disputes.admin.BindParams; -import dev.vality.disputes.admin.CancelParams; +import dev.vality.disputes.admin.*; import dev.vality.disputes.dao.DisputeDao; +import dev.vality.disputes.dao.FileMetaDao; import dev.vality.disputes.dao.ProviderDisputeDao; import dev.vality.disputes.domain.enums.DisputeStatus; import dev.vality.disputes.domain.tables.pojos.ProviderDispute; +import dev.vality.disputes.service.external.FileStorageService; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Optional; + import static dev.vality.disputes.api.service.ApiDisputesService.DISPUTE_PENDING; @Slf4j @@ -24,13 +35,20 @@ public class ManualParsingDisputesService { private final DisputeDao disputeDao; private final ProviderDisputeDao providerDisputeDao; + private final FileMetaDao fileMetaDao; + private final FileStorageService fileStorageService; + private final CloseableHttpClient httpClient; @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public void cancelPendingDispute(CancelParams cancelParams) { var disputeId = cancelParams.getDisputeId(); - var cancelReason = cancelParams.getCancelReason().orElse(null); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var dispute = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + if (disputeOptional.isEmpty()) { + return; + } + var cancelReason = cancelParams.getCancelReason().orElse(null); + var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); if (DISPUTE_PENDING.contains(dispute.getStatus())) { // используется не failed, а cancelled чтоб можно было понять, что зафейлен по внешнему вызову @@ -46,7 +64,11 @@ public class ManualParsingDisputesService { public void approvePendingDispute(ApproveParams approveParam) { var disputeId = approveParam.getDisputeId(); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var dispute = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + if (disputeOptional.isEmpty()) { + return; + } + var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); var skipCallHg = approveParam.isSkipCallHgForCreateAdjustment(); var targetStatus = skipCallHg ? DisputeStatus.succeeded : DisputeStatus.create_adjustment; @@ -70,19 +92,73 @@ public class ManualParsingDisputesService { @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public void bindCreatedDispute(BindParams bindParam) { var disputeId = bindParam.getDisputeId(); - var providerDisputeId = bindParam.getProviderDisputeId(); log.debug("Trying to getForUpdateSkipLocked {}", disputeId); - var dispute = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + var disputeOptional = disputeDao.getForUpdateSkipLocked(Long.parseLong(disputeId)); + if (disputeOptional.isEmpty()) { + return; + } + var providerDisputeId = bindParam.getProviderDisputeId(); + var dispute = disputeOptional.get(); log.debug("GetForUpdateSkipLocked has been found {}", dispute); if (dispute.getStatus() == DisputeStatus.manual_created) { // обрабатываем здесь только вручную созданные диспуты, у остальных предполагается, // что providerDisputeId будет сохранен после создания диспута по API провайдера - log.info("Trying to set manual_parsing_binded_pending Dispute status {}", dispute); + log.info("Trying to set manual_pending Dispute status {}", dispute); providerDisputeDao.save(new ProviderDispute(providerDisputeId, dispute.getId())); disputeDao.update(dispute.getId(), DisputeStatus.manual_pending); - log.debug("Dispute status has been set to manual_parsing_binded_pending {}", dispute); + log.debug("Dispute status has been set to manual_pending {}", dispute); + } else if (dispute.getStatus() == DisputeStatus.already_exist_created) { + log.info("Trying to set pending Dispute status {}", dispute); + providerDisputeDao.save(new ProviderDispute(providerDisputeId, dispute.getId())); + disputeDao.update(dispute.getId(), DisputeStatus.pending); + log.debug("Dispute status has been set to pending {}", dispute); } else { log.info("Request was skipped by inappropriate status {}", dispute); } } + + @SneakyThrows + public Dispute getDispute(DisputeParams disputeParams) { + var disputeId = disputeParams.getDisputeId(); + var disputeOptional = disputeDao.get(Long.parseLong(disputeId)); + if (disputeOptional.isEmpty()) { + log.debug("Trying to get Dispute but null {}", disputeId); + return null; + } + var dispute = disputeOptional.get(); + var disputeResult = new Dispute(); + disputeResult.setDisputeId(disputeId); + disputeResult.setProviderDisputeId(Optional.ofNullable(providerDisputeDao.get(dispute.getId())) + .map(ProviderDispute::getProviderDisputeId) + .orElse(null)); + disputeResult.setInvoiceId(dispute.getInvoiceId()); + disputeResult.setPaymentId(dispute.getPaymentId()); + disputeResult.setProviderTrxId(dispute.getProviderTrxId()); + disputeResult.setStatus(dispute.getStatus().name()); + disputeResult.setErrorMessage(dispute.getErrorMessage()); + disputeResult.setAmount(String.valueOf(dispute.getAmount())); + disputeResult.setChangedAmount(Optional.ofNullable(dispute.getChangedAmount()) + .map(String::valueOf) + .orElse(null)); + disputeResult.setSkipCallHgForCreateAdjustment(dispute.getSkipCallHgForCreateAdjustment()); + log.debug("Dispute getDispute {}", disputeResult); + var disputeFiles = fileMetaDao.getDisputeFiles(dispute.getId()); + if (disputeFiles == null) { + return disputeResult; + } + disputeResult.setAttachments(new ArrayList<>()); + for (var disputeFile : disputeFiles) { + var downloadUrl = fileStorageService.generateDownloadUrl(disputeFile.getFileId()); + var data = httpClient.execute( + new HttpGet(downloadUrl), + new AbstractHttpClientResponseHandler() { + @Override + public byte[] handleEntity(HttpEntity entity) throws IOException { + return EntityUtils.toByteArray(entity); + } + }); + disputeResult.getAttachments().get().add(new Attachment(ByteBuffer.wrap(data))); + } + return disputeResult; + } } diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java index c6d58a5..36f704e 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java +++ b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingHandler.java @@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.thrift.TException; import org.springframework.stereotype.Service; +import java.util.ArrayList; + @Service @RequiredArgsConstructor @Slf4j @@ -30,8 +32,20 @@ public class ManualParsingHandler implements ManualParsingServiceSrv.Iface { @Override public void bindCreated(BindParamsRequest bindParamsRequest) throws TException { - for (BindParams bindParam : bindParamsRequest.getBindParams()) { + for (var bindParam : bindParamsRequest.getBindParams()) { manualParsingDisputesService.bindCreatedDispute(bindParam); } } + + @Override + public DisputeResult getDispute(DisputeParamsRequest disputeParamsRequest) throws TException { + var disputeResult = new DisputeResult(new ArrayList<>()); + for (var disputeParams : disputeParamsRequest.getDisputeParams()) { + var dispute = manualParsingDisputesService.getDispute(disputeParams); + if (dispute != null) { + disputeResult.getDisputes().add(dispute); + } + } + return disputeResult; + } } diff --git a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java index b17b313..19de97f 100644 --- a/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java +++ b/src/main/java/dev/vality/disputes/manualparsing/ManualParsingTopic.java @@ -8,8 +8,6 @@ import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @@ -23,8 +21,7 @@ public class ManualParsingTopic { @Value("${manual-parsing-topic.enabled}") private boolean enabled; - @Transactional(propagation = Propagation.REQUIRED) - public void sendCreated(Dispute dispute, List attachments) { + public void sendCreated(Dispute dispute, List attachments, DisputeStatus disputeStatus) { if (!enabled) { return; } @@ -32,7 +29,7 @@ public class ManualParsingTopic { 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.manual_created.name()); + contextMap.put("dispute_status", disputeStatus.name()); MDC.setContextMap(contextMap); log.warn("Manual parsing case"); MDC.clear(); diff --git a/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java b/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java index 8bea930..ecb52da 100644 --- a/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java +++ b/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java @@ -6,16 +6,12 @@ import dev.vality.damsel.domain.Terminal; import dev.vality.damsel.domain.TerminalRef; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.service.external.DominantService; -import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Map; -import java.util.concurrent.CompletableFuture; @Service public class ExponentialBackOffPollingServiceWrapper { @@ -33,20 +29,17 @@ public class ExponentialBackOffPollingServiceWrapper { return getLocalDateTime(pollingInfo.getStartDateTimePolling().plusSeconds(seconds)); } - @Transactional(propagation = Propagation.REQUIRED) - @SneakyThrows public LocalDateTime prepareNextPollingInterval(Dispute dispute) { var pollingInfo = new PollingInfo(); var startDateTimePolling = dispute.getCreatedAt().toInstant(ZoneOffset.UTC); pollingInfo.setStartDateTimePolling(startDateTimePolling); pollingInfo.setMaxDateTimePolling(dispute.getPollingBefore().toInstant(ZoneOffset.UTC)); var terminal = getTerminal(dispute.getTerminalId()); - var seconds = exponentialBackOffPollingService.prepareNextPollingInterval( - pollingInfo, terminal.get().getOptions()); + var seconds = exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, terminal.getOptions()); return getLocalDateTime(startDateTimePolling.plusSeconds(seconds)); } - private CompletableFuture getTerminal(Integer terminalId) { + private Terminal getTerminal(Integer terminalId) { return dominantService.getTerminal(new TerminalRef(terminalId)); } diff --git a/src/main/java/dev/vality/disputes/schedule/TaskCreateAdjustmentsService.java b/src/main/java/dev/vality/disputes/schedule/TaskCreateAdjustmentsService.java index 73de3f7..633a2a6 100644 --- a/src/main/java/dev/vality/disputes/schedule/TaskCreateAdjustmentsService.java +++ b/src/main/java/dev/vality/disputes/schedule/TaskCreateAdjustmentsService.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength", "MissingSwitchDefault"}) public class TaskCreateAdjustmentsService { private final ExecutorService disputesThreadPool; @@ -25,12 +26,12 @@ public class TaskCreateAdjustmentsService { @Value("${dispute.isScheduleCreateAdjustmentsEnabled}") private boolean isScheduleCreateAdjustmentsEnabled; - @Scheduled(fixedDelayString = "${dispute.fixedDelayCreateAdjustments}") + @Scheduled(fixedDelayString = "${dispute.fixedDelayCreateAdjustments}", initialDelayString = "${dispute.initialDelayCreateAdjustments}") public void processPending() { if (!isScheduleCreateAdjustmentsEnabled) { return; } - log.info("Processing create adjustments get started"); + log.debug("Processing create adjustments get started"); try { var disputes = createAdjustmentsService.getDisputesForHgCall(batchSize); var callables = disputes.stream() @@ -40,7 +41,7 @@ public class TaskCreateAdjustmentsService { } catch (InterruptedException ex) { log.error("Received InterruptedException while thread executed report", ex); Thread.currentThread().interrupt(); - } catch (Exception ex) { + } catch (Throwable ex) { log.error("Received exception while scheduler processed create adjustments", ex); } log.info("Create adjustments were processed"); diff --git a/src/main/java/dev/vality/disputes/schedule/TaskCreatedDisputesService.java b/src/main/java/dev/vality/disputes/schedule/TaskCreatedDisputesService.java index 9760d7e..45351ce 100644 --- a/src/main/java/dev/vality/disputes/schedule/TaskCreatedDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/TaskCreatedDisputesService.java @@ -25,12 +25,12 @@ public class TaskCreatedDisputesService { @Value("${dispute.isScheduleCreatedEnabled}") private boolean isScheduleCreatedEnabled; - @Scheduled(fixedDelayString = "${dispute.fixedDelayCreated}") + @Scheduled(fixedDelayString = "${dispute.fixedDelayCreated}", initialDelayString = "${dispute.initialDelayCreated}") public void processCreated() { if (!isScheduleCreatedEnabled) { return; } - log.info("Processing created disputes get started"); + log.debug("Processing created disputes get started"); try { var disputes = createdDisputesService.getCreatedDisputesForUpdateSkipLocked(batchSize); var callables = disputes.stream() @@ -40,7 +40,7 @@ public class TaskCreatedDisputesService { } catch (InterruptedException ex) { log.error("Received InterruptedException while thread executed report", ex); Thread.currentThread().interrupt(); - } catch (Exception ex) { + } catch (Throwable ex) { log.error("Received exception while scheduler processed created disputes", ex); } log.info("Created disputes were processed"); diff --git a/src/main/java/dev/vality/disputes/schedule/TaskPendingDisputesService.java b/src/main/java/dev/vality/disputes/schedule/TaskPendingDisputesService.java index f43674f..0042f17 100644 --- a/src/main/java/dev/vality/disputes/schedule/TaskPendingDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/TaskPendingDisputesService.java @@ -25,12 +25,12 @@ public class TaskPendingDisputesService { @Value("${dispute.isSchedulePendingEnabled}") private boolean isSchedulePendingEnabled; - @Scheduled(fixedDelayString = "${dispute.fixedDelayPending}") + @Scheduled(fixedDelayString = "${dispute.fixedDelayPending}", initialDelayString = "${dispute.initialDelayPending}") public void processPending() { if (!isSchedulePendingEnabled) { return; } - log.info("Processing pending disputes get started"); + log.debug("Processing pending disputes get started"); try { var disputes = pendingDisputesService.getPendingDisputesForUpdateSkipLocked(batchSize); var callables = disputes.stream() @@ -40,7 +40,7 @@ public class TaskPendingDisputesService { } catch (InterruptedException ex) { log.error("Received InterruptedException while thread executed report", ex); Thread.currentThread().interrupt(); - } catch (Exception ex) { + } catch (Throwable ex) { log.error("Received exception while scheduler processed pending disputes", ex); } log.info("Pending disputes were processed"); diff --git a/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java b/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java index 1bebbf2..fa998a4 100644 --- a/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java +++ b/src/main/java/dev/vality/disputes/schedule/client/RemoteClient.java @@ -17,11 +17,8 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.concurrent.CompletableFuture; @Slf4j @Service @@ -34,37 +31,42 @@ public class RemoteClient { private final DisputeContextConverter disputeContextConverter; private final DisputeParamsConverter disputeParamsConverter; - @Transactional(propagation = Propagation.REQUIRED) @SneakyThrows public DisputeCreatedResult createDispute(Dispute dispute, List attachments) { + log.debug("Trying to call dominant for RemoteClient {}", dispute.getId()); var terminal = getTerminal(dispute.getTerminalId()); var proxy = getProxy(dispute.getProviderId()); - var disputeParams = disputeParamsConverter.convert(dispute, attachments, terminal.get().getOptions()); - var remoteClient = providerIfaceBuilder.build(terminal.get().getOptions(), proxy.get().getUrl()); - log.info("Trying to routed remote provider's createDispute() call {}", dispute); + log.debug("Trying to build disputeParams {}", dispute.getId()); + var disputeParams = disputeParamsConverter.convert(dispute, attachments, terminal.getOptions()); + log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); + var remoteClient = providerIfaceBuilder.buildTHSpawnClient(terminal.getOptions(), proxy.getUrl()); + log.debug("Trying to routed remote provider's createDispute() call {}", dispute.getId()); var result = remoteClient.createDispute(disputeParams); - log.debug("Routed remote provider's createDispute() has been called {}", dispute); + log.info("Routed remote provider's createDispute() has been called {} {}", dispute.getId(), result); return result; } - @Transactional(propagation = Propagation.REQUIRED) @SneakyThrows public DisputeStatusResult checkDisputeStatus(Dispute dispute, ProviderDispute providerDispute) { + log.debug("Trying to call dominant for RemoteClient {}", dispute.getId()); var terminal = getTerminal(dispute.getTerminalId()); var proxy = getProxy(dispute.getProviderId()); - var disputeContext = disputeContextConverter.convert(dispute, providerDispute, terminal.get().getOptions()); - var remoteClient = providerIfaceBuilder.build(terminal.get().getOptions(), proxy.get().getUrl()); - log.info("Trying to routed remote provider's checkDisputeStatus() call {}", dispute); + log.debug("Trying to build disputeContext {}", dispute.getId()); + var disputeContext = disputeContextConverter.convert(dispute, providerDispute, terminal.getOptions()); + log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId()); + var remoteClient = providerIfaceBuilder.buildTHSpawnClient(terminal.getOptions(), proxy.getUrl()); + log.debug("Trying to routed remote provider's checkDisputeStatus() call {}", dispute.getId()); var result = remoteClient.checkDisputeStatus(disputeContext); - log.debug("Routed remote provider's checkDisputeStatus() has been called {}", dispute); + log.info("Routed remote provider's checkDisputeStatus() has been called {} {}", dispute.getId(), result); return result; } - private CompletableFuture getProxy(Integer providerId) { - return dominantService.getProxy(new ProviderRef(providerId)); + private ProxyDefinition getProxy(Integer providerId) { + var provider = dominantService.getProvider(new ProviderRef(providerId)); + return dominantService.getProxy(provider.getProxy().getRef()); } - private CompletableFuture getTerminal(Integer terminalId) { + private Terminal getTerminal(Integer terminalId) { return dominantService.getTerminal(new TerminalRef(terminalId)); } } diff --git a/src/main/java/dev/vality/disputes/schedule/handler/CreateAdjustmentHandler.java b/src/main/java/dev/vality/disputes/schedule/handler/CreateAdjustmentHandler.java index 84842c2..15c3095 100644 --- a/src/main/java/dev/vality/disputes/schedule/handler/CreateAdjustmentHandler.java +++ b/src/main/java/dev/vality/disputes/schedule/handler/CreateAdjustmentHandler.java @@ -3,8 +3,10 @@ package dev.vality.disputes.schedule.handler; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.schedule.service.CreateAdjustmentsService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor +@Slf4j public class CreateAdjustmentHandler { private final CreateAdjustmentsService createAdjustmentsService; @@ -12,10 +14,13 @@ public class CreateAdjustmentHandler { public Long handle(Dispute dispute) { final var currentThread = Thread.currentThread(); final var oldName = currentThread.getName(); - currentThread.setName("dispute-create-adjustment-" + dispute.getId()); + currentThread.setName("dispute-created-adjustment-id-" + dispute.getId() + "-" + oldName); try { createAdjustmentsService.callHgForCreateAdjustment(dispute); return dispute.getId(); + } catch (Throwable ex) { + log.error("Received exception while scheduler processed callHgForCreateAdjustment", ex); + throw ex; } finally { currentThread.setName(oldName); } diff --git a/src/main/java/dev/vality/disputes/schedule/handler/CreatedDisputeHandler.java b/src/main/java/dev/vality/disputes/schedule/handler/CreatedDisputeHandler.java index 2538d44..4f185a0 100644 --- a/src/main/java/dev/vality/disputes/schedule/handler/CreatedDisputeHandler.java +++ b/src/main/java/dev/vality/disputes/schedule/handler/CreatedDisputeHandler.java @@ -3,8 +3,10 @@ package dev.vality.disputes.schedule.handler; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.schedule.service.CreatedDisputesService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor +@Slf4j public class CreatedDisputeHandler { private final CreatedDisputesService createdDisputesService; @@ -12,10 +14,13 @@ public class CreatedDisputeHandler { public Long handle(Dispute dispute) { final var currentThread = Thread.currentThread(); final var oldName = currentThread.getName(); - currentThread.setName("dispute-created-" + dispute.getId()); + currentThread.setName("dispute-created-id-" + dispute.getId() + "-" + oldName); try { createdDisputesService.callCreateDisputeRemotely(dispute); return dispute.getId(); + } catch (Throwable ex) { + log.error("Received exception while scheduler processed callCreateDisputeRemotely", ex); + throw ex; } finally { currentThread.setName(oldName); } diff --git a/src/main/java/dev/vality/disputes/schedule/handler/PendingDisputeHandler.java b/src/main/java/dev/vality/disputes/schedule/handler/PendingDisputeHandler.java index 4e76d37..dabd1c6 100644 --- a/src/main/java/dev/vality/disputes/schedule/handler/PendingDisputeHandler.java +++ b/src/main/java/dev/vality/disputes/schedule/handler/PendingDisputeHandler.java @@ -3,8 +3,10 @@ package dev.vality.disputes.schedule.handler; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.schedule.service.PendingDisputesService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor +@Slf4j public class PendingDisputeHandler { private final PendingDisputesService pendingDisputesService; @@ -12,10 +14,13 @@ public class PendingDisputeHandler { public Long handle(Dispute dispute) { final var currentThread = Thread.currentThread(); final var oldName = currentThread.getName(); - currentThread.setName("dispute-pending-" + dispute.getId()); + currentThread.setName("dispute-pending-id-" + dispute.getId() + "-" + oldName); try { pendingDisputesService.callPendingDisputeRemotely(dispute); return dispute.getId(); + } catch (Throwable ex) { + log.error("Received exception while scheduler processed callPendingDisputeRemotely", ex); + throw ex; } finally { currentThread.setName(oldName); } diff --git a/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java b/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java index 910f534..ab9a1ff 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/CreateAdjustmentsService.java @@ -49,9 +49,9 @@ public class CreateAdjustmentsService { log.debug("GetDisputeForUpdateSkipLocked has been found {}", dispute); var invoicePayment = getInvoicePayment(dispute); if (invoicePayment == null || !invoicePayment.isSetRoute()) { - log.error("Trying to set failed Dispute status with PAYMENT_NOT_FOUND error reason {}", dispute); + log.error("Trying to set failed Dispute status with PAYMENT_NOT_FOUND error reason {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.failed, ErrorReason.PAYMENT_NOT_FOUND); - log.debug("Dispute status has been set to failed {}", dispute); + log.debug("Dispute status has been set to failed {}", dispute.getId()); return; } var invoicePaymentAdjustment = adjustmentExtractor.searchAdjustmentByDispute(invoicePayment, dispute); @@ -59,16 +59,16 @@ public class CreateAdjustmentsService { var changedAmount = adjustmentExtractor.getChangedAmount(invoicePaymentAdjustment.get(), dispute.getChangedAmount()); log.info("Trying to set succeeded Dispute status {}", dispute); disputeDao.update(dispute.getId(), DisputeStatus.succeeded, changedAmount); - log.debug("Dispute status has been set to succeeded {}", dispute); + log.debug("Dispute status has been set to succeeded {}", dispute.getId()); return; } try { var params = invoicePaymentAdjustmentParamsConverter.convert(dispute); var paymentAdjustment = createAdjustment(dispute, params); if (paymentAdjustment == null) { - log.error("Trying to set failed Dispute status with INVOICE_NOT_FOUND error reason {}", dispute); + log.error("Trying to set failed Dispute status with INVOICE_NOT_FOUND error reason {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.failed, ErrorReason.INVOICE_NOT_FOUND); - log.debug("Dispute status has been set to failed {}", dispute); + log.debug("Dispute status has been set to failed {}", dispute.getId()); return; } } catch (InvoicingPaymentStatusPendingException e) { @@ -77,12 +77,12 @@ public class CreateAdjustmentsService { // и тогда диспут будет пулиться, пока платеж не зафиналится, // и тк никакой записи в коде выше нет, то пуллинг не проблема // а запрос в checkDisputeStatus по идемпотентности просто вернет тот же success - log.error("Error when hg.createPaymentAdjustment() got payments status pending {}", dispute, e); + log.error("Error when hg.createPaymentAdjustment() got payments status pending {}", dispute.getId(), e); return; } log.info("Trying to set succeeded Dispute status {}", dispute); disputeDao.update(dispute.getId(), DisputeStatus.succeeded); - log.debug("Dispute status has been set to succeeded {}", dispute); + log.debug("Dispute status has been set to succeeded {}", dispute.getId()); } private InvoicePaymentAdjustment createAdjustment(Dispute dispute, InvoicePaymentAdjustmentParams params) { diff --git a/src/main/java/dev/vality/disputes/schedule/service/CreatedAttachmentsService.java b/src/main/java/dev/vality/disputes/schedule/service/CreatedAttachmentsService.java index 2c2538b..f711762 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/CreatedAttachmentsService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/CreatedAttachmentsService.java @@ -9,8 +9,6 @@ import dev.vality.disputes.service.external.FileStorageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @@ -23,7 +21,6 @@ public class CreatedAttachmentsService { private final FileMetaDao fileMetaDao; private final FileStorageService fileStorageService; - @Transactional(propagation = Propagation.REQUIRED) public List getAttachments(Dispute dispute) { log.debug("Trying to get Attachments {}", dispute); try { diff --git a/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java b/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java index cb3bf95..bf2f219 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/CreatedDisputesService.java @@ -19,7 +19,6 @@ import dev.vality.disputes.service.external.InvoicingService; import dev.vality.geck.serializer.kit.tbase.TErrorUtil; import dev.vality.woody.api.flow.error.WRuntimeException; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; @@ -27,7 +26,6 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.concurrent.CompletableFuture; import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_CAPTURED_BLOCKED; import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_PROVIDERS_API_EXIST; @@ -57,7 +55,6 @@ public class CreatedDisputesService { } @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) - @SneakyThrows public void callCreateDisputeRemotely(Dispute dispute) { log.debug("Trying to getDisputeForUpdateSkipLocked {}", dispute); var forUpdate = disputeDao.getDisputeForUpdateSkipLocked(dispute.getId()); @@ -68,39 +65,39 @@ public class CreatedDisputesService { log.debug("GetDisputeForUpdateSkipLocked has been found {}", dispute); var invoicePayment = getInvoicePayment(dispute); if (invoicePayment == null || !invoicePayment.isSetRoute()) { - log.error("Trying to set failed Dispute status with PAYMENT_NOT_FOUND error reason {}", dispute); + log.error("Trying to set failed Dispute status with PAYMENT_NOT_FOUND error reason {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.failed, ErrorReason.PAYMENT_NOT_FOUND); - log.debug("Dispute status has been set to failed {}", dispute); + log.debug("Dispute status has been set to failed {}", dispute.getId()); return; } var status = invoicePayment.getPayment().getStatus(); if (!status.isSetCaptured() && !status.isSetCancelled() && !status.isSetFailed()) { // не создаем диспут, пока платеж не финален - log.warn("Payment has non-final status {} {}", status, dispute); + log.warn("Payment has non-final status {} {}", status, dispute.getId()); return; } var attachments = createdAttachmentsService.getAttachments(dispute); if (attachments == null || attachments.isEmpty()) { - log.error("Trying to set failed Dispute status with NO_ATTACHMENTS error reason {}", dispute); + log.error("Trying to set failed Dispute status with NO_ATTACHMENTS error reason {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.failed, ErrorReason.NO_ATTACHMENTS); - log.debug("Dispute status has been set to failed {}", dispute); + log.debug("Dispute status has been set to failed {}", dispute.getId()); return; } if ((status.isSetCaptured() && isCapturedBlockedForDispute(dispute)) || isNotProvidersDisputesApiExist(dispute)) { // отправлять на ручной разбор, если выставлена опция // DISPUTE_FLOW_CAPTURED_BLOCKED или не выставлена DISPUTE_FLOW_PROVIDERS_API_EXIST - finishTaskWithManualParsingFlowActivation(dispute, attachments); + finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); return; } try { var result = remoteClient.createDispute(dispute, attachments); - finishTask(dispute, result); + finishTask(dispute, attachments, result); } catch (WRuntimeException e) { if (externalGatewayChecker.isNotProvidersDisputesApiExist(dispute, e)) { // отправлять на ручной разбор, если API диспутов на провайдере не реализовано // (тогда при тесте соединения вернется 404) - finishTaskWithManualParsingFlowActivation(dispute, attachments); + finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created); return; } throw e; @@ -108,41 +105,41 @@ public class CreatedDisputesService { } @Transactional(propagation = Propagation.REQUIRED) - void finishTask(Dispute dispute, DisputeCreatedResult result) { + void finishTask(Dispute dispute, List attachments, DisputeCreatedResult result) { switch (result.getSetField()) { case SUCCESS_RESULT -> { var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute); log.info("Trying to set pending Dispute status {}, {}", dispute, result); providerDisputeDao.save(new ProviderDispute(result.getSuccessResult().getProviderDisputeId(), dispute.getId())); disputeDao.update(dispute.getId(), DisputeStatus.pending, nextCheckAfter); - log.debug("Dispute status has been set to pending {}", dispute); + log.debug("Dispute status has been set to pending {}", dispute.getId()); } case FAIL_RESULT -> { var errorMessage = TErrorUtil.toStringVal(result.getFailResult().getFailure()); - log.warn("Trying to set failed Dispute status {}, {}", dispute, errorMessage); + 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); + 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 attachments) { - manualParsingTopic.sendCreated(dispute, attachments); - log.info("Trying to set manual_parsing_created Dispute status {}", dispute); - disputeDao.update(dispute.getId(), DisputeStatus.manual_created); - log.debug("Dispute status has been set to manual_parsing_created {}", dispute); + void finishTaskWithManualParsingFlowActivation(Dispute dispute, List attachments, DisputeStatus disputeStatus) { + manualParsingTopic.sendCreated(dispute, attachments, disputeStatus); + log.info("Trying to set {} Dispute status {}", disputeStatus, dispute); + disputeDao.update(dispute.getId(), disputeStatus); + log.debug("Dispute status has been set to {} {}", disputeStatus, dispute.getId()); } - @SneakyThrows private boolean isCapturedBlockedForDispute(Dispute dispute) { - return getTerminal(dispute.getTerminalId()).get().getOptions() + return getTerminal(dispute.getTerminalId()).getOptions() .containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED); } - @SneakyThrows private boolean isNotProvidersDisputesApiExist(Dispute dispute) { - return !getTerminal(dispute.getTerminalId()).get().getOptions() + return !getTerminal(dispute.getTerminalId()).getOptions() .containsKey(DISPUTE_FLOW_PROVIDERS_API_EXIST); } @@ -150,7 +147,7 @@ public class CreatedDisputesService { return invoicingService.getInvoicePayment(dispute.getInvoiceId(), dispute.getPaymentId()); } - private CompletableFuture getTerminal(Integer terminalId) { + private Terminal getTerminal(Integer terminalId) { return dominantService.getTerminal(new TerminalRef(terminalId)); } } diff --git a/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java b/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java index 9b3fb83..3c5e348 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ExternalGatewayChecker.java @@ -18,8 +18,6 @@ import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.springframework.stereotype.Service; -import java.util.concurrent.CompletableFuture; - @Slf4j @Service @RequiredArgsConstructor @@ -43,20 +41,20 @@ public class ExternalGatewayChecker { return httpClient.execute(new HttpGet(getRouteUrl(dispute)), isNotFoundResponse()); } - @SneakyThrows private String getRouteUrl(Dispute dispute) { - return providerRouting.getRouteUrl(getTerminal(dispute.getTerminalId()).get().getOptions(), getProxy(dispute.getProviderId()).get().getUrl()); + return providerRouting.getRouteUrl(getTerminal(dispute.getTerminalId()).getOptions(), getProxy(dispute.getProviderId()).getUrl()); } private HttpClientResponseHandler isNotFoundResponse() { return response -> response.getCode() == HttpStatus.SC_NOT_FOUND; } - private CompletableFuture getTerminal(Integer terminalId) { + private Terminal getTerminal(Integer terminalId) { return dominantService.getTerminal(new TerminalRef(terminalId)); } - private CompletableFuture getProxy(Integer providerId) { - return dominantService.getProxy(new ProviderRef(providerId)); + private ProxyDefinition getProxy(Integer providerId) { + var provider = dominantService.getProvider(new ProviderRef(providerId)); + return dominantService.getProxy(provider.getProxy().getRef()); } } diff --git a/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java b/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java index 7d96c0c..9736446 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/service/PendingDisputesService.java @@ -48,23 +48,23 @@ public class PendingDisputesService { return; } log.debug("GetDisputeForUpdateSkipLocked has been found {}", dispute); - log.debug("Trying to get ProviderDispute {}", dispute); + log.debug("Trying to get ProviderDispute {}", dispute.getId()); var providerDispute = providerDisputeDao.get(dispute.getId()); if (providerDispute == null) { var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute); // вернуть в CreatedDisputeService и попробовать создать диспут в провайдере заново - log.error("Trying to set created Dispute status, because createDispute() was not success {}", dispute); + log.error("Trying to set created Dispute status, because createDispute() was not success {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.created, nextCheckAfter); - log.debug("Dispute status has been set to created {}", dispute); + log.debug("Dispute status has been set to created {}", dispute.getId()); return; } if (pollingInfoService.isDeadline(dispute)) { - log.error("Trying to set failed Dispute status with POOLING_EXPIRED error reason {}", dispute); + log.error("Trying to set failed Dispute status with POOLING_EXPIRED error reason {}", dispute.getId()); disputeDao.update(dispute.getId(), DisputeStatus.failed, ErrorReason.POOLING_EXPIRED); - log.debug("Dispute status has been set to failed {}", dispute); + log.debug("Dispute status has been set to failed {}", dispute.getId()); return; } - log.debug("ProviderDispute has been found {}", dispute); + log.debug("ProviderDispute has been found {}", dispute.getId()); var result = remoteClient.checkDisputeStatus(dispute, providerDispute); finishTask(dispute, result); } @@ -76,13 +76,13 @@ public class PendingDisputesService { var changedAmount = result.getStatusSuccess().getChangedAmount().orElse(null); log.info("Trying to set create_adjustment Dispute status {}, {}", dispute, result); disputeDao.update(dispute.getId(), DisputeStatus.create_adjustment, changedAmount); - log.debug("Dispute status has been set to create_adjustment {}", dispute); + log.debug("Dispute status has been set to create_adjustment {}", dispute.getId()); } case STATUS_FAIL -> { var errorMessage = TErrorUtil.toStringVal(result.getStatusFail().getFailure()); - log.warn("Trying to set failed Dispute status {}, {}", dispute, errorMessage); + 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); + log.debug("Dispute status has been set to failed {}", dispute.getId()); } case STATUS_PENDING -> { // дергаем update() чтоб обновить время вызова next_check_after, @@ -91,7 +91,7 @@ public class PendingDisputesService { var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute); log.info("Trying to set pending Dispute status {}, {}", dispute, result); disputeDao.update(dispute.getId(), DisputeStatus.pending, nextCheckAfter); - log.debug("Dispute status has been set to pending {}", dispute); + log.debug("Dispute status has been set to pending {}", dispute.getId()); } } } diff --git a/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java b/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java index d6e4ad6..752e9c3 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ProviderIfaceBuilder.java @@ -5,6 +5,7 @@ import dev.vality.disputes.config.properties.AdaptersConnectionProperties; import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.net.URI; @@ -14,20 +15,19 @@ import java.util.concurrent.TimeUnit; @Slf4j @Service @RequiredArgsConstructor +@SuppressWarnings({"AbbreviationAsWordInName", "LineLength"}) public class ProviderIfaceBuilder { private final ProviderRouting providerRouting; private final AdaptersConnectionProperties adaptersConnectionProperties; - public ProviderDisputesServiceSrv.Iface build(Map options, String url) { - return build(providerRouting.getRouteUrl(options, url)); - } - - private ProviderDisputesServiceSrv.Iface build(String url) { - log.info("Creating new client for url: {}", url); + @Cacheable(value = "adapters", key = "#root.args[1]", cacheManager = "adaptersCacheManager") + public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(Map options, String url) { + var routeUrl = providerRouting.getRouteUrl(options, url); + log.info("Creating new client for url: {}", routeUrl); return new THSpawnClientBuilder() .withNetworkTimeout((int) TimeUnit.SECONDS.toMillis(adaptersConnectionProperties.getTimeoutSec())) - .withAddress(URI.create(url)) + .withAddress(URI.create(routeUrl)) .build(ProviderDisputesServiceSrv.Iface.class); } } diff --git a/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java b/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java index 9e7f69f..e81d8e7 100644 --- a/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java +++ b/src/main/java/dev/vality/disputes/schedule/service/ProviderRouting.java @@ -3,12 +3,11 @@ package dev.vality.disputes.schedule.service; import dev.vality.disputes.exception.RoutingException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import org.springframework.web.util.UriComponentsBuilder; -import java.net.URL; +import java.net.URI; import java.util.Map; @Slf4j @@ -19,7 +18,6 @@ public class ProviderRouting { private static final String DISPUTES_URL_POSTFIX_DEFAULT = "disputes"; private static final String OPTION_DISPUTES_URL_FIELD_NAME = "disputes_url"; - @Cacheable(value = "adapters", key = "defaultProviderUrl", cacheManager = "adaptersCacheManager") public String getRouteUrl(Map options, String defaultProviderUrl) { var url = options.get(OPTION_DISPUTES_URL_FIELD_NAME); if (ObjectUtils.isEmpty(url)) { @@ -31,8 +29,7 @@ public class ProviderRouting { private String createDefaultRouteUrl(String defaultProviderUrl) { log.debug("Creating url by appending postfix"); try { - var validUri = new URL(defaultProviderUrl).toURI(); - return UriComponentsBuilder.fromUri(validUri) + return UriComponentsBuilder.fromUri(URI.create(defaultProviderUrl)) .pathSegment(DISPUTES_URL_POSTFIX_DEFAULT) .encode() .build() diff --git a/src/main/java/dev/vality/disputes/security/AccessService.java b/src/main/java/dev/vality/disputes/security/AccessService.java index d874bfa..d0f6e62 100644 --- a/src/main/java/dev/vality/disputes/security/AccessService.java +++ b/src/main/java/dev/vality/disputes/security/AccessService.java @@ -90,7 +90,7 @@ public class AccessService { } private InvoicePayment getInvoicePayment(dev.vality.damsel.payment_processing.Invoice invoice, String paymentId) { - log.debug("Processing invoice: {}", invoice); + log.debug("Processing invoice: {}", invoice.getInvoice().getId()); return invoice.getPayments().stream() .filter(invoicePayment -> paymentId.equals(invoicePayment.getPayment().getId()) && invoicePayment.isSetRoute()) diff --git a/src/main/java/dev/vality/disputes/security/service/impl/BouncerServiceImpl.java b/src/main/java/dev/vality/disputes/security/service/impl/BouncerServiceImpl.java index 3db3de5..1bd4c69 100644 --- a/src/main/java/dev/vality/disputes/security/service/impl/BouncerServiceImpl.java +++ b/src/main/java/dev/vality/disputes/security/service/impl/BouncerServiceImpl.java @@ -15,6 +15,7 @@ import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor +@SuppressWarnings({"ParameterName", "LineLength"}) public class BouncerServiceImpl implements BouncerService { private final BouncerProperties bouncerProperties; @@ -23,9 +24,9 @@ public class BouncerServiceImpl implements BouncerService { @Override public Resolution getResolution(AccessData accessData) { - log.debug("Check access with bouncer context: {}", accessData); + log.debug("Check access with bouncer context: {}{}", accessData.getInvoice().getInvoice().getId(), accessData.getPayment().getPayment().getId()); var context = bouncerContextFactory.buildContext(accessData); - log.debug("Built thrift context: {}", context); + log.debug("Built thrift context: {}{}", accessData.getInvoice().getInvoice().getId(), accessData.getPayment().getPayment().getId()); try { var judge = bouncerClient.judge(bouncerProperties.getRuleSetId(), context); log.debug("Have judge: {}", judge); diff --git a/src/main/java/dev/vality/disputes/service/external/DominantService.java b/src/main/java/dev/vality/disputes/service/external/DominantService.java index ab998f3..7495af6 100644 --- a/src/main/java/dev/vality/disputes/service/external/DominantService.java +++ b/src/main/java/dev/vality/disputes/service/external/DominantService.java @@ -2,14 +2,14 @@ package dev.vality.disputes.service.external; import dev.vality.damsel.domain.*; -import java.util.concurrent.CompletableFuture; - public interface DominantService { Currency getCurrency(CurrencyRef currencyRef); - CompletableFuture getTerminal(TerminalRef terminalRef); + Terminal getTerminal(TerminalRef terminalRef); - CompletableFuture getProxy(ProviderRef providerRef); + ProxyDefinition getProxy(ProxyRef proxyRef); + + Provider getProvider(ProviderRef providerRef); } diff --git a/src/main/java/dev/vality/disputes/service/external/impl/DominantServiceImpl.java b/src/main/java/dev/vality/disputes/service/external/impl/DominantServiceImpl.java index 23796d1..905fefc 100644 --- a/src/main/java/dev/vality/disputes/service/external/impl/DominantServiceImpl.java +++ b/src/main/java/dev/vality/disputes/service/external/impl/DominantServiceImpl.java @@ -6,11 +6,8 @@ import dev.vality.disputes.service.external.DominantService; import dev.vality.disputes.service.external.impl.dominant.DominantCacheServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import java.util.concurrent.CompletableFuture; - @Slf4j @Service @RequiredArgsConstructor @@ -23,26 +20,18 @@ public class DominantServiceImpl implements DominantService { return dominantCacheService.getCurrency(currencyRef); } - @Async @Override - public CompletableFuture getTerminal(TerminalRef terminalRef) { - try { - var terminal = dominantCacheService.getTerminal(terminalRef); - return CompletableFuture.completedFuture(terminal); - } catch (Exception e) { - return CompletableFuture.failedFuture(e); - } + public Terminal getTerminal(TerminalRef terminalRef) { + return dominantCacheService.getTerminal(terminalRef); } - @Async @Override - public CompletableFuture getProxy(ProviderRef providerRef) { - try { - var provider = dominantCacheService.getProvider(providerRef); - var proxy = dominantCacheService.getProxy(provider.getProxy().getRef()); - return CompletableFuture.completedFuture(proxy); - } catch (Exception e) { - return CompletableFuture.failedFuture(e); - } + public ProxyDefinition getProxy(ProxyRef proxyRef) { + return dominantCacheService.getProxy(proxyRef); + } + + @Override + public Provider getProvider(ProviderRef providerRef) { + return dominantCacheService.getProvider(providerRef); } } diff --git a/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantAsyncService.java b/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantAsyncService.java new file mode 100644 index 0000000..cc6941d --- /dev/null +++ b/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantAsyncService.java @@ -0,0 +1,41 @@ +package dev.vality.disputes.service.external.impl.dominant; + +import dev.vality.damsel.domain.Currency; +import dev.vality.damsel.domain.CurrencyRef; +import dev.vality.damsel.domain.Terminal; +import dev.vality.damsel.domain.TerminalRef; +import dev.vality.disputes.service.external.DominantService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DominantAsyncService { + + private final DominantService dominantService; + + @Async("dominantAsyncServiceExecutor") + public CompletableFuture getCurrency(CurrencyRef currencyRef) { + try { + var currency = dominantService.getCurrency(currencyRef); + return CompletableFuture.completedFuture(currency); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Async("dominantAsyncServiceExecutor") + public CompletableFuture getTerminal(TerminalRef terminalRef) { + try { + var terminal = dominantService.getTerminal(terminalRef); + return CompletableFuture.completedFuture(terminal); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } +} diff --git a/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantCacheServiceImpl.java b/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantCacheServiceImpl.java index 36abb8c..63fb49e 100644 --- a/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantCacheServiceImpl.java +++ b/src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantCacheServiceImpl.java @@ -19,20 +19,19 @@ public class DominantCacheServiceImpl { private final RepositoryClientSrv.Iface dominantClient; @Cacheable(value = "currencies", key = "#currencyRef.symbolic_code", cacheManager = "currenciesCacheManager") - public Currency getCurrency(CurrencyRef currencyRef) throws NotFoundException { + public Currency getCurrency(CurrencyRef currencyRef) { return getCurrency(currencyRef, Reference.head(new Head())); } - private Currency getCurrency(CurrencyRef currencyRef, Reference revisionReference) - throws NotFoundException { + private Currency getCurrency(CurrencyRef currencyRef, Reference revisionReference) { log.debug("Trying to get currency, currencyRef='{}', revisionReference='{}'", currencyRef, revisionReference); try { var reference = new dev.vality.damsel.domain.Reference(); reference.setCurrency(currencyRef); var versionedObject = checkoutObject(revisionReference, reference); var currency = versionedObject.getObject().getCurrency().getData(); - log.debug("Currency has been found, currencyRef='{}', revisionReference='{}', currency='{}'", - currencyRef, revisionReference, currency); + log.debug("Currency has been found, currencyRef='{}', revisionReference='{}'", + currencyRef, revisionReference); return currency; } catch (VersionNotFound | ObjectNotFound ex) { throw new NotFoundException(String.format("Version not found, currencyRef='%s', revisionReference='%s'", @@ -48,8 +47,7 @@ public class DominantCacheServiceImpl { return getTerminal(terminalRef, Reference.head(new Head())); } - public Terminal getTerminal(TerminalRef terminalRef, Reference revisionReference) - throws NotFoundException { + public Terminal getTerminal(TerminalRef terminalRef, Reference revisionReference) { log.debug("Trying to get terminal from dominant, terminalRef='{}', revisionReference='{}'", terminalRef, revisionReference); try { @@ -57,8 +55,8 @@ public class DominantCacheServiceImpl { reference.setTerminal(terminalRef); var versionedObject = checkoutObject(revisionReference, reference); var terminal = versionedObject.getObject().getTerminal().getData(); - log.debug("Terminal has been found, terminalRef='{}', revisionReference='{}', terminal='{}'", - terminalRef, revisionReference, terminal); + log.debug("Terminal has been found, terminalRef='{}', revisionReference='{}'", + terminalRef, revisionReference); return terminal; } catch (VersionNotFound | ObjectNotFound ex) { throw new NotFoundException(String.format("Version not found, terminalRef='%s', revisionReference='%s'", @@ -74,8 +72,7 @@ public class DominantCacheServiceImpl { return getProvider(providerRef, Reference.head(new Head())); } - private Provider getProvider(ProviderRef providerRef, Reference revisionReference) - throws NotFoundException { + private Provider getProvider(ProviderRef providerRef, Reference revisionReference) { log.debug("Trying to get provider from dominant, providerRef='{}', revisionReference='{}'", providerRef, revisionReference); try { @@ -83,8 +80,8 @@ public class DominantCacheServiceImpl { reference.setProvider(providerRef); var versionedObject = checkoutObject(revisionReference, reference); var provider = versionedObject.getObject().getProvider().getData(); - log.debug("Provider has been found, providerRef='{}', revisionReference='{}', terminal='{}'", - providerRef, revisionReference, provider); + log.debug("Provider has been found, providerRef='{}', revisionReference='{}'", + providerRef, revisionReference); return provider; } catch (VersionNotFound | ObjectNotFound ex) { throw new NotFoundException(String.format("Version not found, providerRef='%s', revisionReference='%s'", @@ -101,8 +98,7 @@ public class DominantCacheServiceImpl { } - private ProxyDefinition getProxy(ProxyRef proxyRef, Reference revisionReference) - throws NotFoundException { + private ProxyDefinition getProxy(ProxyRef proxyRef, Reference revisionReference) { log.debug("Trying to get proxy from dominant, proxyRef='{}', revisionReference='{}'", proxyRef, revisionReference); try { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bdce340..c8cde87 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -91,6 +91,9 @@ dispute: fixedDelayCreated: 5000 fixedDelayPending: 5000 fixedDelayCreateAdjustments: 5000 + initialDelayCreated: 5000 + initialDelayPending: 5000 + initialDelayCreateAdjustments: 5000 isScheduleCreatedEnabled: true isSchedulePendingEnabled: true isScheduleCreateAdjustmentsEnabled: true @@ -105,3 +108,10 @@ manual-parsing-topic: testcontainers: postgresql: tag: '11.4' + +http-client: + requestTimeout: 60000 + poolTimeout: 10000 + connectionTimeout: 10000 + maxTotalPooling: 200 + defaultMaxPerRoute: 200 diff --git a/src/main/resources/db/migration/V4__add_already_exist_status.sql b/src/main/resources/db/migration/V4__add_already_exist_status.sql new file mode 100644 index 0000000..fbdbb4e --- /dev/null +++ b/src/main/resources/db/migration/V4__add_already_exist_status.sql @@ -0,0 +1 @@ +ALTER TYPE dspt.dispute_status ADD VALUE 'already_exist_created'; diff --git a/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java b/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java index 17af52b..949dfdd 100644 --- a/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java +++ b/src/test/java/dev/vality/disputes/dao/DisputeDaoTest.java @@ -1,26 +1,47 @@ package dev.vality.disputes.dao; +import dev.vality.disputes.dao.config.PostgresqlSpringBootITest; import dev.vality.disputes.domain.tables.pojos.Dispute; -import dev.vality.testcontainers.annotations.DefaultSpringBootTest; -import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton; +import dev.vality.disputes.exception.NotFoundException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static dev.vality.testcontainers.annotations.util.RandomBeans.random; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateId; +import static dev.vality.testcontainers.annotations.util.ValuesGenerator.generateLong; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -@PostgresqlTestcontainerSingleton -@DefaultSpringBootTest +@PostgresqlSpringBootITest public class DisputeDaoTest { @Autowired private DisputeDao disputeDao; @Test - public void insertAndFindTest() { + public void testInsertAndFind() { var random = random(Dispute.class); disputeDao.save(random); assertEquals(random, disputeDao.get(random.getId(), random.getInvoiceId(), random.getPaymentId())); } + + @Test + public void testNotFoundException() { + assertThrows(NotFoundException.class, + () -> disputeDao.get(generateLong(), generateId(), generateId())); + } + + @Test + public void testMultiInsertAndFind() { + var random = random(Dispute.class); + random.setId(null); + random.setInvoiceId("setInvoiceId"); + random.setPaymentId("setPaymentId"); + disputeDao.save(random); + disputeDao.save(random); + disputeDao.save(random); + assertEquals(3, + disputeDao.get(random.getInvoiceId(), random.getPaymentId()).size()); + } } diff --git a/src/test/java/dev/vality/disputes/dao/config/PostgresqlSpringBootITest.java b/src/test/java/dev/vality/disputes/dao/config/PostgresqlSpringBootITest.java new file mode 100644 index 0000000..369e0ba --- /dev/null +++ b/src/test/java/dev/vality/disputes/dao/config/PostgresqlSpringBootITest.java @@ -0,0 +1,19 @@ +package dev.vality.disputes.dao.config; + +import dev.vality.disputes.dao.config.testconfiguration.MockedUnimportantServicesConfig; +import dev.vality.testcontainers.annotations.DefaultSpringBootTest; +import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PostgresqlTestcontainerSingleton +@DefaultSpringBootTest +@Import(MockedUnimportantServicesConfig.class) +public @interface PostgresqlSpringBootITest { +} diff --git a/src/test/java/dev/vality/disputes/dao/config/testconfiguration/MockedUnimportantServicesConfig.java b/src/test/java/dev/vality/disputes/dao/config/testconfiguration/MockedUnimportantServicesConfig.java new file mode 100644 index 0000000..9ccf13b --- /dev/null +++ b/src/test/java/dev/vality/disputes/dao/config/testconfiguration/MockedUnimportantServicesConfig.java @@ -0,0 +1,21 @@ +package dev.vality.disputes.dao.config.testconfiguration; + +import dev.vality.disputes.schedule.TaskCreateAdjustmentsService; +import dev.vality.disputes.schedule.TaskCreatedDisputesService; +import dev.vality.disputes.schedule.TaskPendingDisputesService; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +@TestConfiguration +public class MockedUnimportantServicesConfig { + + @MockBean + private TaskCreatedDisputesService taskCreatedDisputesService; + + @MockBean + private TaskPendingDisputesService taskPendingDisputesService; + + @MockBean + private TaskCreateAdjustmentsService taskCreateAdjustmentsService; + +}