Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>dev.vality</groupId>
<artifactId>service-parent-pom</artifactId>
<version>3.1.9</version>
<version>3.1.11</version>
</parent>

<artifactId>wallets-hooker</artifactId>
Expand Down Expand Up @@ -124,16 +124,17 @@
<dependency>
<groupId>dev.vality</groupId>
<artifactId>damsel</artifactId>
<version>1.698-99c91c9</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>fistful-proto</artifactId>
<version>1.180-8b04c7f</version>
<version>1.191-94b75ce</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>swag-wallets-webhook-events-server</artifactId>
<version>1.42-d30b8f0</version>
<version>1.43-1b943c0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +24,7 @@
import org.apache.thrift.TBase;

import java.util.LinkedHashSet;
import java.util.List;

public class TestBeanFactory {

Expand Down Expand Up @@ -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())));

Expand All @@ -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<EventType> eventTypes = new LinkedHashSet<>();
eventTypes.add(EventType.WITHDRAWAL_CREATED);
Expand All @@ -153,4 +184,26 @@ private static <T extends TBase> 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));
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
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;

import java.util.List;
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 {
Expand All @@ -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);

Expand All @@ -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<WebhookMessage> 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\"}")));
}
}
Loading