From b26ba53d748b67fad1eb1e9a99c2db5007317df9 Mon Sep 17 00:00:00 2001 From: vitaxa Date: Thu, 29 Jan 2026 16:23:12 +0300 Subject: [PATCH 1/7] BG-702: withdrawal fee --- pom.xml | 2 +- ...awalStatusChangedHookMessageGenerator.java | 30 +++++++++++++++- .../hooker/service/WithdrawalClient.java | 36 +++++++++++++++++++ .../wallets/hooker/utils/CashFlowUtils.java | 30 ++++++++++++++++ src/main/resources/application.yml | 5 +++ .../handler/WithdrawalEventHandlerTest.java | 4 +++ .../hooker/kafka/WebhookServiceTest.java | 5 +++ 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java create mode 100644 src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java diff --git a/pom.xml b/pom.xml index 6b882d7..ca5402c 100644 --- a/pom.xml +++ b/pom.xml @@ -133,7 +133,7 @@ dev.vality swag-wallets-webhook-events-server - 1.41-1174f82 + 1.42-d30b8f0 com.google.guava diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java index 7887277..b92f8f4 100644 --- a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java @@ -5,6 +5,7 @@ import dev.vality.fistful.withdrawal.StatusChange; import dev.vality.fistful.withdrawal.status.Status; import dev.vality.swag.wallets.webhook.events.model.Event; +import dev.vality.swag.wallets.webhook.events.model.Fee; import dev.vality.swag.wallets.webhook.events.model.WithdrawalFailed; import dev.vality.swag.wallets.webhook.events.model.WithdrawalSucceeded; import dev.vality.wallets.hooker.domain.WebHookModel; @@ -14,6 +15,8 @@ import dev.vality.wallets.hooker.model.MessageGenParams; import dev.vality.wallets.hooker.service.BaseHookMessageGenerator; import dev.vality.wallets.hooker.service.WebHookMessageGeneratorServiceImpl; +import dev.vality.wallets.hooker.service.WithdrawalClient; +import dev.vality.wallets.hooker.utils.CashFlowUtils; import dev.vality.webhook.dispatcher.WebhookMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -29,16 +32,19 @@ public class WithdrawalStatusChangedHookMessageGenerator extends BaseHookMessage private final WebHookMessageGeneratorServiceImpl generatorService; private final ObjectMapper objectMapper; private final AdditionalHeadersGenerator additionalHeadersGenerator; + private final WithdrawalClient withdrawalClient; public WithdrawalStatusChangedHookMessageGenerator( WebHookMessageGeneratorServiceImpl generatorService, ObjectMapper objectMapper, AdditionalHeadersGenerator additionalHeadersGenerator, + WithdrawalClient withdrawalClient, @Value("${parent.not.exist.id}") Long parentId) { super(parentId); this.generatorService = generatorService; this.objectMapper = objectMapper; this.additionalHeadersGenerator = additionalHeadersGenerator; + this.withdrawalClient = withdrawalClient; } @Override @@ -97,9 +103,11 @@ private String initRequestBody( withdrawalFailed.setTopic(Event.TopicEnum.WITHDRAWAL_TOPIC); return objectMapper.writeValueAsString(withdrawalFailed); } else if (status.isSetSucceeded()) { + Fee fee = calculateFee(withdrawalId, eventId); WithdrawalSucceeded withdrawalSucceeded = new WithdrawalSucceeded() .withdrawalID(withdrawalId) - .externalID(externalId); + .externalID(externalId) + .fee(fee); withdrawalSucceeded.setEventType(Event.EventTypeEnum.WITHDRAWAL_SUCCEEDED); withdrawalSucceeded.setEventID(eventId.toString()); withdrawalSucceeded.setOccuredAt(OffsetDateTime.parse(createdAt)); @@ -114,4 +122,24 @@ private String initRequestBody( } } + private Fee calculateFee(String withdrawalId, Long eventId) { + try { + var withdrawalState = withdrawalClient.getWithdrawalInfo(withdrawalId, eventId); + if (withdrawalState != null + && withdrawalState.getEffectiveFinalCashFlow() != null + && withdrawalState.getEffectiveFinalCashFlow().getPostings() != null) { + long amount = CashFlowUtils.getFistfulFee(withdrawalState.getEffectiveFinalCashFlow().getPostings()); + String currency = withdrawalState.getBody().getCurrency().getSymbolicCode(); + return new Fee().amount(amount).currency(currency); + } + log.warn("Unable to calculate fee for withdrawalId={}, eventId={}: missing cash flow data", + withdrawalId, eventId); + return null; + } catch (Exception e) { + log.warn("Error calculating fee for withdrawalId={}, eventId={}: {}", + withdrawalId, eventId, e.getMessage()); + return null; + } + } + } diff --git a/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java b/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java new file mode 100644 index 0000000..a4586e9 --- /dev/null +++ b/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java @@ -0,0 +1,36 @@ +package dev.vality.wallets.hooker.service; + +import dev.vality.fistful.base.EventRange; +import dev.vality.fistful.withdrawal.ManagementSrv; +import dev.vality.fistful.withdrawal.WithdrawalState; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WithdrawalClient { + + private final ManagementSrv.Iface withdrawalFistfulClient; + + public WithdrawalState getWithdrawalInfo(String withdrawalId, long eventId) { + try { + log.debug("Fetching withdrawal state for withdrawalId={}, eventId={}", withdrawalId, eventId); + WithdrawalState withdrawalState = withdrawalFistfulClient.get(withdrawalId, createEventRange(eventId)); + if (withdrawalState == null) { + log.warn("Withdrawal not found for withdrawalId={}, eventId={}", withdrawalId, eventId); + throw new RuntimeException("Withdrawal not found!"); + } + log.debug("Successfully fetched withdrawal state for withdrawalId={}", withdrawalId); + return withdrawalState; + } catch (Exception e) { + log.error("Error fetching withdrawal state for withdrawalId={}, eventId={}", withdrawalId, eventId, e); + throw new RuntimeException("Failed to fetch withdrawal state", e); + } + } + + private EventRange createEventRange(long eventId) { + return new EventRange().setLimit((int) eventId); + } +} diff --git a/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java new file mode 100644 index 0000000..d10df7b --- /dev/null +++ b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java @@ -0,0 +1,30 @@ +package dev.vality.wallets.hooker.utils; + +import dev.vality.fistful.cashflow.FinalCashFlowPosting; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.function.Predicate; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CashFlowUtils { + + public static long getFistfulFee(List postings) { + return getFistfulAmount( + postings, + posting -> posting.getSource().getAccountType().isSetWallet() + && posting.getDestination().getAccountType().isSetSystem() + ); + } + + public static long getFistfulAmount( + List postings, + Predicate filter + ) { + return postings.stream() + .filter(filter) + .map(posting -> posting.getVolume().getAmount()) + .reduce(0L, Long::sum); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 67a0b85..8a3709a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -90,6 +90,11 @@ parent: exist: id: -1 +service: + withdrawal: + url: http://fistful:8022/v1/ + networkTimeout: 5000 + testcontainers: postgresql: tag: '11.4' diff --git a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java index c580946..47a41e9 100644 --- a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java +++ b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java @@ -5,6 +5,7 @@ import dev.vality.wallets.hooker.dao.webhook.WebHookDao; import dev.vality.wallets.hooker.domain.WebHookModel; import dev.vality.wallets.hooker.service.WebHookMessageSenderService; +import dev.vality.wallets.hooker.service.WithdrawalClient; import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +30,9 @@ class WithdrawalEventHandlerTest { @MockitoBean private WebHookMessageSenderService webHookMessageSenderService; + @MockitoBean + private WithdrawalClient withdrawalClient; + @Test void handleWithdrawalCreatedAndAndStatusChange() throws InterruptedException { WebHookModel webhook = TestBeanFactory.createWebhookModel(); diff --git a/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java b/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java index 5704e66..bb1f656 100644 --- a/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java +++ b/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java @@ -5,6 +5,7 @@ import dev.vality.wallets.hooker.config.KafkaPostgresqlSpringBootITest; import dev.vality.wallets.hooker.handler.TestBeanFactory; import dev.vality.wallets.hooker.service.WebHookMessageSenderService; +import dev.vality.wallets.hooker.service.WithdrawalClient; import dev.vality.wallets.hooker.service.kafka.DestinationEventService; import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService; import dev.vality.webhook.dispatcher.WebhookMessage; @@ -15,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.Duration; import java.util.ArrayList; @@ -29,6 +31,9 @@ @TestPropertySource(properties = "merchant.callback.timeout=1") class WebhookServiceTest { + @MockitoBean + private WithdrawalClient withdrawalClient; + private static final String TEST = "/test"; private static final String URL_2 = TEST + "/qwe"; From 6f6a8418dedce08586d3881b73f95affc0baeb97 Mon Sep 17 00:00:00 2001 From: vitaxa Date: Thu, 29 Jan 2026 16:35:13 +0300 Subject: [PATCH 2/7] bump testcontainers annotations lib --- pom.xml | 2 +- src/main/resources/application.yml | 3 ++- .../hooker/config/KafkaPostgresqlSpringBootITest.java | 6 ++++-- .../java/dev/vality/wallets/hooker/kafka/KafkaProducer.java | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ca5402c..54a30d7 100644 --- a/pom.xml +++ b/pom.xml @@ -184,7 +184,7 @@ dev.vality testcontainers-annotations - 3.0.2 + 4.0.0 test diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8a3709a..d4567e1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -99,4 +99,5 @@ testcontainers: postgresql: tag: '11.4' kafka: - tag: '6.2.0' + confluent: + tag: '6.2.0' diff --git a/src/test/java/dev/vality/wallets/hooker/config/KafkaPostgresqlSpringBootITest.java b/src/test/java/dev/vality/wallets/hooker/config/KafkaPostgresqlSpringBootITest.java index ccf23b7..42a74eb 100644 --- a/src/test/java/dev/vality/wallets/hooker/config/KafkaPostgresqlSpringBootITest.java +++ b/src/test/java/dev/vality/wallets/hooker/config/KafkaPostgresqlSpringBootITest.java @@ -1,7 +1,8 @@ package dev.vality.wallets.hooker.config; -import dev.vality.testcontainers.annotations.KafkaConfig; +import dev.vality.testcontainers.annotations.KafkaTestConfig; import dev.vality.testcontainers.annotations.kafka.KafkaTestcontainerSingleton; +import dev.vality.testcontainers.annotations.kafka.constants.Provider; import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton; import org.springframework.boot.test.context.SpringBootTest; @@ -14,6 +15,7 @@ @Retention(RetentionPolicy.RUNTIME) @PostgresqlTestcontainerSingleton @KafkaTestcontainerSingleton( + provider = Provider.CONFLUENT, properties = { "kafka.topic.wallet.listener.enabled=true", "kafka.topic.withdrawal.listener.enabled=true", @@ -24,6 +26,6 @@ "kafka.topic.withdrawal.name", "kafka.topic.destination.name"}) @SpringBootTest -@KafkaConfig +@KafkaTestConfig public @interface KafkaPostgresqlSpringBootITest { } diff --git a/src/test/java/dev/vality/wallets/hooker/kafka/KafkaProducer.java b/src/test/java/dev/vality/wallets/hooker/kafka/KafkaProducer.java index 5e4190d..ad2fbe6 100644 --- a/src/test/java/dev/vality/wallets/hooker/kafka/KafkaProducer.java +++ b/src/test/java/dev/vality/wallets/hooker/kafka/KafkaProducer.java @@ -2,7 +2,7 @@ import dev.vality.machinegun.eventsink.MachineEvent; import dev.vality.machinegun.eventsink.SinkEvent; -import dev.vality.testcontainers.annotations.kafka.config.KafkaProducerConfig; +import dev.vality.testcontainers.annotations.kafka.config.KafkaProducerTestConfig; import lombok.extern.slf4j.Slf4j; import org.apache.thrift.TBase; import org.springframework.beans.factory.annotation.Autowired; @@ -13,7 +13,7 @@ import java.time.format.DateTimeFormatter; @TestComponent -@Import(KafkaProducerConfig.class) +@Import(KafkaProducerTestConfig.class) @Slf4j public class KafkaProducer { From 7b3a5741023d6284da2bcbd0efbea346794e27ff Mon Sep 17 00:00:00 2001 From: vitaxa Date: Thu, 29 Jan 2026 16:56:52 +0300 Subject: [PATCH 3/7] bump testcontainers annotations lib [2] --- pom.xml | 2 +- src/main/resources/application.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 54a30d7..7b5d431 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ dev.vality service-parent-pom - 3.1.7 + 3.1.9 wallets-hooker diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d4567e1..37e3fc5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -100,4 +100,4 @@ testcontainers: tag: '11.4' kafka: confluent: - tag: '6.2.0' + tag: '7.8.0' From 7bf25b83b526b52e5ad6cea4c89cf40b14a4dd92 Mon Sep 17 00:00:00 2001 From: vitaxa Date: Thu, 29 Jan 2026 17:13:30 +0300 Subject: [PATCH 4/7] testcontainer properties [WIP] --- src/test/resources/docker-java.properties | 1 + src/test/resources/testcontainers.properties | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 src/test/resources/docker-java.properties create mode 100644 src/test/resources/testcontainers.properties diff --git a/src/test/resources/docker-java.properties b/src/test/resources/docker-java.properties new file mode 100644 index 0000000..e1af86b --- /dev/null +++ b/src/test/resources/docker-java.properties @@ -0,0 +1 @@ +api.version=1.44 \ No newline at end of file diff --git a/src/test/resources/testcontainers.properties b/src/test/resources/testcontainers.properties new file mode 100644 index 0000000..365f6bd --- /dev/null +++ b/src/test/resources/testcontainers.properties @@ -0,0 +1,8 @@ +docker.api.version=1.44 +docker.client.strategy=org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy +testcontainers.timeout=120 +testcontainers.reuse.enable=false +testcontainers.kafka.enabled=false +testcontainers.clickhouse.enabled=false +testcontainers.minio.enabled=false +testcontainers.opensearch.enabled=false From 2863210c35eca3c36ab92f8a83e2c83099d8fa9d Mon Sep 17 00:00:00 2001 From: vitaxa Date: Thu, 29 Jan 2026 17:46:42 +0300 Subject: [PATCH 5/7] fix tests --- .../hooker/dao/webhook/WebHookDaoImplTest.java | 9 +++++++++ .../hooker/handler/DestinationEventHandlerTest.java | 11 +++++++++-- .../hooker/handler/WithdrawalEventHandlerTest.java | 4 ++++ .../wallets/hooker/kafka/WebhookServiceTest.java | 4 ++++ src/test/resources/docker-java.properties | 1 - src/test/resources/testcontainers.properties | 8 -------- 6 files changed, 26 insertions(+), 11 deletions(-) delete mode 100644 src/test/resources/docker-java.properties delete mode 100644 src/test/resources/testcontainers.properties diff --git a/src/test/java/dev/vality/wallets/hooker/dao/webhook/WebHookDaoImplTest.java b/src/test/java/dev/vality/wallets/hooker/dao/webhook/WebHookDaoImplTest.java index 609a5d3..ab8d744 100644 --- a/src/test/java/dev/vality/wallets/hooker/dao/webhook/WebHookDaoImplTest.java +++ b/src/test/java/dev/vality/wallets/hooker/dao/webhook/WebHookDaoImplTest.java @@ -4,8 +4,11 @@ import dev.vality.wallets.hooker.domain.WebHookModel; import dev.vality.wallets.hooker.domain.enums.EventType; import dev.vality.wallets.hooker.domain.tables.pojos.Webhook; +import dev.vality.wallets.hooker.service.WithdrawalClient; +import dev.vality.fistful.withdrawal.ManagementSrv; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.util.LinkedHashSet; import java.util.List; @@ -21,6 +24,12 @@ public class WebHookDaoImplTest { @Autowired private WebHookDao webHookDao; + @MockitoBean + private WithdrawalClient withdrawalClient; + + @MockitoBean + private ManagementSrv.Iface withdrawalFistfulClient; + @Test void create() { LinkedHashSet eventTypes = new LinkedHashSet<>(); diff --git a/src/test/java/dev/vality/wallets/hooker/handler/DestinationEventHandlerTest.java b/src/test/java/dev/vality/wallets/hooker/handler/DestinationEventHandlerTest.java index 1231ede..591f0c3 100644 --- a/src/test/java/dev/vality/wallets/hooker/handler/DestinationEventHandlerTest.java +++ b/src/test/java/dev/vality/wallets/hooker/handler/DestinationEventHandlerTest.java @@ -5,8 +5,9 @@ import dev.vality.wallets.hooker.domain.WebHookModel; import dev.vality.wallets.hooker.domain.enums.EventType; import dev.vality.wallets.hooker.service.WebHookMessageSenderService; +import dev.vality.wallets.hooker.service.WithdrawalClient; +import dev.vality.fistful.withdrawal.ManagementSrv; import dev.vality.wallets.hooker.service.kafka.DestinationEventService; -import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +36,12 @@ class DestinationEventHandlerTest { @MockitoBean private WebHookMessageSenderService webHookMessageSenderService; + @MockitoBean + private WithdrawalClient withdrawalClient; + + @MockitoBean + private ManagementSrv.Iface withdrawalFistfulClient; + @Test void failHandleDestinationCreated() { WebHookModel webhook = TestBeanFactory.createWebhookModel(); @@ -61,4 +68,4 @@ void handleDestinationCreatedAndAccountChange() { verify(webHookMessageSenderService, times(1)) .send(any()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java index 47a41e9..32c0254 100644 --- a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java +++ b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java @@ -6,6 +6,7 @@ import dev.vality.wallets.hooker.domain.WebHookModel; import dev.vality.wallets.hooker.service.WebHookMessageSenderService; import dev.vality.wallets.hooker.service.WithdrawalClient; +import dev.vality.fistful.withdrawal.ManagementSrv; import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +34,9 @@ class WithdrawalEventHandlerTest { @MockitoBean private WithdrawalClient withdrawalClient; + @MockitoBean + private ManagementSrv.Iface withdrawalFistfulClient; + @Test void handleWithdrawalCreatedAndAndStatusChange() throws InterruptedException { WebHookModel webhook = TestBeanFactory.createWebhookModel(); diff --git a/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java b/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java index bb1f656..86ab081 100644 --- a/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java +++ b/src/test/java/dev/vality/wallets/hooker/kafka/WebhookServiceTest.java @@ -6,6 +6,7 @@ import dev.vality.wallets.hooker.handler.TestBeanFactory; import dev.vality.wallets.hooker.service.WebHookMessageSenderService; import dev.vality.wallets.hooker.service.WithdrawalClient; +import dev.vality.fistful.withdrawal.ManagementSrv; import dev.vality.wallets.hooker.service.kafka.DestinationEventService; import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService; import dev.vality.webhook.dispatcher.WebhookMessage; @@ -34,6 +35,9 @@ class WebhookServiceTest { @MockitoBean private WithdrawalClient withdrawalClient; + @MockitoBean + private ManagementSrv.Iface withdrawalFistfulClient; + private static final String TEST = "/test"; private static final String URL_2 = TEST + "/qwe"; diff --git a/src/test/resources/docker-java.properties b/src/test/resources/docker-java.properties deleted file mode 100644 index e1af86b..0000000 --- a/src/test/resources/docker-java.properties +++ /dev/null @@ -1 +0,0 @@ -api.version=1.44 \ No newline at end of file diff --git a/src/test/resources/testcontainers.properties b/src/test/resources/testcontainers.properties deleted file mode 100644 index 365f6bd..0000000 --- a/src/test/resources/testcontainers.properties +++ /dev/null @@ -1,8 +0,0 @@ -docker.api.version=1.44 -docker.client.strategy=org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy -testcontainers.timeout=120 -testcontainers.reuse.enable=false -testcontainers.kafka.enabled=false -testcontainers.clickhouse.enabled=false -testcontainers.minio.enabled=false -testcontainers.opensearch.enabled=false From 23a9d7eb95810411af83ee31f32571f09775357b Mon Sep 17 00:00:00 2001 From: vitaxa Date: Fri, 30 Jan 2026 11:27:17 +0300 Subject: [PATCH 6/7] review fix --- .../hooker/config/FistfulClientConfig.java | 28 ++++++++++++ .../wallets/hooker/config/RetryConfig.java | 43 +++++++++++++++++++ ...awalStatusChangedHookMessageGenerator.java | 2 +- .../hooker/service/WithdrawalClient.java | 25 +++++++---- .../wallets/hooker/utils/CashFlowUtils.java | 17 +++----- src/main/resources/application.yml | 5 +++ 6 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 src/main/java/dev/vality/wallets/hooker/config/FistfulClientConfig.java create mode 100644 src/main/java/dev/vality/wallets/hooker/config/RetryConfig.java diff --git a/src/main/java/dev/vality/wallets/hooker/config/FistfulClientConfig.java b/src/main/java/dev/vality/wallets/hooker/config/FistfulClientConfig.java new file mode 100644 index 0000000..c7fe96b --- /dev/null +++ b/src/main/java/dev/vality/wallets/hooker/config/FistfulClientConfig.java @@ -0,0 +1,28 @@ +package dev.vality.wallets.hooker.config; + +import dev.vality.fistful.withdrawal.ManagementSrv; +import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.net.URI; + +@Configuration +public class FistfulClientConfig { + + @Value("${service.withdrawal.url}") + private String withdrawalUrl; + + @Value("${service.withdrawal.networkTimeout}") + private int networkTimeout; + + @Bean + public ManagementSrv.Iface withdrawalFistfulClient() throws IOException { + return new THSpawnClientBuilder() + .withNetworkTimeout(networkTimeout) + .withAddress(URI.create(withdrawalUrl)) + .build(ManagementSrv.Iface.class); + } +} \ No newline at end of file diff --git a/src/main/java/dev/vality/wallets/hooker/config/RetryConfig.java b/src/main/java/dev/vality/wallets/hooker/config/RetryConfig.java new file mode 100644 index 0000000..3f8a762 --- /dev/null +++ b/src/main/java/dev/vality/wallets/hooker/config/RetryConfig.java @@ -0,0 +1,43 @@ +package dev.vality.wallets.hooker.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; + +@Slf4j +@Configuration +public class RetryConfig { + + @Value("${service.withdrawal.retry.maxAttempts}") + private int maxAttempts; + + @Value("${service.withdrawal.retry.initialIntervalMs}") + private long initialIntervalMs; + + @Value("${service.withdrawal.retry.multiplier}") + private double multiplier; + + @Value("${service.withdrawal.retry.maxIntervalMs}") + private long maxIntervalMs; + + @Bean + public RetryTemplate withdrawalRetryTemplate() { + var retryTemplate = new RetryTemplate(); + + var retryPolicy = new SimpleRetryPolicy(); + retryPolicy.setMaxAttempts(maxAttempts); + retryTemplate.setRetryPolicy(retryPolicy); + + var backOffPolicy = new ExponentialBackOffPolicy(); + backOffPolicy.setInitialInterval(initialIntervalMs); + backOffPolicy.setMultiplier(multiplier); + backOffPolicy.setMaxInterval(maxIntervalMs); + retryTemplate.setBackOffPolicy(backOffPolicy); + + return retryTemplate; + } +} diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java index b92f8f4..987ded3 100644 --- a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/generator/WithdrawalStatusChangedHookMessageGenerator.java @@ -128,7 +128,7 @@ private Fee calculateFee(String withdrawalId, Long eventId) { if (withdrawalState != null && withdrawalState.getEffectiveFinalCashFlow() != null && withdrawalState.getEffectiveFinalCashFlow().getPostings() != null) { - long amount = CashFlowUtils.getFistfulFee(withdrawalState.getEffectiveFinalCashFlow().getPostings()); + long amount = CashFlowUtils.getWithdrawalFee(withdrawalState.getEffectiveFinalCashFlow().getPostings()); String currency = withdrawalState.getBody().getCurrency().getSymbolicCode(); return new Fee().amount(amount).currency(currency); } diff --git a/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java b/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java index a4586e9..c71c372 100644 --- a/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java +++ b/src/main/java/dev/vality/wallets/hooker/service/WithdrawalClient.java @@ -5,6 +5,7 @@ import dev.vality.fistful.withdrawal.WithdrawalState; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; @Slf4j @@ -13,17 +14,25 @@ public class WithdrawalClient { private final ManagementSrv.Iface withdrawalFistfulClient; + private final RetryTemplate retryTemplate; public WithdrawalState getWithdrawalInfo(String withdrawalId, long eventId) { try { - log.debug("Fetching withdrawal state for withdrawalId={}, eventId={}", withdrawalId, eventId); - WithdrawalState withdrawalState = withdrawalFistfulClient.get(withdrawalId, createEventRange(eventId)); - if (withdrawalState == null) { - log.warn("Withdrawal not found for withdrawalId={}, eventId={}", withdrawalId, eventId); - throw new RuntimeException("Withdrawal not found!"); - } - log.debug("Successfully fetched withdrawal state for withdrawalId={}", withdrawalId); - return withdrawalState; + return retryTemplate.execute(context -> { + log.debug("Attempt {} to fetch withdrawal state for withdrawalId={}, eventId={}", + context.getRetryCount() + 1, withdrawalId, eventId); + + WithdrawalState withdrawalState = withdrawalFistfulClient.get(withdrawalId, createEventRange(eventId)); + if (withdrawalState == null) { + log.warn("Withdrawal not found for withdrawalId={}, eventId={} (attempt {})", + withdrawalId, eventId, context.getRetryCount() + 1); + throw new RuntimeException("Withdrawal not found!"); + } + + log.debug("Successfully fetched withdrawal state for withdrawalId={} (attempt {})", + withdrawalId, context.getRetryCount() + 1); + return withdrawalState; + }); } catch (Exception e) { log.error("Error fetching withdrawal state for withdrawalId={}, eventId={}", withdrawalId, eventId, e); throw new RuntimeException("Failed to fetch withdrawal state", e); diff --git a/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java index d10df7b..6fa6d68 100644 --- a/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java +++ b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java @@ -5,25 +5,20 @@ import lombok.NoArgsConstructor; import java.util.List; -import java.util.function.Predicate; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class CashFlowUtils { - public static long getFistfulFee(List postings) { - return getFistfulAmount( - postings, - posting -> posting.getSource().getAccountType().isSetWallet() - && posting.getDestination().getAccountType().isSetSystem() - ); + public static long getWithdrawalFee(List postings) { + return getMerchantFee(postings); } - public static long getFistfulAmount( - List postings, - Predicate filter + private static long getMerchantFee( + List postings ) { return postings.stream() - .filter(filter) + .filter(posting -> posting.getSource().getAccountType().isSetWallet() + && posting.getDestination().getAccountType().isSetSystem()) .map(posting -> posting.getVolume().getAmount()) .reduce(0L, Long::sum); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 37e3fc5..95ef260 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -94,6 +94,11 @@ service: withdrawal: url: http://fistful:8022/v1/ networkTimeout: 5000 + retry: + maxAttempts: 10 + initialIntervalMs: 1000 + multiplier: 2.0 + maxIntervalMs: 10000 testcontainers: postgresql: From 5e441af92617e128229f0173a2f1bcd48b7ee0fc Mon Sep 17 00:00:00 2001 From: vitaxa Date: Fri, 30 Jan 2026 12:04:31 +0300 Subject: [PATCH 7/7] review fix [2] --- .../java/dev/vality/wallets/hooker/utils/CashFlowUtils.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java index 6fa6d68..fc539ed 100644 --- a/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java +++ b/src/main/java/dev/vality/wallets/hooker/utils/CashFlowUtils.java @@ -10,12 +10,6 @@ public class CashFlowUtils { public static long getWithdrawalFee(List postings) { - return getMerchantFee(postings); - } - - private static long getMerchantFee( - List postings - ) { return postings.stream() .filter(posting -> posting.getSource().getAccountType().isSetWallet() && posting.getDestination().getAccountType().isSetSystem())