diff --git a/pom.xml b/pom.xml index 85e3b56..7766f2d 100644 --- a/pom.xml +++ b/pom.xml @@ -134,7 +134,7 @@ dev.vality swag-wallets-webhook-events-server - 1.43-1b943c0 + 1.45-4390ebb com.google.guava diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalAdjustmentSucceededHandler.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalAdjustmentSucceededHandler.java new file mode 100644 index 0000000..9723e72 --- /dev/null +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalAdjustmentSucceededHandler.java @@ -0,0 +1,58 @@ +package dev.vality.wallets.hooker.handler.withdrawal; + +import dev.vality.fistful.withdrawal.TimestampedChange; +import dev.vality.fistful.withdrawal.WithdrawalState; +import dev.vality.machinegun.eventsink.MachineEvent; +import dev.vality.wallets.hooker.domain.enums.EventType; +import dev.vality.wallets.hooker.service.WithdrawalClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WithdrawalAdjustmentSucceededHandler implements WithdrawalEventHandler { + + private final WithdrawalChangeStatusHandler withdrawalChangeStatusHandler; + private final WithdrawalClient withdrawalClient; + + @Override + public boolean accept(TimestampedChange change) { + return change.getChange().isSetAdjustment() + && change.getChange().getAdjustment().isSetPayload() + && change.getChange().getAdjustment().getPayload().isSetStatusChanged() + && change.getChange().getAdjustment().getPayload().getStatusChanged().isSetStatus() + && change.getChange().getAdjustment().getPayload().getStatusChanged().getStatus().isSetSucceeded(); + } + + @Override + public void handle(TimestampedChange change, MachineEvent event) { + String withdrawalId = event.getSourceId(); + WithdrawalState withdrawalState = getWithdrawalState(withdrawalId, event.getEventId()); + if (withdrawalState != null + && withdrawalState.getStatus() != null + && withdrawalState.getStatus().isSetSucceeded()) { + log.info("Start handling WithdrawalAdjustmentChange: withdrawalId={} change={}", withdrawalId, change); + + withdrawalChangeStatusHandler.handleChangeStatus( + change, + event, + withdrawalId, + EventType.WITHDRAWAL_SUCCEEDED, + withdrawalState); + + log.info("Finish handling WithdrawalAdjustmentChange: withdrawalId={}", withdrawalId); + } + } + + private WithdrawalState getWithdrawalState(String withdrawalId, Long eventId) { + try { + return withdrawalClient.getWithdrawalInfo(withdrawalId, eventId); + } catch (Exception e) { + log.warn("Error getting withdrawal state for withdrawalId={}, eventId={}: {}", + withdrawalId, eventId, e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalChangeStatusHandler.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalChangeStatusHandler.java index 1328f90..77d7d25 100644 --- a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalChangeStatusHandler.java +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalChangeStatusHandler.java @@ -1,7 +1,7 @@ package dev.vality.wallets.hooker.handler.withdrawal; -import dev.vality.fistful.withdrawal.StatusChange; import dev.vality.fistful.withdrawal.TimestampedChange; +import dev.vality.fistful.withdrawal.WithdrawalState; import dev.vality.machinegun.eventsink.MachineEvent; import dev.vality.wallets.hooker.dao.webhook.WebHookDao; import dev.vality.wallets.hooker.dao.withdrawal.WithdrawalReferenceDao; @@ -35,7 +35,8 @@ public void handleChangeStatus( TimestampedChange change, MachineEvent event, String withdrawalId, - EventType eventType) { + EventType eventType, + WithdrawalState withdrawalState) { try { var reference = waitReferenceWithdrawal(withdrawalId); Long parentId = Long.valueOf(reference.getEventId()); @@ -44,13 +45,14 @@ public void handleChangeStatus( .filter(webHook -> webHook.getWalletId() == null || webHook.getWalletId().equals(reference.getWalletId())) .map(webhook -> generateWithdrawalStatusChangeHookMsg( - change.getChange().getStatusChanged(), + change, webhook, withdrawalId, event.getEventId(), parentId, event.getCreatedAt(), - reference.getExternalId())) + reference.getExternalId(), + withdrawalState)) .forEach(webHookMessageSenderService::send); } catch (Exception e) { log.error("Error while handling WithdrawalStatusChangedChange: {}, withdrawalId: {}", @@ -76,22 +78,24 @@ private WithdrawalReference waitReferenceWithdrawal(String withdrawalId) { } private WebhookMessage generateWithdrawalStatusChangeHookMsg( - StatusChange statusChanged, + TimestampedChange change, WebHookModel webhook, String withdrawalId, long eventId, Long parentId, String createdAt, - String externalId) { + String externalId, + WithdrawalState withdrawalState) { MessageGenParams messageGenParams = MessageGenParams.builder() .sourceId(withdrawalId) .eventId(eventId) .parentId(parentId) .createdAt(createdAt) .externalId(externalId) + .withdrawalState(withdrawalState) .build(); - return withdrawalStatusChangedHookMessageGenerator.generate(statusChanged, webhook, messageGenParams); + return withdrawalStatusChangedHookMessageGenerator.generate(change, webhook, messageGenParams); } } diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalFailedHandler.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalFailedHandler.java index 6477c24..f4f6cd3 100644 --- a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalFailedHandler.java +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalFailedHandler.java @@ -1,8 +1,10 @@ package dev.vality.wallets.hooker.handler.withdrawal; import dev.vality.fistful.withdrawal.TimestampedChange; +import dev.vality.fistful.withdrawal.WithdrawalState; import dev.vality.machinegun.eventsink.MachineEvent; import dev.vality.wallets.hooker.domain.enums.EventType; +import dev.vality.wallets.hooker.service.WithdrawalClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -14,6 +16,7 @@ public class WithdrawalFailedHandler implements WithdrawalEventHandler { private final WithdrawalChangeStatusHandler withdrawalChangeStatusHandler; + @Override public boolean accept(TimestampedChange change) { return change.getChange().isSetStatusChanged() @@ -30,8 +33,10 @@ public void handle(TimestampedChange change, MachineEvent event) { change, event, withdrawalId, - EventType.WITHDRAWAL_FAILED); + EventType.WITHDRAWAL_FAILED, + null); log.info("Finish handling WithdrawalFailedChange: withdrawalId={}", withdrawalId); } + } diff --git a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalSucceededHandler.java b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalSucceededHandler.java index f12cec0..751605e 100644 --- a/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalSucceededHandler.java +++ b/src/main/java/dev/vality/wallets/hooker/handler/withdrawal/WithdrawalSucceededHandler.java @@ -1,8 +1,10 @@ package dev.vality.wallets.hooker.handler.withdrawal; import dev.vality.fistful.withdrawal.TimestampedChange; +import dev.vality.fistful.withdrawal.WithdrawalState; import dev.vality.machinegun.eventsink.MachineEvent; import dev.vality.wallets.hooker.domain.enums.EventType; +import dev.vality.wallets.hooker.service.WithdrawalClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -13,6 +15,7 @@ public class WithdrawalSucceededHandler implements WithdrawalEventHandler { private final WithdrawalChangeStatusHandler withdrawalChangeStatusHandler; + private final WithdrawalClient withdrawalClient; @Override public boolean accept(TimestampedChange change) { @@ -24,14 +27,27 @@ public boolean accept(TimestampedChange change) { @Override public void handle(TimestampedChange change, MachineEvent event) { String withdrawalId = event.getSourceId(); + WithdrawalState withdrawalState = getWithdrawalState(withdrawalId, event.getEventId()); + log.info("Start handling WithdrawalSucceededChange: withdrawalId={} change={}", withdrawalId, change); withdrawalChangeStatusHandler.handleChangeStatus( change, event, withdrawalId, - EventType.WITHDRAWAL_SUCCEEDED); + EventType.WITHDRAWAL_SUCCEEDED, + withdrawalState); log.info("Finish handling WithdrawalSucceededChange: withdrawalId={}", withdrawalId); } -} + + private WithdrawalState getWithdrawalState(String withdrawalId, Long eventId) { + try { + return withdrawalClient.getWithdrawalInfo(withdrawalId, eventId); + } catch (Exception e) { + log.warn("Error getting withdrawal state for withdrawalId={}, eventId={}: {}", + withdrawalId, eventId, e.getMessage()); + return null; + } + } +} \ No newline at end of file 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 c3d1a82..be81b4c 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 @@ -3,14 +3,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import dev.vality.fistful.base.Cash; +import dev.vality.fistful.withdrawal.TimestampedChange; 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.swag.wallets.webhook.events.model.*; import dev.vality.wallets.hooker.domain.WebHookModel; import dev.vality.wallets.hooker.domain.enums.EventType; import dev.vality.wallets.hooker.exception.GenerateMessageException; @@ -18,7 +14,6 @@ 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; @@ -30,41 +25,41 @@ @Slf4j @Component -public class WithdrawalStatusChangedHookMessageGenerator extends BaseHookMessageGenerator { +public class WithdrawalStatusChangedHookMessageGenerator extends BaseHookMessageGenerator { - private final WebHookMessageGeneratorServiceImpl generatorService; + private final WebHookMessageGeneratorServiceImpl generatorService; private final ObjectMapper objectMapper; private final AdditionalHeadersGenerator additionalHeadersGenerator; - private final WithdrawalClient withdrawalClient; public WithdrawalStatusChangedHookMessageGenerator( - WebHookMessageGeneratorServiceImpl generatorService, + 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 protected WebhookMessage generateMessage( - StatusChange event, + TimestampedChange event, WebHookModel model, MessageGenParams messageGenParams) { try { String message = initRequestBody( - event.getStatus(), + event.getChange().isSetStatusChanged() + ? event.getChange().getStatusChanged().getStatus() + : messageGenParams.getWithdrawalState().getStatus(), messageGenParams.getSourceId(), messageGenParams.getEventId(), messageGenParams.getCreatedAt(), - messageGenParams.getExternalId()); + messageGenParams.getExternalId(), + messageGenParams.getWithdrawalState()); WebhookMessage webhookMessage = generatorService.generate(event, model, messageGenParams); - webhookMessage.setParentEventId(initPatenId(model, messageGenParams.getParentId())); + webhookMessage.setParentEventId(initParenId(model, messageGenParams.getParentId())); webhookMessage.setRequestBody(message.getBytes()); webhookMessage.setAdditionalHeaders(additionalHeadersGenerator.generate(model, message)); @@ -82,7 +77,7 @@ protected WebhookMessage generateMessage( } - private Long initPatenId(WebHookModel model, Long parentId) { + private Long initParenId(WebHookModel model, Long parentId) { if (model.getEventTypes() != null && model.getEventTypes().contains(EventType.WITHDRAWAL_CREATED)) { return parentId; } @@ -95,7 +90,8 @@ private String initRequestBody( String withdrawalId, Long eventId, String createdAt, - String externalId) throws JsonProcessingException { + String externalId, + WithdrawalState withdrawalState) throws JsonProcessingException { if (status.isSetFailed()) { WithdrawalFailed withdrawalFailed = new WithdrawalFailed() .withdrawalID(withdrawalId) @@ -106,7 +102,6 @@ private String initRequestBody( withdrawalFailed.setTopic(Event.TopicEnum.WITHDRAWAL_TOPIC); return objectMapper.writeValueAsString(withdrawalFailed); } else if (status.isSetSucceeded()) { - WithdrawalState withdrawalState = getWithdrawalState(withdrawalId, eventId); Fee fee = calculateFee(withdrawalId, eventId, withdrawalState); WithdrawalSucceeded withdrawalSucceeded = new WithdrawalSucceeded() .withdrawalID(withdrawalId) @@ -127,16 +122,6 @@ private String initRequestBody( } } - private WithdrawalState getWithdrawalState(String withdrawalId, Long eventId) { - try { - return withdrawalClient.getWithdrawalInfo(withdrawalId, eventId); - } catch (Exception e) { - 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 @@ -154,7 +139,7 @@ private WithdrawalBody initNewBody(WithdrawalState withdrawalState) { if (!amountChanged(withdrawalState)) { return null; } - return initBody(withdrawalState.getNewBody()); + return initBody(withdrawalState.getNewBody(), withdrawalState.getBody()); } private boolean amountChanged(WithdrawalState withdrawalState) { @@ -162,10 +147,11 @@ private boolean amountChanged(WithdrawalState withdrawalState) { && withdrawalState.isSetNewBody(); } - private WithdrawalBody initBody(Cash body) { + private WithdrawalBody initBody(Cash newBody, Cash oldBody) { var withdrawalBody = new WithdrawalBody(); - withdrawalBody.setAmount(body.getAmount()); - withdrawalBody.setCurrency(body.getCurrency().getSymbolicCode()); + withdrawalBody.setAmount(oldBody.getAmount()); + withdrawalBody.setChangedAmount(newBody.getAmount()); + withdrawalBody.setCurrency(newBody.getCurrency().getSymbolicCode()); return withdrawalBody; } diff --git a/src/main/java/dev/vality/wallets/hooker/model/MessageGenParams.java b/src/main/java/dev/vality/wallets/hooker/model/MessageGenParams.java index 2af9c47..53159cc 100644 --- a/src/main/java/dev/vality/wallets/hooker/model/MessageGenParams.java +++ b/src/main/java/dev/vality/wallets/hooker/model/MessageGenParams.java @@ -1,5 +1,6 @@ package dev.vality.wallets.hooker.model; +import dev.vality.fistful.withdrawal.WithdrawalState; import lombok.Builder; import lombok.Data; @@ -17,4 +18,6 @@ public class MessageGenParams { private String externalId; + private WithdrawalState withdrawalState; + } 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 816455c..953a3af 100644 --- a/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java +++ b/src/test/java/dev/vality/wallets/hooker/handler/TestBeanFactory.java @@ -2,18 +2,10 @@ 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.cashflow.*; 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.*; import dev.vality.fistful.withdrawal.status.Status; import dev.vality.fistful.withdrawal.status.Succeeded; import dev.vality.kafka.common.serialization.ThriftSerializer; @@ -139,6 +131,30 @@ public static MachineEvent createWithdrawalSucceeded(Long eventId) { timestampedChange); } + public static MachineEvent createWithdrawalAdjustmentChange(Long eventId) { + var status = new dev.vality.fistful.withdrawal.adjustment.StatusChange( + dev.vality.fistful.withdrawal.adjustment.Status.succeeded( + new dev.vality.fistful.withdrawal.adjustment.Succeeded())); + var payload = new dev.vality.fistful.withdrawal.adjustment.Change(); + payload.setStatusChanged(status); + var adjustment = new AdjustmentChange(); + adjustment.setId("1"); + adjustment.setPayload(payload); + dev.vality.fistful.withdrawal.Change change = new dev.vality.fistful.withdrawal.Change(); + change.setAdjustment(adjustment); + + dev.vality.fistful.withdrawal.TimestampedChange timestampedChange = + new dev.vality.fistful.withdrawal.TimestampedChange() + .setOccuredAt("2016-03-22T06:12:27Z") + .setChange(change); + + return machineEvent( + WITHDRAWAL_ID, + eventId, + new ThriftSerializer<>(), + timestampedChange); + } + public static WithdrawalState createWithdrawalState() { return new WithdrawalState() .setId(WITHDRAWAL_ID) @@ -153,6 +169,7 @@ public static WithdrawalState createWithdrawalState() { public static WithdrawalState createWithdrawalStateWithNewBody() { return createWithdrawalState() + .setStatus(Status.succeeded(new Succeeded())) .setNewBody(createCash(1500, "USD")) .setEffectiveFinalCashFlow(new FinalCashFlow() .setPostings(List.of(createFeePosting()))); 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 cbff401..439c95c 100644 --- a/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java +++ b/src/test/java/dev/vality/wallets/hooker/handler/WithdrawalEventHandlerTest.java @@ -18,12 +18,8 @@ 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; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @PostgresqlSpringBootITest class WithdrawalEventHandlerTest { @@ -83,7 +79,29 @@ void handleWithdrawalSucceededWithNewBodyInSucceededWebhook() { .map(WebhookMessage::getRequestBody) .map(String::new) .anyMatch(body -> body.contains("\"eventType\":\"WithdrawalSucceeded\"") - && body.contains("\"body\":{\"amount\":1500,\"currency\":\"USD\"}") + && body.contains("\"body\":{\"amount\":1000,\"changedAmount\":1500,\"currency\":\"USD\"}") && body.contains("\"fee\":{\"amount\":25,\"currency\":\"RUB\"}"))); } + + @Test + void handleWithdrawalAdjustmentSucceededWebhook() { + WebHookModel webhook = TestBeanFactory.createWebhookModel(); + when(withdrawalClient.getWithdrawalInfo(eq(TestBeanFactory.WITHDRAWAL_ID), anyLong())) + .thenReturn(TestBeanFactory.createWithdrawalStateWithNewBody()); + + webHookDao.create(webhook); + + withdrawalEventService.handleEvents(List.of(TestBeanFactory.createWithdrawalEvent())); + withdrawalEventService.handleEvents(List.of(TestBeanFactory.createWithdrawalSucceeded(69L))); + withdrawalEventService.handleEvents(List.of(TestBeanFactory.createWithdrawalAdjustmentChange(70L))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(WebhookMessage.class); + verify(webHookMessageSenderService, timeout(1000L).times(3)) + .send(captor.capture()); + Assertions.assertTrue(captor.getAllValues().stream() + .map(WebhookMessage::getRequestBody) + .map(String::new) + .anyMatch(body -> body.contains("\"eventType\":\"WithdrawalSucceeded\"") + && body.contains("\"body\":{\"amount\":1000,\"changedAmount\":1500,\"currency\":\"USD\"}"))); + } }