diff --git a/pom.xml b/pom.xml
index 7b5d431..85e3b56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
dev.vality
service-parent-pom
- 3.1.9
+ 3.1.11
wallets-hooker
@@ -124,16 +124,17 @@
dev.vality
damsel
+ 1.698-99c91c9
dev.vality
fistful-proto
- 1.180-8b04c7f
+ 1.191-94b75ce
dev.vality
swag-wallets-webhook-events-server
- 1.42-d30b8f0
+ 1.43-1b943c0
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 987ded3..c3d1a82 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
@@ -2,10 +2,13 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.vality.fistful.base.Cash;
+import dev.vality.fistful.withdrawal.WithdrawalState;
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.WithdrawalBody;
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;
@@ -103,11 +106,13 @@ private String initRequestBody(
withdrawalFailed.setTopic(Event.TopicEnum.WITHDRAWAL_TOPIC);
return objectMapper.writeValueAsString(withdrawalFailed);
} else if (status.isSetSucceeded()) {
- Fee fee = calculateFee(withdrawalId, eventId);
+ WithdrawalState withdrawalState = getWithdrawalState(withdrawalId, eventId);
+ Fee fee = calculateFee(withdrawalId, eventId, withdrawalState);
WithdrawalSucceeded withdrawalSucceeded = new WithdrawalSucceeded()
.withdrawalID(withdrawalId)
.externalID(externalId)
- .fee(fee);
+ .fee(fee)
+ .body(initNewBody(withdrawalState));
withdrawalSucceeded.setEventType(Event.EventTypeEnum.WITHDRAWAL_SUCCEEDED);
withdrawalSucceeded.setEventID(eventId.toString());
withdrawalSucceeded.setOccuredAt(OffsetDateTime.parse(createdAt));
@@ -122,24 +127,46 @@ private String initRequestBody(
}
}
- private Fee calculateFee(String withdrawalId, Long eventId) {
+ private WithdrawalState getWithdrawalState(String withdrawalId, Long eventId) {
try {
- var withdrawalState = withdrawalClient.getWithdrawalInfo(withdrawalId, eventId);
- if (withdrawalState != null
- && withdrawalState.getEffectiveFinalCashFlow() != null
- && withdrawalState.getEffectiveFinalCashFlow().getPostings() != null) {
- long amount = CashFlowUtils.getWithdrawalFee(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;
+ return withdrawalClient.getWithdrawalInfo(withdrawalId, eventId);
} catch (Exception e) {
- log.warn("Error calculating fee for withdrawalId={}, eventId={}: {}",
+ log.warn("Error getting withdrawal state for withdrawalId={}, eventId={}: {}",
withdrawalId, eventId, e.getMessage());
return null;
}
}
+ private Fee calculateFee(String withdrawalId, Long eventId, WithdrawalState withdrawalState) {
+ if (withdrawalState != null
+ && withdrawalState.getEffectiveFinalCashFlow() != null
+ && withdrawalState.getEffectiveFinalCashFlow().getPostings() != null) {
+ long amount = CashFlowUtils.getWithdrawalFee(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;
+ }
+
+ private WithdrawalBody initNewBody(WithdrawalState withdrawalState) {
+ if (!amountChanged(withdrawalState)) {
+ return null;
+ }
+ return initBody(withdrawalState.getNewBody());
+ }
+
+ private boolean amountChanged(WithdrawalState withdrawalState) {
+ return withdrawalState != null
+ && withdrawalState.isSetNewBody();
+ }
+
+ private WithdrawalBody initBody(Cash body) {
+ var withdrawalBody = new WithdrawalBody();
+ withdrawalBody.setAmount(body.getAmount());
+ withdrawalBody.setCurrency(body.getCurrency().getSymbolicCode());
+ return withdrawalBody;
+ }
+
}
diff --git a/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java b/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java
index 33a175e..816455c 100644
--- a/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java
+++ b/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java
@@ -2,11 +2,18 @@
import dev.vality.fistful.account.Account;
import dev.vality.fistful.base.*;
+import dev.vality.fistful.cashflow.CashFlowAccount;
+import dev.vality.fistful.cashflow.FinalCashFlow;
+import dev.vality.fistful.cashflow.FinalCashFlowAccount;
+import dev.vality.fistful.cashflow.FinalCashFlowPosting;
+import dev.vality.fistful.cashflow.SystemCashFlowAccount;
+import dev.vality.fistful.cashflow.WalletCashFlowAccount;
import dev.vality.fistful.destination.Destination;
import dev.vality.fistful.destination.TimestampedChange;
import dev.vality.fistful.withdrawal.CreatedChange;
import dev.vality.fistful.withdrawal.StatusChange;
import dev.vality.fistful.withdrawal.Withdrawal;
+import dev.vality.fistful.withdrawal.WithdrawalState;
import dev.vality.fistful.withdrawal.status.Status;
import dev.vality.fistful.withdrawal.status.Succeeded;
import dev.vality.kafka.common.serialization.ThriftSerializer;
@@ -17,6 +24,7 @@
import org.apache.thrift.TBase;
import java.util.LinkedHashSet;
+import java.util.List;
public class TestBeanFactory {
@@ -112,6 +120,10 @@ public static MachineEvent createWithdrawalEvent() {
}
public static MachineEvent createWithdrawalSucceeded() {
+ return createWithdrawalSucceeded(67L);
+ }
+
+ public static MachineEvent createWithdrawalSucceeded(Long eventId) {
dev.vality.fistful.withdrawal.Change change = new dev.vality.fistful.withdrawal.Change();
change.setStatusChanged(new StatusChange().setStatus(Status.succeeded(new Succeeded())));
@@ -122,11 +134,30 @@ public static MachineEvent createWithdrawalSucceeded() {
return machineEvent(
WITHDRAWAL_ID,
- 67L,
+ eventId,
new ThriftSerializer<>(),
timestampedChange);
}
+ public static WithdrawalState createWithdrawalState() {
+ return new WithdrawalState()
+ .setId(WITHDRAWAL_ID)
+ .setDestinationId(DESTINATION)
+ .setExternalId("extId")
+ .setWalletId(SOURCE_WALLET_ID)
+ .setPartyId(PARTY_ID)
+ .setCreatedAt("2025-03-22T06:12:27Z")
+ .setDomainRevision(1L)
+ .setBody(createCash(1000));
+ }
+
+ public static WithdrawalState createWithdrawalStateWithNewBody() {
+ return createWithdrawalState()
+ .setNewBody(createCash(1500, "USD"))
+ .setEffectiveFinalCashFlow(new FinalCashFlow()
+ .setPostings(List.of(createFeePosting())));
+ }
+
public static WebHookModel createWebhookModel() {
LinkedHashSet eventTypes = new LinkedHashSet<>();
eventTypes.add(EventType.WITHDRAWAL_CREATED);
@@ -153,4 +184,26 @@ private static MachineEvent machineEvent(
.setCreatedAt("2016-03-22T06:12:27Z")
.setData(Value.bin(depositChangeSerializer.serialize("", change)));
}
+
+ private static Cash createCash(long amount) {
+ return createCash(amount, "RUB");
+ }
+
+ private static Cash createCash(long amount, String currencyCode) {
+ Cash body = new Cash();
+ body.setAmount(amount);
+ CurrencyRef currency = new CurrencyRef();
+ currency.setSymbolicCode(currencyCode);
+ body.setCurrency(currency);
+ return body;
+ }
+
+ private static FinalCashFlowPosting createFeePosting() {
+ return new FinalCashFlowPosting()
+ .setSource(new FinalCashFlowAccount()
+ .setAccountType(CashFlowAccount.wallet(WalletCashFlowAccount.sender_source)))
+ .setDestination(new FinalCashFlowAccount()
+ .setAccountType(CashFlowAccount.system(SystemCashFlowAccount.settlement)))
+ .setVolume(createCash(25));
+ }
}
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 32c0254..cbff401 100644
--- a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java
+++ b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java
@@ -1,14 +1,17 @@
package dev.vality.wallets.hooker.handler;
+import dev.vality.fistful.withdrawal.ManagementSrv;
import dev.vality.machinegun.eventsink.MachineEvent;
import dev.vality.wallets.hooker.config.PostgresqlSpringBootITest;
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.fistful.withdrawal.ManagementSrv;
import dev.vality.wallets.hooker.service.kafka.WithdrawalEventService;
+import dev.vality.webhook.dispatcher.WebhookMessage;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -16,8 +19,11 @@
import java.util.concurrent.CountDownLatch;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@PostgresqlSpringBootITest
class WithdrawalEventHandlerTest {
@@ -40,6 +46,8 @@ class WithdrawalEventHandlerTest {
@Test
void handleWithdrawalCreatedAndAndStatusChange() throws InterruptedException {
WebHookModel webhook = TestBeanFactory.createWebhookModel();
+ when(withdrawalClient.getWithdrawalInfo(eq(TestBeanFactory.WITHDRAWAL_ID), anyLong()))
+ .thenReturn(TestBeanFactory.createWithdrawalState());
webHookDao.create(webhook);
@@ -56,4 +64,26 @@ void handleWithdrawalCreatedAndAndStatusChange() throws InterruptedException {
latch.await();
}
+
+ @Test
+ void handleWithdrawalSucceededWithNewBodyInSucceededWebhook() {
+ WebHookModel webhook = TestBeanFactory.createWebhookModel();
+ when(withdrawalClient.getWithdrawalInfo(TestBeanFactory.WITHDRAWAL_ID, 68L))
+ .thenReturn(TestBeanFactory.createWithdrawalStateWithNewBody());
+
+ webHookDao.create(webhook);
+
+ withdrawalEventService.handleEvents(List.of(TestBeanFactory.createWithdrawalEvent()));
+ withdrawalEventService.handleEvents(List.of(TestBeanFactory.createWithdrawalSucceeded(68L)));
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(WebhookMessage.class);
+ verify(webHookMessageSenderService, timeout(1000L).times(2))
+ .send(captor.capture());
+ Assertions.assertTrue(captor.getAllValues().stream()
+ .map(WebhookMessage::getRequestBody)
+ .map(String::new)
+ .anyMatch(body -> body.contains("\"eventType\":\"WithdrawalSucceeded\"")
+ && body.contains("\"body\":{\"amount\":1500,\"currency\":\"USD\"}")
+ && body.contains("\"fee\":{\"amount\":25,\"currency\":\"RUB\"}")));
+ }
}