mirror of
https://github.com/valitydev/disputes-api.git
synced 2024-11-06 00:55:23 +00:00
fixes, add already_exist_created flow, add GetDispute handler (#9)
This commit is contained in:
parent
7c61521a9a
commit
6faacd9ac1
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
- 'epic/**'
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
|
@ -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` которая автоматически вызывает апи провайдера для проверки статуса
|
||||
|
||||
# Схема аппрува корректировок
|
||||
|
10
pom.xml
10
pom.xml
@ -47,7 +47,7 @@
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
<artifactId>provider-disputes-proto</artifactId>
|
||||
<version>1.16-61eff7c</version>
|
||||
<version>1.18-6602d79</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.vality</groupId>
|
||||
@ -89,10 +89,6 @@
|
||||
</dependency>
|
||||
|
||||
<!--spring-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
@ -193,6 +189,10 @@
|
||||
<version>3.0.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Currency> getCurrency(Optional<CompletableFuture<Currency>> 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)
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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> connectionSocketFactory() {
|
||||
var sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustAllStrategy()).build();
|
||||
var sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
|
||||
return RegistryBuilder.<ConnectionSocketFactory>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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -62,15 +62,18 @@ public class DisputeDao extends AbstractGenericDao {
|
||||
.orElse(List.of());
|
||||
}
|
||||
|
||||
public Dispute getForUpdateSkipLocked(long disputeId) {
|
||||
public Optional<Dispute> get(long disputeId) {
|
||||
var query = getDslContext().selectFrom(DISPUTE)
|
||||
.where(DISPUTE.ID.eq(disputeId));
|
||||
return Optional.ofNullable(fetchOne(query, disputeRowMapper));
|
||||
}
|
||||
|
||||
public Optional<Dispute> 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<Dispute> getDisputesForUpdateSkipLocked(int limit, DisputeStatus disputeStatus) {
|
||||
|
@ -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<Dispute> 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<Attachment> attachments;
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class Attachment {
|
||||
|
||||
private String data;
|
||||
|
||||
}
|
||||
|
||||
public static class AttachmentDeserializer extends JsonDeserializer<Attachment> {
|
||||
|
||||
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<dev.vality.disputes.admin.Attachment>() {
|
||||
});
|
||||
var attachmentResult = new Attachment();
|
||||
attachmentResult.setData(Base64.getEncoder().encodeToString(attachment.getData()));
|
||||
return attachmentResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<byte[]>() {
|
||||
@Override
|
||||
public byte[] handleEntity(HttpEntity entity) throws IOException {
|
||||
return EntityUtils.toByteArray(entity);
|
||||
}
|
||||
});
|
||||
disputeResult.getAttachments().get().add(new Attachment(ByteBuffer.wrap(data)));
|
||||
}
|
||||
return disputeResult;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<Attachment> attachments) {
|
||||
public void sendCreated(Dispute dispute, List<Attachment> 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();
|
||||
|
@ -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<Terminal> getTerminal(Integer terminalId) {
|
||||
private Terminal getTerminal(Integer terminalId) {
|
||||
return dominantService.getTerminal(new TerminalRef(terminalId));
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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<Attachment> 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<ProxyDefinition> 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<Terminal> getTerminal(Integer terminalId) {
|
||||
private Terminal getTerminal(Integer terminalId) {
|
||||
return dominantService.getTerminal(new TerminalRef(terminalId));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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<Attachment> getAttachments(Dispute dispute) {
|
||||
log.debug("Trying to get Attachments {}", dispute);
|
||||
try {
|
||||
|
@ -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<Attachment> 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<Attachment> 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<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());
|
||||
}
|
||||
|
||||
@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<Terminal> getTerminal(Integer terminalId) {
|
||||
private Terminal getTerminal(Integer terminalId) {
|
||||
return dominantService.getTerminal(new TerminalRef(terminalId));
|
||||
}
|
||||
}
|
||||
|
@ -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<Boolean> isNotFoundResponse() {
|
||||
return response -> response.getCode() == HttpStatus.SC_NOT_FOUND;
|
||||
}
|
||||
|
||||
private CompletableFuture<Terminal> getTerminal(Integer terminalId) {
|
||||
private Terminal getTerminal(Integer terminalId) {
|
||||
return dominantService.getTerminal(new TerminalRef(terminalId));
|
||||
}
|
||||
|
||||
private CompletableFuture<ProxyDefinition> 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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String, String> 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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String, String> 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()
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
|
@ -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<Terminal> getTerminal(TerminalRef terminalRef);
|
||||
Terminal getTerminal(TerminalRef terminalRef);
|
||||
|
||||
CompletableFuture<ProxyDefinition> getProxy(ProviderRef providerRef);
|
||||
ProxyDefinition getProxy(ProxyRef proxyRef);
|
||||
|
||||
Provider getProvider(ProviderRef providerRef);
|
||||
|
||||
}
|
||||
|
@ -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<Terminal> 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<ProxyDefinition> 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);
|
||||
}
|
||||
}
|
||||
|
41
src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantAsyncService.java
vendored
Normal file
41
src/main/java/dev/vality/disputes/service/external/impl/dominant/DominantAsyncService.java
vendored
Normal file
@ -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<Currency> getCurrency(CurrencyRef currencyRef) {
|
||||
try {
|
||||
var currency = dominantService.getCurrency(currencyRef);
|
||||
return CompletableFuture.completedFuture(currency);
|
||||
} catch (Exception e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("dominantAsyncServiceExecutor")
|
||||
public CompletableFuture<Terminal> getTerminal(TerminalRef terminalRef) {
|
||||
try {
|
||||
var terminal = dominantService.getTerminal(terminalRef);
|
||||
return CompletableFuture.completedFuture(terminal);
|
||||
} catch (Exception e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -0,0 +1 @@
|
||||
ALTER TYPE dspt.dispute_status ADD VALUE 'already_exist_created';
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user