diff --git a/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java b/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java index a8c5da62..d03d16d3 100644 --- a/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java +++ b/src/main/java/dev/vality/disputes/polling/ExponentialBackOffPollingServiceWrapper.java @@ -3,6 +3,7 @@ import dev.vality.adapter.flow.lib.model.PollingInfo; import dev.vality.disputes.domain.tables.pojos.Dispute; import dev.vality.disputes.domain.tables.pojos.Notification; +import dev.vality.disputes.domain.tables.pojos.ProviderCallback; import org.springframework.stereotype.Service; import java.time.Instant; @@ -41,6 +42,14 @@ public LocalDateTime prepareNextPollingInterval(Notification notification, Local notification.getNextAttemptAfter().toInstant(ZoneOffset.UTC).plusSeconds(seconds)); } + public LocalDateTime prepareNextPollingInterval(ProviderCallback providerCallback, Map options) { + var pollingInfo = new PollingInfo(); + pollingInfo.setStartDateTimePolling(providerCallback.getCreatedAt().toInstant(ZoneOffset.UTC)); + var seconds = exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, options); + return getLocalDateTime( + providerCallback.getNextCheckAfter().toInstant(ZoneOffset.UTC).plusSeconds(seconds)); + } + private LocalDateTime getLocalDateTime(Instant instant) { return LocalDateTime.ofInstant(instant, ZoneOffset.UTC); } diff --git a/src/main/java/dev/vality/disputes/provider/payments/dao/ProviderCallbackDao.java b/src/main/java/dev/vality/disputes/provider/payments/dao/ProviderCallbackDao.java index c74bd20f..df4d732a 100644 --- a/src/main/java/dev/vality/disputes/provider/payments/dao/ProviderCallbackDao.java +++ b/src/main/java/dev/vality/disputes/provider/payments/dao/ProviderCallbackDao.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Component; import javax.sql.DataSource; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -59,7 +61,9 @@ public ProviderCallback get(String invoiceId, String paymentId) { public List getProviderCallbacksForHgCall(int limit) { var query = getDslContext().selectFrom(PROVIDER_CALLBACK) - .where(PROVIDER_CALLBACK.STATUS.eq(ProviderPaymentsStatus.create_adjustment)) + .where(PROVIDER_CALLBACK.STATUS.eq(ProviderPaymentsStatus.create_adjustment) + .and(PROVIDER_CALLBACK.NEXT_CHECK_AFTER.le(LocalDateTime.now(ZoneOffset.UTC)))) + .orderBy(PROVIDER_CALLBACK.NEXT_CHECK_AFTER) .limit(limit) .forUpdate() .skipLocked(); @@ -74,4 +78,11 @@ var record = getDslContext().newRecord(PROVIDER_CALLBACK, providerCallback); .where(PROVIDER_CALLBACK.ID.eq(providerCallback.getId())); execute(query); } + + public void updateNextCheckAfter(UUID id, LocalDateTime nextCheckAfter) { + var query = getDslContext().update(PROVIDER_CALLBACK) + .set(PROVIDER_CALLBACK.NEXT_CHECK_AFTER, nextCheckAfter) + .where(PROVIDER_CALLBACK.ID.eq(id)); + execute(query); + } } diff --git a/src/main/java/dev/vality/disputes/provider/payments/service/ProviderPaymentsService.java b/src/main/java/dev/vality/disputes/provider/payments/service/ProviderPaymentsService.java index 1ed8cda9..a93ce753 100644 --- a/src/main/java/dev/vality/disputes/provider/payments/service/ProviderPaymentsService.java +++ b/src/main/java/dev/vality/disputes/provider/payments/service/ProviderPaymentsService.java @@ -9,6 +9,7 @@ import dev.vality.disputes.exception.InvoicingPaymentStatusRestrictionsException; import dev.vality.disputes.exception.NotFoundException; import dev.vality.disputes.exception.ProviderTrxIdNotFoundException; +import dev.vality.disputes.polling.ExponentialBackOffPollingServiceWrapper; import dev.vality.disputes.provider.payments.client.ProviderPaymentsRemoteClient; import dev.vality.disputes.provider.payments.converter.ProviderPaymentsToInvoicePaymentCapturedAdjustmentParamsConverter; import dev.vality.disputes.provider.payments.converter.ProviderPaymentsToInvoicePaymentCashFlowAdjustmentParamsConverter; @@ -35,6 +36,7 @@ import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Optional; import static dev.vality.disputes.constant.ErrorMessage.INVOICE_NOT_FOUND; @@ -60,6 +62,7 @@ public class ProviderPaymentsService { private final DisputesService disputesService; private final ProviderPaymentsRemoteClient providerPaymentsRemoteClient; private final ProviderPaymentsCheckStatusScheduler providerPaymentsCheckStatusScheduler; + private final ExponentialBackOffPollingServiceWrapper exponentialBackOffPollingService; @Async("disputesAsyncServiceExecutor") public void processCallback(ProviderPaymentsCallbackParams callback) { @@ -177,6 +180,7 @@ public void callHgForCreateAdjustment(ProviderCallback providerCallback) { if (statusAction == PaymentStatusValidator.StatusAction.WAIT) { log.info("Invoice payment is not final, retry create adjustment later, invoiceId={}, paymentId={}", providerCallback.getInvoiceId(), providerCallback.getPaymentId()); + updateNextCheckAfter(providerCallback); return; } if (statusAction == PaymentStatusValidator.StatusAction.FAILED) { @@ -251,6 +255,13 @@ private void checkCreateAdjustmentStatus(ProviderCallback providerCallback) { } } + private void updateNextCheckAfter(ProviderCallback providerCallback) { + var nextCheckAfter = exponentialBackOffPollingService.prepareNextPollingInterval(providerCallback, Map.of()); + log.info("Trying to update ProviderCallback nextCheckAfter {}", providerCallback); + providerCallbackDao.updateNextCheckAfter(providerCallback.getId(), nextCheckAfter); + log.debug("ProviderCallback nextCheckAfter has been updated {}", providerCallback.getInvoiceId()); + } + private boolean createCashFlowAdjustment(ProviderCallback providerCallback, InvoicePayment invoicePayment) { var cashFlowAdjustmentExists = providerPaymentsAdjustmentExtractor.isCashFlowAdjustmentByProviderPaymentsExist( invoicePayment, diff --git a/src/main/java/dev/vality/disputes/schedule/core/CreatedDisputesService.java b/src/main/java/dev/vality/disputes/schedule/core/CreatedDisputesService.java index 0fa6410c..bf4a7072 100644 --- a/src/main/java/dev/vality/disputes/schedule/core/CreatedDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/core/CreatedDisputesService.java @@ -70,6 +70,7 @@ public void callCreateDisputeRemotely(Dispute dispute) { if (statusAction == PaymentStatusValidator.StatusAction.WAIT) { log.info("Invoice payment is not final, retry create dispute later, invoiceId={}, paymentId={}", dispute.getInvoiceId(), dispute.getPaymentId()); + disputesService.setNextStepToCreated(dispute); return; } if (statusAction == PaymentStatusValidator.StatusAction.SUCCEEDED) { diff --git a/src/main/java/dev/vality/disputes/schedule/core/ForgottenDisputesService.java b/src/main/java/dev/vality/disputes/schedule/core/ForgottenDisputesService.java index 59097744..8f8f93bf 100644 --- a/src/main/java/dev/vality/disputes/schedule/core/ForgottenDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/core/ForgottenDisputesService.java @@ -43,6 +43,7 @@ public void process(Dispute dispute) { if (statusAction == PaymentStatusValidator.StatusAction.WAIT) { log.info("Invoice payment is not final, retry forgotten dispute later, invoiceId={}, paymentId={}", dispute.getInvoiceId(), dispute.getPaymentId()); + disputesService.updateNextPollingInterval(dispute); return; } if (statusAction == PaymentStatusValidator.StatusAction.SUCCEEDED) { diff --git a/src/main/java/dev/vality/disputes/schedule/core/PendingDisputesService.java b/src/main/java/dev/vality/disputes/schedule/core/PendingDisputesService.java index 1e1244d1..988bf8b5 100644 --- a/src/main/java/dev/vality/disputes/schedule/core/PendingDisputesService.java +++ b/src/main/java/dev/vality/disputes/schedule/core/PendingDisputesService.java @@ -55,6 +55,7 @@ public void callPendingDisputeRemotely(Dispute dispute) { if (statusAction == PaymentStatusValidator.StatusAction.WAIT) { log.info("Invoice payment is not final, retry pending dispute later, invoiceId={}, paymentId={}", dispute.getInvoiceId(), dispute.getPaymentId()); + disputesService.setNextStepToPending(dispute); return; } if (statusAction == PaymentStatusValidator.StatusAction.SUCCEEDED) { diff --git a/src/main/java/dev/vality/disputes/service/DisputesService.java b/src/main/java/dev/vality/disputes/service/DisputesService.java index 017473f9..340b7ed0 100644 --- a/src/main/java/dev/vality/disputes/service/DisputesService.java +++ b/src/main/java/dev/vality/disputes/service/DisputesService.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -72,8 +73,16 @@ public void finishCancelled(Dispute dispute, String mapping, String providerMess } public void setNextStepToCreated(Dispute dispute, ProviderData providerData) { + setNextStepToCreated(dispute, providerData.getOptions()); + } + + public void setNextStepToCreated(Dispute dispute) { + setNextStepToCreated(dispute, Map.of()); + } + + private void setNextStepToCreated(Dispute dispute, Map options) { var nextCheckAfter = - exponentialBackOffPollingService.prepareNextPollingInterval(dispute, providerData.getOptions()); + exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options); log.info("Trying to set created Dispute status {}", dispute.getId()); disputeDao.setNextStepToCreated(dispute.getId(), nextCheckAfter); log.debug("Dispute status has been set to created {}", dispute.getId()); @@ -81,8 +90,16 @@ public void setNextStepToCreated(Dispute dispute, ProviderData providerData) { } public void setNextStepToPending(Dispute dispute, ProviderData providerData) { + setNextStepToPending(dispute, providerData.getOptions()); + } + + public void setNextStepToPending(Dispute dispute) { + setNextStepToPending(dispute, Map.of()); + } + + private void setNextStepToPending(Dispute dispute, Map options) { var nextCheckAfter = - exponentialBackOffPollingService.prepareNextPollingInterval(dispute, providerData.getOptions()); + exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options); log.info("Trying to set pending Dispute status {}", dispute); disputeDao.setNextStepToPending(dispute.getId(), nextCheckAfter, dispute.getPollingBefore()); log.debug("Dispute status has been set to pending {}", dispute.getId()); @@ -119,8 +136,16 @@ public void setNextStepToPoolingExpired(Dispute dispute) { } public void updateNextPollingInterval(Dispute dispute, ProviderData providerData) { + updateNextPollingInterval(dispute, providerData.getOptions()); + } + + public void updateNextPollingInterval(Dispute dispute) { + updateNextPollingInterval(dispute, Map.of()); + } + + private void updateNextPollingInterval(Dispute dispute, Map options) { var nextCheckAfter = - exponentialBackOffPollingService.prepareNextPollingInterval(dispute, providerData.getOptions()); + exponentialBackOffPollingService.prepareNextPollingInterval(dispute, options); disputeDao.updateNextPollingInterval(dispute, nextCheckAfter); } diff --git a/src/main/resources/db/migration/V16__add_next_check_after_to_provider_callback.sql b/src/main/resources/db/migration/V16__add_next_check_after_to_provider_callback.sql new file mode 100644 index 00000000..44387406 --- /dev/null +++ b/src/main/resources/db/migration/V16__add_next_check_after_to_provider_callback.sql @@ -0,0 +1,5 @@ +ALTER TABLE dspt.provider_callback + ADD COLUMN "next_check_after" TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc'); + +CREATE INDEX provider_callback_status_next_check_after_idx + ON dspt.provider_callback USING btree (status, next_check_after); diff --git a/src/test/java/dev/vality/disputes/provider/payments/ProviderPaymentsServiceTest.java b/src/test/java/dev/vality/disputes/provider/payments/ProviderPaymentsServiceTest.java index 22feb17d..6d4a7ab7 100644 --- a/src/test/java/dev/vality/disputes/provider/payments/ProviderPaymentsServiceTest.java +++ b/src/test/java/dev/vality/disputes/provider/payments/ProviderPaymentsServiceTest.java @@ -15,6 +15,7 @@ import static dev.vality.disputes.util.MockUtil.createInvoicePayment; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -76,8 +77,10 @@ public void testRetryLaterWhenInvoicePaymentStatusIsPending() { providerPaymentsService.callHgForCreateAdjustment(providerCallback); + var nextCheckAfter = providerCallback.getNextCheckAfter(); providerCallback = providerCallbackDao.get(dispute.getInvoiceId(), dispute.getPaymentId()); assertEquals(ProviderPaymentsStatus.create_adjustment, providerCallback.getStatus()); + assertTrue(providerCallback.getNextCheckAfter().isAfter(nextCheckAfter)); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).getStatus()); verify(invoicingClient, never()).createPaymentAdjustment(any(), any(), any()); } @@ -94,8 +97,10 @@ public void testRetryLaterWhenInvoicePaymentStatusIsProcessed() { providerPaymentsService.callHgForCreateAdjustment(providerCallback); + var nextCheckAfter = providerCallback.getNextCheckAfter(); providerCallback = providerCallbackDao.get(dispute.getInvoiceId(), dispute.getPaymentId()); assertEquals(ProviderPaymentsStatus.create_adjustment, providerCallback.getStatus()); + assertTrue(providerCallback.getNextCheckAfter().isAfter(nextCheckAfter)); assertEquals(DisputeStatus.create_adjustment, disputeDao.get(disputeId).getStatus()); verify(invoicingClient, never()).createPaymentAdjustment(any(), any(), any()); } diff --git a/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java index a2a8d8b0..194e4b4a 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java +++ b/src/test/java/dev/vality/disputes/schedule/service/CreatedDisputesServiceTest.java @@ -266,7 +266,9 @@ public void testWaitWhenInvoicePaymentStatusIsPending() { var disputeId = UUID.fromString(merchantApiMvcPerformer.createDispute(invoiceId, paymentId).getDisputeId()); var dispute = disputeDao.get(disputeId); createdDisputesService.callCreateDisputeRemotely(dispute); - assertEquals(DisputeStatus.created, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.created, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); disputeDao.finishFailed(disputeId, null); } @@ -281,7 +283,9 @@ public void testWaitWhenInvoicePaymentStatusIsProcessed() { var disputeId = UUID.fromString(merchantApiMvcPerformer.createDispute(invoiceId, paymentId).getDisputeId()); var dispute = disputeDao.get(disputeId); createdDisputesService.callCreateDisputeRemotely(dispute); - assertEquals(DisputeStatus.created, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.created, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); disputeDao.finishFailed(disputeId, null); } diff --git a/src/test/java/dev/vality/disputes/schedule/service/ForgottenDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/ForgottenDisputesServiceTest.java index 96ff04b4..25666973 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/ForgottenDisputesServiceTest.java +++ b/src/test/java/dev/vality/disputes/schedule/service/ForgottenDisputesServiceTest.java @@ -17,6 +17,7 @@ import static dev.vality.disputes.util.MockUtil.createInvoicePayment; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -69,7 +70,9 @@ public void testWaitWhenInvoicePaymentStatusIsPending() { invoicePayment.getPayment().setStatus(InvoicePaymentStatus.pending(new InvoicePaymentPending())); when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); forgottenDisputesService.process(dispute); - assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.pending, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); } @Test @@ -81,7 +84,9 @@ public void testWaitWhenInvoicePaymentStatusIsProcessed() { invoicePayment.getPayment().setStatus(InvoicePaymentStatus.processed(new InvoicePaymentProcessed())); when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); forgottenDisputesService.process(dispute); - assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.pending, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); } @Test diff --git a/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java index 2c0db8e4..6875811d 100644 --- a/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java +++ b/src/test/java/dev/vality/disputes/schedule/service/PendingDisputesServiceTest.java @@ -192,7 +192,9 @@ public void testPendingWhenInvoicePaymentStatusIsPending() { invoicePayment.getPayment().setStatus(InvoicePaymentStatus.pending(new InvoicePaymentPending())); when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); pendingDisputesService.callPendingDisputeRemotely(dispute); - assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.pending, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); disputeDao.finishFailed(disputeId, null); } @@ -205,7 +207,9 @@ public void testPendingWhenInvoicePaymentStatusIsProcessed() { invoicePayment.getPayment().setStatus(InvoicePaymentStatus.processed(new InvoicePaymentProcessed())); when(invoicingClient.getPayment(any(), any())).thenReturn(invoicePayment); pendingDisputesService.callPendingDisputeRemotely(dispute); - assertEquals(DisputeStatus.pending, disputeDao.get(disputeId).getStatus()); + var updatedDispute = disputeDao.get(disputeId); + assertEquals(DisputeStatus.pending, updatedDispute.getStatus()); + assertTrue(updatedDispute.getNextCheckAfter().isAfter(dispute.getNextCheckAfter())); disputeDao.finishFailed(disputeId, null); } }