add mergedOptions + refactor (#22)

* add mergedOptions + refactor

* fine-tuning ExponentialBackOffPollingService

* fix after review
This commit is contained in:
Anatolii Karlov 2024-10-22 20:14:58 +07:00 committed by GitHub
parent b934fe2569
commit 2329359235
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 264 additions and 135 deletions

View File

@ -1,11 +1,12 @@
package dev.vality.disputes.api.service;
import dev.vality.damsel.domain.Currency;
import dev.vality.damsel.domain.Cash;
import dev.vality.damsel.domain.CurrencyRef;
import dev.vality.damsel.domain.TransactionInfo;
import dev.vality.damsel.payment_processing.InvoicePayment;
import dev.vality.disputes.api.model.PaymentParams;
import dev.vality.disputes.schedule.service.ProviderDataService;
import dev.vality.disputes.security.AccessData;
import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService;
import dev.vality.disputes.service.external.impl.partymgnt.PartyManagementAsyncService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@ -13,14 +14,13 @@ 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 DominantAsyncService dominantAsyncService;
private final ProviderDataService providerDataService;
private final PartyManagementAsyncService partyManagementAsyncService;
@SneakyThrows
@ -29,12 +29,7 @@ public class PaymentParamsBuilder {
log.debug("Start building PaymentParams id={}", invoice.getId());
var payment = accessData.getPayment();
// http 500
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 -> dominantAsyncService.getCurrency(cost.getCurrency()));
var currency = providerDataService.getCurrency(getCurrencyRef(payment));
var shop = partyManagementAsyncService.getShop(invoice.getOwnerId(), invoice.getShopId());
var paymentParams = PaymentParams.builder()
.invoiceId(invoice.getId())
@ -42,15 +37,12 @@ public class PaymentParamsBuilder {
.terminalId(payment.getRoute().getTerminal().getId())
.providerId(payment.getRoute().getProvider().getId())
.providerTrxId(getProviderTrxId(payment))
.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())
.currencyName(currency.getName())
.currencySymbolicCode(currency.getSymbolicCode())
.currencyNumericCode((int) currency.getNumericCode())
.currencyExponent((int) currency.getExponent())
// http 500
.options(providerDataService.getProviderData(payment).getOptions())
.shopId(invoice.getShopId())
.shopDetailsName(shop.get().getDetails().getName())
.invoiceAmount(payment.getPayment().getCost().getAmount())
@ -59,14 +51,12 @@ public class PaymentParamsBuilder {
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 CurrencyRef getCurrencyRef(InvoicePayment payment) {
return Optional.of(payment)
.filter(p -> p.getPayment().isSetCost())
.map(p -> p.getPayment().getCost())
.map(Cash::getCurrency)
.orElse(null);
}
private String getProviderTrxId(InvoicePayment payment) {

View File

@ -0,0 +1,36 @@
package dev.vality.disputes.polling;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.utils.backoff.ExponentialBackOff;
import java.time.Instant;
import java.util.Map;
import static dev.vality.adapter.flow.lib.utils.backoff.ExponentialBackOff.*;
public class ExponentialBackOffPollingService {
public int prepareNextPollingInterval(PollingInfo pollingInfo, Map<String, String> options) {
return exponentialBackOff(pollingInfo, options)
.start()
.nextBackOff()
.intValue();
}
private ExponentialBackOff exponentialBackOff(PollingInfo pollingInfo, Map<String, String> options) {
final var currentLocalTime = Instant.now().toEpochMilli();
var startTime = pollingInfo.getStartDateTimePolling() != null
? pollingInfo.getStartDateTimePolling().toEpochMilli()
: currentLocalTime;
var exponential = TimeOptionsExtractors.extractExponent(options, DEFAULT_MUTIPLIER);
var defaultInitialExponential =
TimeOptionsExtractors.extractDefaultInitialExponential(options, DEFAULT_INITIAL_INTERVAL_SEC);
var maxTimeBackOff = TimeOptionsExtractors.extractMaxTimeBackOff(options, DEFAULT_MAX_INTERVAL_SEC);
return new ExponentialBackOff(
startTime,
currentLocalTime,
exponential,
defaultInitialExponential,
maxTimeBackOff);
}
}

View File

@ -1,11 +1,7 @@
package dev.vality.disputes.polling;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.service.ExponentialBackOffPollingService;
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 org.springframework.stereotype.Service;
import java.time.Instant;
@ -17,10 +13,8 @@ import java.util.Map;
public class ExponentialBackOffPollingServiceWrapper {
private final ExponentialBackOffPollingService exponentialBackOffPollingService;
private final DominantService dominantService;
public ExponentialBackOffPollingServiceWrapper(DominantService dominantService) {
this.dominantService = dominantService;
public ExponentialBackOffPollingServiceWrapper() {
this.exponentialBackOffPollingService = new ExponentialBackOffPollingService();
}
@ -29,20 +23,15 @@ public class ExponentialBackOffPollingServiceWrapper {
return getLocalDateTime(pollingInfo.getStartDateTimePolling().plusSeconds(seconds));
}
public LocalDateTime prepareNextPollingInterval(Dispute dispute) {
public LocalDateTime prepareNextPollingInterval(Dispute dispute, Map<String, String> options) {
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.getOptions());
var seconds = exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, options);
return getLocalDateTime(dispute.getNextCheckAfter().toInstant(ZoneOffset.UTC).plusSeconds(seconds));
}
private Terminal getTerminal(Integer terminalId) {
return dominantService.getTerminal(new TerminalRef(terminalId));
}
private LocalDateTime getLocalDateTime(Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
}

View File

@ -0,0 +1,36 @@
package dev.vality.disputes.polling;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TimeOptionsExtractors {
public static final String TIMER_EXPONENTIAL = "exponential";
public static final String MAX_TIME_BACKOFF_SEC = "max_time_backoff_sec";
public static final String DEFAULT_INITIAL_EXPONENTIAL_SEC = "default_initial_exponential_sec";
public static final String DISPUTE_TIMER_EXPONENTIAL = "dispute_exponential";
public static final String DISPUTE_MAX_TIME_BACKOFF_SEC = "dispute_max_time_backoff_sec";
public static final String DISPUTE_DEFAULT_INITIAL_EXPONENTIAL_SEC = "dispute_default_initial_exponential_sec";
public static Integer extractExponent(Map<String, String> options, int maxTimePolling) {
return Integer.parseInt(options.getOrDefault(
DISPUTE_TIMER_EXPONENTIAL,
options.getOrDefault(TIMER_EXPONENTIAL, String.valueOf(maxTimePolling))));
}
public static Integer extractMaxTimeBackOff(Map<String, String> options, int maxTimeBackOff) {
return Integer.parseInt(options.getOrDefault(
DISPUTE_MAX_TIME_BACKOFF_SEC,
options.getOrDefault(MAX_TIME_BACKOFF_SEC, String.valueOf(maxTimeBackOff))));
}
public static Integer extractDefaultInitialExponential(Map<String, String> options, int defaultInitialExponential) {
return Integer.parseInt(
options.getOrDefault(DISPUTE_DEFAULT_INITIAL_EXPONENTIAL_SEC,
options.getOrDefault(DEFAULT_INITIAL_EXPONENTIAL_SEC, String.valueOf(
defaultInitialExponential))));
}
}

View File

@ -1,9 +1,5 @@
package dev.vality.disputes.schedule.client;
import dev.vality.damsel.domain.ProviderRef;
import dev.vality.damsel.domain.ProxyDefinition;
import dev.vality.damsel.domain.Terminal;
import dev.vality.damsel.domain.TerminalRef;
import dev.vality.disputes.domain.tables.pojos.Dispute;
import dev.vality.disputes.domain.tables.pojos.ProviderDispute;
import dev.vality.disputes.provider.Attachment;
@ -11,8 +7,8 @@ import dev.vality.disputes.provider.DisputeCreatedResult;
import dev.vality.disputes.provider.DisputeStatusResult;
import dev.vality.disputes.schedule.converter.DisputeContextConverter;
import dev.vality.disputes.schedule.converter.DisputeParamsConverter;
import dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.disputes.schedule.service.ProviderIfaceBuilder;
import dev.vality.disputes.service.external.DominantService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -27,19 +23,16 @@ import java.util.List;
public class RemoteClient {
private final ProviderIfaceBuilder providerIfaceBuilder;
private final DominantService dominantService;
private final DisputeContextConverter disputeContextConverter;
private final DisputeParamsConverter disputeParamsConverter;
@SneakyThrows
public DisputeCreatedResult createDispute(Dispute dispute, List<Attachment> attachments) {
public DisputeCreatedResult createDispute(Dispute dispute, List<Attachment> attachments, ProviderData providerData) {
log.debug("Trying to call dominant for RemoteClient {}", dispute.getId());
var terminal = getTerminal(dispute.getTerminalId());
var proxy = getProxy(dispute.getProviderId());
log.debug("Trying to build disputeParams {}", dispute.getId());
var disputeParams = disputeParamsConverter.convert(dispute, attachments, terminal.getOptions());
var disputeParams = disputeParamsConverter.convert(dispute, attachments, providerData.getOptions());
log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId());
var remoteClient = providerIfaceBuilder.buildTHSpawnClient(terminal.getOptions(), proxy.getUrl());
var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData);
log.debug("Trying to routed remote provider's createDispute() call {}", dispute.getId());
var result = remoteClient.createDispute(disputeParams);
log.info("Routed remote provider's createDispute() has been called {} {}", dispute.getId(), result);
@ -47,26 +40,15 @@ public class RemoteClient {
}
@SneakyThrows
public DisputeStatusResult checkDisputeStatus(Dispute dispute, ProviderDispute providerDispute) {
public DisputeStatusResult checkDisputeStatus(Dispute dispute, ProviderDispute providerDispute, ProviderData providerData) {
log.debug("Trying to call dominant for RemoteClient {}", dispute.getId());
var terminal = getTerminal(dispute.getTerminalId());
var proxy = getProxy(dispute.getProviderId());
log.debug("Trying to build disputeContext {}", dispute.getId());
var disputeContext = disputeContextConverter.convert(dispute, providerDispute, terminal.getOptions());
var disputeContext = disputeContextConverter.convert(dispute, providerDispute, providerData.getOptions());
log.debug("Trying to call ProviderIfaceBuilder {}", dispute.getId());
var remoteClient = providerIfaceBuilder.buildTHSpawnClient(terminal.getOptions(), proxy.getUrl());
var remoteClient = providerIfaceBuilder.buildTHSpawnClient(providerData);
log.debug("Trying to routed remote provider's checkDisputeStatus() call {}", dispute.getId());
var result = remoteClient.checkDisputeStatus(disputeContext);
log.info("Routed remote provider's checkDisputeStatus() has been called {} {}", dispute.getId(), result);
return result;
}
private ProxyDefinition getProxy(Integer providerId) {
var provider = dominantService.getProvider(new ProviderRef(providerId));
return dominantService.getProxy(provider.getProxy().getRef());
}
private Terminal getTerminal(Integer terminalId) {
return dominantService.getTerminal(new TerminalRef(terminalId));
}
}

View File

@ -12,6 +12,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
@ -22,11 +24,11 @@ public class DisputeStatusResultHandler {
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
@Transactional(propagation = Propagation.REQUIRED)
public void handleStatusPending(Dispute dispute, DisputeStatusResult result) {
public void handleStatusPending(Dispute dispute, DisputeStatusResult result, Map<String, String> options) {
// дергаем update() чтоб обновить время вызова next_check_after,
// чтобы шедулатор далее доставал пачку самых древних диспутов и смещал
// и этим вызовом мы финализируем состояние диспута, что он был обновлен недавно
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute);
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options);
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.getId());

View File

@ -0,0 +1,15 @@
package dev.vality.disputes.schedule.model;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
public class ProviderData {
private Map<String, String> options;
private String defaultProviderUrl;
}

View File

@ -1,7 +1,5 @@
package dev.vality.disputes.schedule.service;
import dev.vality.damsel.domain.Terminal;
import dev.vality.damsel.domain.TerminalRef;
import dev.vality.damsel.payment_processing.InvoicePayment;
import dev.vality.disputes.constant.ErrorReason;
import dev.vality.disputes.dao.DisputeDao;
@ -14,7 +12,6 @@ import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper;
import dev.vality.disputes.provider.Attachment;
import dev.vality.disputes.provider.DisputeCreatedResult;
import dev.vality.disputes.schedule.client.RemoteClient;
import dev.vality.disputes.service.external.DominantService;
import dev.vality.disputes.service.external.InvoicingService;
import dev.vality.disputes.utils.ErrorFormatter;
import dev.vality.woody.api.flow.error.WRuntimeException;
@ -26,6 +23,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_CAPTURED_BLOCKED;
import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_PROVIDERS_API_EXIST;
@ -42,7 +40,7 @@ public class CreatedDisputesService {
private final CreatedAttachmentsService createdAttachmentsService;
private final InvoicingService invoicingService;
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
private final DominantService dominantService;
private final ProviderDataService providerDataService;
private final ExternalGatewayChecker externalGatewayChecker;
private final ManualParsingTopic manualParsingTopic;
@ -83,19 +81,21 @@ public class CreatedDisputesService {
log.debug("Dispute status has been set to failed {}", dispute.getId());
return;
}
if ((status.isSetCaptured() && isCapturedBlockedForDispute(dispute))
|| isNotProvidersDisputesApiExist(dispute)) {
var providerData = providerDataService.getProviderData(dispute.getProviderId(), dispute.getTerminalId());
var options = providerData.getOptions();
if ((status.isSetCaptured() && isCapturedBlockedForDispute(options))
|| isNotProvidersDisputesApiExist(options)) {
// отправлять на ручной разбор, если выставлена опция
// DISPUTE_FLOW_CAPTURED_BLOCKED или не выставлена DISPUTE_FLOW_PROVIDERS_API_EXIST
log.warn("finishTaskWithManualParsingFlowActivation, options capt={}, apiExist={}", isCapturedBlockedForDispute(dispute), isNotProvidersDisputesApiExist(dispute));
log.warn("finishTaskWithManualParsingFlowActivation, options capt={}, apiExist={}", isCapturedBlockedForDispute(options), isNotProvidersDisputesApiExist(options));
finishTaskWithManualParsingFlowActivation(dispute, attachments, DisputeStatus.manual_created);
return;
}
try {
var result = remoteClient.createDispute(dispute, attachments);
finishTask(dispute, attachments, result);
var result = remoteClient.createDispute(dispute, attachments, providerData);
finishTask(dispute, attachments, result, options);
} catch (WRuntimeException e) {
if (externalGatewayChecker.isNotProvidersDisputesApiExist(dispute, e)) {
if (externalGatewayChecker.isNotProvidersDisputesApiExist(providerData, e)) {
// отправлять на ручной разбор, если API диспутов на провайдере не реализовано
// (тогда при тесте соединения вернется 404)
log.warn("finishTaskWithManualParsingFlowActivation with externalGatewayChecker", e);
@ -107,10 +107,10 @@ public class CreatedDisputesService {
}
@Transactional(propagation = Propagation.REQUIRED)
void finishTask(Dispute dispute, List<Attachment> attachments, DisputeCreatedResult result) {
void finishTask(Dispute dispute, List<Attachment> attachments, DisputeCreatedResult result, Map<String, String> options) {
switch (result.getSetField()) {
case SUCCESS_RESULT -> {
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute);
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options);
log.info("Trying to set pending Dispute status {}, {}", dispute, result);
providerDisputeDao.save(new ProviderDispute(result.getSuccessResult().getProviderDisputeId(), dispute.getId()));
disputeDao.update(dispute.getId(), DisputeStatus.pending, nextCheckAfter);
@ -135,21 +135,15 @@ public class CreatedDisputesService {
log.debug("Dispute status has been set to {} {}", disputeStatus, dispute.getId());
}
private boolean isCapturedBlockedForDispute(Dispute dispute) {
return getTerminal(dispute.getTerminalId()).getOptions()
.containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED);
private boolean isCapturedBlockedForDispute(Map<String, String> options) {
return options.containsKey(DISPUTE_FLOW_CAPTURED_BLOCKED);
}
private boolean isNotProvidersDisputesApiExist(Dispute dispute) {
return !getTerminal(dispute.getTerminalId()).getOptions()
.containsKey(DISPUTE_FLOW_PROVIDERS_API_EXIST);
private boolean isNotProvidersDisputesApiExist(Map<String, String> options) {
return !options.containsKey(DISPUTE_FLOW_PROVIDERS_API_EXIST);
}
private InvoicePayment getInvoicePayment(Dispute dispute) {
return invoicingService.getInvoicePayment(dispute.getInvoiceId(), dispute.getPaymentId());
}
private Terminal getTerminal(Integer terminalId) {
return dominantService.getTerminal(new TerminalRef(terminalId));
}
}

View File

@ -1,11 +1,6 @@
package dev.vality.disputes.schedule.service;
import dev.vality.damsel.domain.ProviderRef;
import dev.vality.damsel.domain.ProxyDefinition;
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 dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.woody.api.flow.error.WErrorSource;
import dev.vality.woody.api.flow.error.WErrorType;
import dev.vality.woody.api.flow.error.WRuntimeException;
@ -25,24 +20,23 @@ import org.springframework.stereotype.Service;
public class ExternalGatewayChecker {
private final CloseableHttpClient httpClient;
private final DominantService dominantService;
private final ProviderRouting providerRouting;
public boolean isNotProvidersDisputesApiExist(Dispute dispute, WRuntimeException e) {
public boolean isNotProvidersDisputesApiExist(ProviderData providerData, WRuntimeException e) {
return e.getErrorDefinition() != null
&& e.getErrorDefinition().getGenerationSource() == WErrorSource.EXTERNAL
&& e.getErrorDefinition().getErrorType() == WErrorType.UNEXPECTED_ERROR
&& e.getErrorDefinition().getErrorSource() == WErrorSource.INTERNAL
&& isNotFoundProvidersDisputesApi(dispute);
&& isNotFoundProvidersDisputesApi(providerData);
}
@SneakyThrows
private Boolean isNotFoundProvidersDisputesApi(Dispute dispute) {
return httpClient.execute(new HttpGet(getRouteUrl(dispute)), isNotFoundResponse());
private Boolean isNotFoundProvidersDisputesApi(ProviderData providerData) {
return httpClient.execute(new HttpGet(getRouteUrl(providerData)), isNotFoundResponse());
}
private String getRouteUrl(Dispute dispute) {
var routeUrl = providerRouting.getRouteUrl(getTerminal(dispute.getTerminalId()).getOptions(), getProxy(dispute.getProviderId()).getUrl());
private String getRouteUrl(ProviderData providerData) {
var routeUrl = providerRouting.getRouteUrl(providerData);
log.debug("Check adapter connection, routeUrl={}", routeUrl);
return routeUrl;
}
@ -53,13 +47,4 @@ public class ExternalGatewayChecker {
return response.getCode() == HttpStatus.SC_NOT_FOUND;
};
}
private Terminal getTerminal(Integer terminalId) {
return dominantService.getTerminal(new TerminalRef(terminalId));
}
private ProxyDefinition getProxy(Integer providerId) {
var provider = dominantService.getProvider(new ProviderRef(providerId));
return dominantService.getProxy(provider.getProxy().getRef());
}
}

View File

@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@ -30,6 +31,7 @@ public class PendingDisputesService {
private final ProviderDisputeDao providerDisputeDao;
private final PollingInfoService pollingInfoService;
private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService;
private final ProviderDataService providerDataService;
private final DisputeStatusResultHandler disputeStatusResultHandler;
@Transactional(propagation = Propagation.REQUIRED)
@ -50,9 +52,10 @@ public class PendingDisputesService {
}
log.debug("GetDisputeForUpdateSkipLocked has been found {}", dispute);
log.debug("Trying to get ProviderDispute {}", dispute.getId());
var providerData = providerDataService.getProviderData(dispute.getProviderId(), dispute.getTerminalId());
var providerDispute = providerDisputeDao.get(dispute.getId());
if (providerDispute == null) {
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute);
var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(dispute, providerData.getOptions());
// вернуть в CreatedDisputeService и попробовать создать диспут в провайдере заново
log.error("Trying to set created Dispute status, because createDispute() was not success {}", dispute.getId());
disputeDao.update(dispute.getId(), DisputeStatus.created, nextCheckAfter);
@ -66,16 +69,16 @@ public class PendingDisputesService {
return;
}
log.debug("ProviderDispute has been found {}", dispute.getId());
var result = remoteClient.checkDisputeStatus(dispute, providerDispute);
finishTask(dispute, result);
var result = remoteClient.checkDisputeStatus(dispute, providerDispute, providerData);
finishTask(dispute, result, providerData.getOptions());
}
@Transactional(propagation = Propagation.REQUIRED)
void finishTask(Dispute dispute, DisputeStatusResult result) {
void finishTask(Dispute dispute, DisputeStatusResult result, Map<String, String> options) {
switch (result.getSetField()) {
case STATUS_SUCCESS -> disputeStatusResultHandler.handleStatusSuccess(dispute, result);
case STATUS_FAIL -> disputeStatusResultHandler.handleStatusFail(dispute, result);
case STATUS_PENDING -> disputeStatusResultHandler.handleStatusPending(dispute, result);
case STATUS_PENDING -> disputeStatusResultHandler.handleStatusPending(dispute, result, options);
}
}
}

View File

@ -0,0 +1,50 @@
package dev.vality.disputes.schedule.service;
import dev.vality.damsel.domain.Currency;
import dev.vality.damsel.domain.CurrencyRef;
import dev.vality.damsel.domain.ProviderRef;
import dev.vality.damsel.domain.TerminalRef;
import dev.vality.damsel.payment_processing.InvoicePayment;
import dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.disputes.service.external.DominantService;
import dev.vality.disputes.service.external.impl.dominant.DominantAsyncService;
import dev.vality.disputes.utils.OptionsExtractors;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProviderDataService {
private final DominantService dominantService;
private final DominantAsyncService dominantAsyncService;
public ProviderData getProviderData(Integer providerId, Integer terminalId) {
var provider = dominantService.getProvider(new ProviderRef(providerId));
var terminal = dominantService.getTerminal(new TerminalRef(terminalId));
var proxy = dominantService.getProxy(provider.getProxy().getRef());
return ProviderData.builder()
.options(OptionsExtractors.mergeOptions(provider, proxy, terminal))
.defaultProviderUrl(proxy.getUrl())
.build();
}
@SneakyThrows
public ProviderData getProviderData(InvoicePayment payment) {
var provider = dominantAsyncService.getProvider(payment.getRoute().getProvider());
var terminal = dominantAsyncService.getTerminal(payment.getRoute().getTerminal());
var proxy = dominantAsyncService.getProxy(provider.get().getProxy().getRef());
return ProviderData.builder()
.options(OptionsExtractors.mergeOptions(provider.get(), proxy.get(), terminal.get()))
.defaultProviderUrl(proxy.get().getUrl())
.build();
}
@SneakyThrows
public Currency getCurrency(CurrencyRef currencyRef) {
return currencyRef == null ? new Currency() : dominantAsyncService.getCurrency(currencyRef).get();
}
}

View File

@ -2,6 +2,7 @@ package dev.vality.disputes.schedule.service;
import dev.vality.disputes.config.properties.AdaptersConnectionProperties;
import dev.vality.disputes.provider.ProviderDisputesServiceSrv;
import dev.vality.disputes.schedule.model.ProviderData;
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -9,7 +10,6 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@ -21,9 +21,9 @@ public class ProviderIfaceBuilder {
private final ProviderRouting providerRouting;
private final AdaptersConnectionProperties adaptersConnectionProperties;
@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);
@Cacheable(value = "adapters", key = "#providerData.defaultProviderUrl", cacheManager = "adaptersCacheManager")
public ProviderDisputesServiceSrv.Iface buildTHSpawnClient(ProviderData providerData) {
var routeUrl = providerRouting.getRouteUrl(providerData);
log.info("Creating new client for url: {}", routeUrl);
return new THSpawnClientBuilder()
.withNetworkTimeout((int) TimeUnit.SECONDS.toMillis(adaptersConnectionProperties.getTimeoutSec()))

View File

@ -1,6 +1,7 @@
package dev.vality.disputes.schedule.service;
import dev.vality.disputes.exception.RoutingException;
import dev.vality.disputes.schedule.model.ProviderData;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -8,7 +9,6 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Map;
@Slf4j
@Service
@ -18,10 +18,10 @@ public class ProviderRouting {
private static final String DISPUTES_URL_POSTFIX_DEFAULT = "disputes";
private static final String OPTION_DISPUTES_URL_FIELD_NAME = "disputes_url";
public String getRouteUrl(Map<String, String> options, String defaultProviderUrl) {
var url = options.get(OPTION_DISPUTES_URL_FIELD_NAME);
public String getRouteUrl(ProviderData providerData) {
var url = providerData.getOptions().get(OPTION_DISPUTES_URL_FIELD_NAME);
if (ObjectUtils.isEmpty(url)) {
url = createDefaultRouteUrl(defaultProviderUrl);
url = createDefaultRouteUrl(providerData.getDefaultProviderUrl());
}
return url;
}

View File

@ -1,9 +1,6 @@
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.damsel.domain.*;
import dev.vality.disputes.service.external.DominantService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -38,4 +35,24 @@ public class DominantAsyncService {
return CompletableFuture.failedFuture(e);
}
}
@Async("disputesAsyncServiceExecutor")
public CompletableFuture<ProxyDefinition> getProxy(ProxyRef proxyRef) {
try {
var proxy = dominantService.getProxy(proxyRef);
return CompletableFuture.completedFuture(proxy);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
@Async("disputesAsyncServiceExecutor")
public CompletableFuture<Provider> getProvider(ProviderRef providerRef) {
try {
var provider = dominantService.getProvider(providerRef);
return CompletableFuture.completedFuture(provider);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}

View File

@ -1,8 +1,13 @@
package dev.vality.disputes.utils;
import dev.vality.damsel.domain.Provider;
import dev.vality.damsel.domain.ProxyDefinition;
import dev.vality.damsel.domain.Terminal;
import lombok.experimental.UtilityClass;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static dev.vality.disputes.constant.TerminalOptionsField.DISPUTE_FLOW_MAX_TIME_POLLING_MIN;
@ -13,4 +18,17 @@ public class OptionsExtractors {
return Integer.parseInt(
options.getOrDefault(DISPUTE_FLOW_MAX_TIME_POLLING_MIN, String.valueOf(maxTimePolling)));
}
public static Map<String, String> mergeOptions(Provider provider, ProxyDefinition proxy, Terminal terminal) {
var merged = new HashMap<String, String>();
merged.putAll(safetyPut(provider.getProxy().getAdditional()));
merged.putAll(safetyPut(proxy.getOptions()));
merged.putAll(safetyPut(terminal.getOptions()));
return merged;
}
private static Map<String, String> safetyPut(Map<String, String> options) {
return Optional.ofNullable(options)
.orElse(new HashMap<>());
}
}

View File

@ -92,6 +92,8 @@ public class DisputesApiDelegateServiceTest {
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(dominantAsyncService.getTerminal(any())).thenReturn(createTerminal());
when(dominantAsyncService.getCurrency(any())).thenReturn(createCurrency());
when(dominantAsyncService.getProvider(any())).thenReturn(createProvider());
when(dominantAsyncService.getProxy(any())).thenReturn(createProxy());
when(partyManagementAsyncService.getShop(any(), any())).thenReturn(createShop());
when(fileStorageClient.createNewFile(any(), any())).thenReturn(createNewFileResult(wiremockAddressesHolder.getUploadUrl()));
WiremockUtils.mockS3AttachmentUpload();
@ -108,6 +110,8 @@ public class DisputesApiDelegateServiceTest {
verify(bouncerClient, times(1)).judge(any(), any());
verify(dominantAsyncService, times(1)).getTerminal(any());
verify(dominantAsyncService, times(1)).getCurrency(any());
verify(dominantAsyncService, times(1)).getProvider(any());
verify(dominantAsyncService, times(1)).getProxy(any());
verify(partyManagementAsyncService, times(1)).getShop(any(), any());
verify(fileStorageClient, times(1)).createNewFile(any(), any());
mvc.perform(get("/disputes/status")
@ -149,6 +153,8 @@ public class DisputesApiDelegateServiceTest {
verify(bouncerClient, times(4)).judge(any(), any());
verify(dominantAsyncService, times(2)).getTerminal(any());
verify(dominantAsyncService, times(2)).getCurrency(any());
verify(dominantAsyncService, times(2)).getProvider(any());
verify(dominantAsyncService, times(2)).getProxy(any());
verify(partyManagementAsyncService, times(2)).getShop(any(), any());
verify(fileStorageClient, times(2)).createNewFile(any(), any());
disputeDao.update(UUID.fromString(response.getDisputeId()), DisputeStatus.failed);

View File

@ -101,6 +101,8 @@ public class CreatedDisputesServiceTest {
when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment);
when(fileStorageClient.generateDownloadUrl(any(), any())).thenReturn(wiremockAddressesHolder.getDownloadUrl());
when(dominantService.getTerminal(any())).thenReturn(createTerminal().get());
when(dominantService.getProvider(any())).thenReturn(createProvider().get());
when(dominantService.getProxy(any())).thenReturn(createProxy().get());
var dispute = disputeDao.get(disputeId);
createdDisputesService.callCreateDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.manual_created, disputeDao.get(disputeId).get().getStatus());
@ -130,7 +132,7 @@ public class CreatedDisputesServiceTest {
when(dominantService.getProxy(any())).thenReturn(createProxy().get());
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.createDispute(any())).thenReturn(createDisputeCreatedFailResult());
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
createdDisputesService.callCreateDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus());
@ -153,7 +155,7 @@ public class CreatedDisputesServiceTest {
when(dominantService.getProxy(any())).thenReturn(createProxy().get());
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.createDispute(any())).thenReturn(createDisputeAlreadyExistResult());
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
createdDisputesService.callCreateDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.already_exist_created, disputeDao.get(disputeId).get().getStatus());

View File

@ -50,6 +50,8 @@ public class PendingDisputesServiceTest {
var terminal = createTerminal().get();
terminal.getOptions().putAll(getOptions());
when(dominantService.getTerminal(any())).thenReturn(terminal);
when(dominantService.getProvider(any())).thenReturn(createProvider().get());
when(dominantService.getProxy(any())).thenReturn(createProxy().get());
var dispute = disputeDao.get(disputeId);
pendingDisputesService.callPendingDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.created, disputeDao.get(disputeId).get().getStatus());
@ -69,7 +71,7 @@ public class PendingDisputesServiceTest {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusFailResult());
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
pendingDisputesService.callPendingDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.failed, disputeDao.get(disputeId).get().getStatus());
@ -81,7 +83,7 @@ public class PendingDisputesServiceTest {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusPendingResult());
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
pendingDisputesService.callPendingDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).get().getStatus());

View File

@ -65,7 +65,7 @@ public class CreatedDisputesTestService {
when(dominantService.getProxy(any())).thenReturn(createProxy(String.format("http://127.0.0.1:%s%s", 8023, TestUrlPaths.ADAPTER)).get());
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.createDispute(any())).thenReturn(createDisputeCreatedSuccessResult(providerDisputeId));
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
createdDisputesService.callCreateDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).get().getStatus());

View File

@ -58,6 +58,8 @@ public class DisputeApiTestService {
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(dominantAsyncService.getTerminal(any())).thenReturn(createTerminal());
when(dominantAsyncService.getCurrency(any())).thenReturn(createCurrency());
when(dominantAsyncService.getProvider(any())).thenReturn(createProvider());
when(dominantAsyncService.getProxy(any())).thenReturn(createProxy());
when(partyManagementAsyncService.getShop(any(), any())).thenReturn(createShop());
when(fileStorageClient.createNewFile(any(), any())).thenReturn(createNewFileResult(wiremockAddressesHolder.getUploadUrl()));
WiremockUtils.mockS3AttachmentUpload();

View File

@ -37,7 +37,7 @@ public class PendingDisputesTestService {
var disputeId = createdDisputesTestService.callCreateDisputeRemotely();
var providerMock = mock(ProviderDisputesServiceSrv.Client.class);
when(providerMock.checkDisputeStatus(any())).thenReturn(createDisputeStatusSuccessResult());
when(providerIfaceBuilder.buildTHSpawnClient(any(), any())).thenReturn(providerMock);
when(providerIfaceBuilder.buildTHSpawnClient(any())).thenReturn(providerMock);
var dispute = disputeDao.get(disputeId);
pendingDisputesService.callPendingDisputeRemotely(dispute.get());
assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).get().getStatus());