From 94418ab4eafb9fad8cdb761c1c550d48087597c2 Mon Sep 17 00:00:00 2001 From: struga Date: Thu, 3 Aug 2023 15:23:52 +0300 Subject: [PATCH] Add flow for work with qr code (#69) --- pom.xml | 2 +- .../entry/CtxToEntryModelConverter.java | 14 +- ...rtedGenerateTokenResultIntentResolver.java | 17 +++ ...upportedGenerateTokenStepResolverImpl.java | 20 +++ .../flow/lib/handler/ServerFlowHandler.java | 38 +---- .../lib/handler/ServerFlowHandlerImpl.java | 44 ++++++ .../flow/lib/model/BaseResponseModel.java | 4 + .../flow/lib/model/ExitStateModel.java | 1 + .../adapter/flow/lib/model/QrDisplayData.java | 24 ++++ .../lib/processor/QrDisplayProcessor.java | 41 ++++++ .../lib/service/TagManagementService.java | 7 +- .../factory/IntentResultQrPaymentFactory.java | 131 ++++++++++++++++++ .../flow/lib/flow/config/HandlerConfig.java | 3 +- .../ds/config/FullThreeDsFlowConfig.java | 5 +- .../flow/lib/flow/qr/ErrorPaymentQrTest.java | 54 ++++++++ .../lib/flow/qr/PaymentSuccessQrTest.java | 99 +++++++++++++ .../QrRedirectWithPollingDsFlowConfig.java | 116 ++++++++++++++++ .../redirect/GenerateToken3ds1Test.java | 71 ---------- ...SimpleRedirectWithPollingDsFlowConfig.java | 5 +- .../flow/lib/flow/utils/BeanUtils.java | 8 ++ .../adapter/flow/lib/flow/utils/MockUtil.java | 38 +++++ 21 files changed, 624 insertions(+), 118 deletions(-) create mode 100644 src/main/java/dev/vality/adapter/flow/lib/flow/simple/UnsupportedGenerateTokenResultIntentResolver.java create mode 100644 src/main/java/dev/vality/adapter/flow/lib/flow/simple/UnsupportedGenerateTokenStepResolverImpl.java create mode 100644 src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandlerImpl.java create mode 100644 src/main/java/dev/vality/adapter/flow/lib/model/QrDisplayData.java create mode 100644 src/main/java/dev/vality/adapter/flow/lib/processor/QrDisplayProcessor.java create mode 100644 src/main/java/dev/vality/adapter/flow/lib/service/factory/IntentResultQrPaymentFactory.java create mode 100644 src/test/java/dev/vality/adapter/flow/lib/flow/qr/ErrorPaymentQrTest.java create mode 100644 src/test/java/dev/vality/adapter/flow/lib/flow/qr/PaymentSuccessQrTest.java create mode 100644 src/test/java/dev/vality/adapter/flow/lib/flow/qr/config/QrRedirectWithPollingDsFlowConfig.java delete mode 100644 src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/GenerateToken3ds1Test.java diff --git a/pom.xml b/pom.xml index da15417..9d6cb99 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ adapter-flow-lib - 0.1.25 + 0.2.1 jar adapter-flow-lib diff --git a/src/main/java/dev/vality/adapter/flow/lib/converter/entry/CtxToEntryModelConverter.java b/src/main/java/dev/vality/adapter/flow/lib/converter/entry/CtxToEntryModelConverter.java index 49ff494..2eaf9a1 100644 --- a/src/main/java/dev/vality/adapter/flow/lib/converter/entry/CtxToEntryModelConverter.java +++ b/src/main/java/dev/vality/adapter/flow/lib/converter/entry/CtxToEntryModelConverter.java @@ -4,14 +4,17 @@ import dev.vality.adapter.common.cds.CdsStorageClient; import dev.vality.adapter.common.cds.model.CardDataProxyModel; import dev.vality.adapter.common.damsel.ProxyProviderPackageCreators; import dev.vality.adapter.common.damsel.ProxyProviderPackageExtractors; -import dev.vality.adapter.flow.lib.constant.*; +import dev.vality.adapter.flow.lib.constant.MetaData; +import dev.vality.adapter.flow.lib.constant.OptionFields; +import dev.vality.adapter.flow.lib.constant.Step; +import dev.vality.adapter.flow.lib.constant.TargetStatus; import dev.vality.adapter.flow.lib.model.*; import dev.vality.adapter.flow.lib.serde.TemporaryContextDeserializer; +import dev.vality.adapter.flow.lib.service.CallbackUrlExtractor; import dev.vality.adapter.flow.lib.service.CardDataService; import dev.vality.adapter.flow.lib.service.IdGenerator; import dev.vality.adapter.flow.lib.service.TemporaryContextService; import dev.vality.adapter.flow.lib.utils.AdapterProperties; -import dev.vality.adapter.flow.lib.service.CallbackUrlExtractor; import dev.vality.adapter.flow.lib.utils.CardDataUtils; import dev.vality.adapter.flow.lib.utils.TargetStatusResolver; import dev.vality.cds.storage.Auth3DS; @@ -55,7 +58,8 @@ public class CtxToEntryModelConverter implements Converter { + + @Override + public Step resolveCurrentStep(EntryStateModel stateModel) { + throw new UnsupportedOperationException(); + } + + @Override + public Step resolveNextStep(ExitStateModel exitStateModel) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandler.java b/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandler.java index 827234d..3a70311 100644 --- a/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandler.java +++ b/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandler.java @@ -1,43 +1,11 @@ package dev.vality.adapter.flow.lib.handler; -import dev.vality.adapter.flow.lib.exception.UnknownHandlerForStepException; -import dev.vality.adapter.flow.lib.flow.StepResolver; -import dev.vality.adapter.flow.lib.model.EntryStateModel; -import dev.vality.adapter.flow.lib.model.ExitStateModel; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.thrift.TException; -import org.springframework.core.convert.converter.Converter; -import java.util.List; +public interface ServerFlowHandler { -@Slf4j -@RequiredArgsConstructor -public class ServerFlowHandler { - - private final List> handlers; - private final StepResolver stepResolver; - private final Converter entryConverter; - private final Converter exitConverter; - - public R handle(T context) throws TException { - var entryStateModel = prepareEntryState(entryConverter, context); - log.info("EntryStateModel: {}", entryStateModel); - var exitStateModel = handlers.stream() - .filter(handler -> handler.isHandle(entryStateModel)) - .findFirst() - .orElseThrow(() -> new UnknownHandlerForStepException("Can't find handler to data: " + entryStateModel)) - .handle(entryStateModel); - log.info("ExitStateModel: {}", exitStateModel); - exitStateModel.setEntryStateModel(entryStateModel); - exitStateModel.setNextStep(stepResolver.resolveNextStep(exitStateModel)); - log.info("Step changing: {} -> {}", entryStateModel.getCurrentStep(), exitStateModel.getNextStep()); - return exitConverter.convert(exitStateModel); + default R handle(T context) throws TException { + throw new UnsupportedOperationException(); } - private EntryStateModel prepareEntryState(Converter entryConverter, T context) { - EntryStateModel entryStateModel = entryConverter.convert(context); - entryStateModel.setCurrentStep(stepResolver.resolveCurrentStep(entryStateModel)); - return entryStateModel; - } } diff --git a/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandlerImpl.java b/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandlerImpl.java new file mode 100644 index 0000000..4126316 --- /dev/null +++ b/src/main/java/dev/vality/adapter/flow/lib/handler/ServerFlowHandlerImpl.java @@ -0,0 +1,44 @@ +package dev.vality.adapter.flow.lib.handler; + +import dev.vality.adapter.flow.lib.exception.UnknownHandlerForStepException; +import dev.vality.adapter.flow.lib.flow.StepResolver; +import dev.vality.adapter.flow.lib.model.EntryStateModel; +import dev.vality.adapter.flow.lib.model.ExitStateModel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.core.convert.converter.Converter; + +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +public class ServerFlowHandlerImpl implements ServerFlowHandler { + + private final List> handlers; + private final StepResolver stepResolver; + private final Converter entryConverter; + private final Converter exitConverter; + + @Override + public R handle(T context) throws TException { + var entryStateModel = prepareEntryState(entryConverter, context); + log.info("EntryStateModel: {}", entryStateModel); + var exitStateModel = handlers.stream() + .filter(handler -> handler.isHandle(entryStateModel)) + .findFirst() + .orElseThrow(() -> new UnknownHandlerForStepException("Can't find handler to data: " + entryStateModel)) + .handle(entryStateModel); + log.info("ExitStateModel: {}", exitStateModel); + exitStateModel.setEntryStateModel(entryStateModel); + exitStateModel.setNextStep(stepResolver.resolveNextStep(exitStateModel)); + log.info("Step changing: {} -> {}", entryStateModel.getCurrentStep(), exitStateModel.getNextStep()); + return exitConverter.convert(exitStateModel); + } + + private EntryStateModel prepareEntryState(Converter entryConverter, T context) { + EntryStateModel entryStateModel = entryConverter.convert(context); + entryStateModel.setCurrentStep(stepResolver.resolveCurrentStep(entryStateModel)); + return entryStateModel; + } +} diff --git a/src/main/java/dev/vality/adapter/flow/lib/model/BaseResponseModel.java b/src/main/java/dev/vality/adapter/flow/lib/model/BaseResponseModel.java index f8c8899..bcfa48c 100644 --- a/src/main/java/dev/vality/adapter/flow/lib/model/BaseResponseModel.java +++ b/src/main/java/dev/vality/adapter/flow/lib/model/BaseResponseModel.java @@ -38,6 +38,10 @@ public class BaseResponseModel { * Data for choose 3ds flow and parameters for redirects. */ private ThreeDsData threeDsData; + /** + * Data for display qr code. + */ + private QrDisplayData qrDisplayData; /** * Data for support about transactions. */ diff --git a/src/main/java/dev/vality/adapter/flow/lib/model/ExitStateModel.java b/src/main/java/dev/vality/adapter/flow/lib/model/ExitStateModel.java index 3ed2f01..c277c11 100644 --- a/src/main/java/dev/vality/adapter/flow/lib/model/ExitStateModel.java +++ b/src/main/java/dev/vality/adapter/flow/lib/model/ExitStateModel.java @@ -26,6 +26,7 @@ public class ExitStateModel { private Map trxExtra; private PollingInfo pollingInfo; private ThreeDsData threeDsData; + private QrDisplayData qrDisplayData; private AdditionalTrxInfo additionalTrxInfo; private String recToken; diff --git a/src/main/java/dev/vality/adapter/flow/lib/model/QrDisplayData.java b/src/main/java/dev/vality/adapter/flow/lib/model/QrDisplayData.java new file mode 100644 index 0000000..9b29be8 --- /dev/null +++ b/src/main/java/dev/vality/adapter/flow/lib/model/QrDisplayData.java @@ -0,0 +1,24 @@ +package dev.vality.adapter.flow.lib.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class QrDisplayData { + + /** + * Url for get qr code. + */ + private String qrUrl; + + /** + * Id for generate tag + */ + private String tagId; + +} diff --git a/src/main/java/dev/vality/adapter/flow/lib/processor/QrDisplayProcessor.java b/src/main/java/dev/vality/adapter/flow/lib/processor/QrDisplayProcessor.java new file mode 100644 index 0000000..87c89c9 --- /dev/null +++ b/src/main/java/dev/vality/adapter/flow/lib/processor/QrDisplayProcessor.java @@ -0,0 +1,41 @@ +package dev.vality.adapter.flow.lib.processor; + +import dev.vality.adapter.flow.lib.constant.Status; +import dev.vality.adapter.flow.lib.model.BaseResponseModel; +import dev.vality.adapter.flow.lib.model.EntryStateModel; +import dev.vality.adapter.flow.lib.model.ExitStateModel; +import dev.vality.adapter.flow.lib.utils.ErrorUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public class QrDisplayProcessor implements Processor { + + private final Processor nextProcessor; + + @Override + public ExitStateModel process(BaseResponseModel response, EntryStateModel entryStateModel) { + if (response.getStatus() == Status.NEED_REDIRECT + && !ErrorUtils.isError(response) + && response.getQrDisplayData() != null + && response.getQrDisplayData().getQrUrl() != null) { + log.debug("Start qr display process response: {} entryStateModel: {}", response, entryStateModel); + ExitStateModel exitStateModel = new ExitStateModel(); + exitStateModel.setQrDisplayData(response.getQrDisplayData()); + exitStateModel.setLastOperationStatus(response.getStatus()); + exitStateModel.setProviderTrxId(response.getProviderTrxId()); + exitStateModel.setTrxExtra(response.getSaveData()); + exitStateModel.setAdditionalTrxInfo(response.getAdditionalTrxInfo()); + exitStateModel.setCustomContext(response.getCustomContext()); + log.debug("Finish qr display process response: {} entryStateModel: {}", response, entryStateModel); + return exitStateModel; + } + + if (nextProcessor != null) { + return nextProcessor.process(response, entryStateModel); + } + + throw new IllegalStateException("Processor didn't match for response " + response); + } +} diff --git a/src/main/java/dev/vality/adapter/flow/lib/service/TagManagementService.java b/src/main/java/dev/vality/adapter/flow/lib/service/TagManagementService.java index c9bd95f..194efbb 100644 --- a/src/main/java/dev/vality/adapter/flow/lib/service/TagManagementService.java +++ b/src/main/java/dev/vality/adapter/flow/lib/service/TagManagementService.java @@ -18,7 +18,12 @@ public class TagManagementService { Optional first = adapterProperties.getTagGeneratorFieldNames().stream() .filter(s -> StringUtils.hasText(parameters.get(s))) .findFirst(); - return adapterProperties.getTagPrefix() + parameters.get(first.get()); + String tagId = parameters.get(first.get()); + return initTag(tagId); + } + + public String initTag(String tagId) { + return adapterProperties.getTagPrefix() + tagId; } } diff --git a/src/main/java/dev/vality/adapter/flow/lib/service/factory/IntentResultQrPaymentFactory.java b/src/main/java/dev/vality/adapter/flow/lib/service/factory/IntentResultQrPaymentFactory.java new file mode 100644 index 0000000..8972634 --- /dev/null +++ b/src/main/java/dev/vality/adapter/flow/lib/service/factory/IntentResultQrPaymentFactory.java @@ -0,0 +1,131 @@ +package dev.vality.adapter.flow.lib.service.factory; + +import dev.vality.adapter.common.mapper.ErrorMapping; +import dev.vality.adapter.flow.lib.constant.HttpMethod; +import dev.vality.adapter.flow.lib.model.*; +import dev.vality.adapter.flow.lib.serde.ParametersSerializer; +import dev.vality.adapter.flow.lib.service.ExponentialBackOffPollingService; +import dev.vality.adapter.flow.lib.service.PollingInfoService; +import dev.vality.adapter.flow.lib.service.TagManagementService; +import dev.vality.adapter.flow.lib.utils.TimeoutUtils; +import dev.vality.adapter.flow.lib.utils.TimerProperties; +import dev.vality.damsel.base.Timer; +import dev.vality.damsel.proxy_provider.*; +import dev.vality.damsel.timeout_behaviour.TimeoutBehaviour; +import dev.vality.damsel.user_interaction.QrCode; +import dev.vality.damsel.user_interaction.QrCodeDisplayRequest; +import dev.vality.damsel.user_interaction.UserInteraction; +import lombok.RequiredArgsConstructor; + +import java.nio.ByteBuffer; +import java.util.Map; + +import static dev.vality.adapter.common.damsel.OptionsExtractors.extractRedirectTimeout; +import static dev.vality.adapter.common.damsel.ProxyProviderPackageCreators.*; +import static dev.vality.adapter.flow.lib.utils.ThreeDsDataInitializer.TAG; + +@RequiredArgsConstructor +public class IntentResultQrPaymentFactory implements IntentResultFactory { + + private final TimerProperties timerProperties; + private final TagManagementService tagManagementService; + private final ParametersSerializer parametersSerializer; + private final PollingInfoService pollingInfoService; + private final ErrorMapping errorMapping; + private final ExponentialBackOffPollingService exponentialBackOffPollingService; + + @Override + public Intent createFinishIntentSuccessWithCheckToken(ExitStateModel exitStateModel) { + EntryStateModel entryStateModel = exitStateModel.getEntryStateModel(); + if (entryStateModel.getBaseRequestModel().getRecurrentPaymentData() != null + && entryStateModel.getBaseRequestModel().getRecurrentPaymentData().isMakeRecurrent()) { + return createFinishIntentSuccessWithToken(exitStateModel.getRecToken()); + } + return createFinishIntentSuccess(); + } + + @Override + public Intent createSuspendIntentWithFailedAfterTimeout(ExitStateModel exitStateModel) { + EntryStateModel entryStateModel = exitStateModel.getEntryStateModel(); + QrDisplayData qrDisplayData = exitStateModel.getQrDisplayData(); + Map adapterConfigurations = entryStateModel.getBaseRequestModel().getAdapterConfigurations(); + int timerRedirectTimeoutMin = extractRedirectTimeout( + adapterConfigurations, + timerProperties.getRedirectTimeoutMin()); + return Intent.suspend( + new SuspendIntent( + tagManagementService.initTag(qrDisplayData.getTagId()), + Timer.timeout(TimeoutUtils.toSeconds(timerRedirectTimeoutMin)) + ).setUserInteraction( + UserInteraction.qr_code_display_request(new QrCodeDisplayRequest() + .setQrCode(new QrCode() + .setPayload(qrDisplayData.getQrUrl().getBytes()))) + ) + ); + } + + @Override + public Intent createSuspendIntentWithCallbackAfterTimeout(ExitStateModel exitStateModel) { + QrDisplayData qrDisplayData = exitStateModel.getQrDisplayData(); + EntryStateModel entryStateModel = exitStateModel.getEntryStateModel(); + + PollingInfo pollingInfo = pollingInfoService.initPollingInfo(entryStateModel); + if (pollingInfoService.isDeadline(pollingInfo)) { + return createFinishIntentFailed("Sleep timeout", "Max time pool limit reached"); + } + exitStateModel.setPollingInfo(pollingInfo); + Map adapterConfigurations = entryStateModel.getBaseRequestModel().getAdapterConfigurations(); + int timerRedirectTimeoutMin = extractRedirectTimeout( + adapterConfigurations, + timerProperties.getRedirectTimeoutMin()); + + + String tag = tagManagementService.initTag(qrDisplayData.getTagId()); + return Intent.suspend( + new SuspendIntent( + tag, + Timer.timeout(TimeoutUtils.toSeconds(timerRedirectTimeoutMin))) + .setTimeoutBehaviour(TimeoutBehaviour.callback( + ByteBuffer.wrap( + parametersSerializer.writeByte(Map.of(TAG, tag)))) + ).setUserInteraction( + UserInteraction.qr_code_display_request(new QrCodeDisplayRequest() + .setQrCode(new QrCode() + .setPayload(qrDisplayData.getQrUrl().getBytes())))) + ); + } + + @Override + public Intent createFinishIntentSuccess() { + return Intent.finish(new FinishIntent(FinishStatus.success(new Success()))); + } + + @Override + public Intent createSleepIntentWithExponentialPolling(ExitStateModel exitStateModel) { + EntryStateModel entryStateModel = exitStateModel.getEntryStateModel(); + PollingInfo pollingInfo = pollingInfoService.initPollingInfo(entryStateModel); + if (pollingInfoService.isDeadline(pollingInfo)) { + return createFinishIntentFailed("Sleep timeout", "Max time pool limit reached"); + } + exitStateModel.setPollingInfo(pollingInfo); + + Map adapterConfigurations = entryStateModel.getBaseRequestModel().getAdapterConfigurations(); + int nextTimeoutSec = + exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, adapterConfigurations); + return Intent.sleep(new SleepIntent(Timer.timeout(nextTimeoutSec))); + } + + @Override + public Intent createFinishIntentFailed(ExitStateModel exitStateModel) { + return Intent.finish(new FinishIntent(FinishStatus.failure( + errorMapping.mapFailure(exitStateModel.getErrorCode(), + exitStateModel.getErrorMessage())))); + } + + @Override + public Intent createFinishIntentFailed(String errorCode, String errorMessage) { + return Intent.finish(new FinishIntent(FinishStatus.failure( + errorMapping.mapFailure(errorCode, errorMessage)))); + } + +} diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/config/HandlerConfig.java b/src/test/java/dev/vality/adapter/flow/lib/flow/config/HandlerConfig.java index 8e00897..7fceaba 100644 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/config/HandlerConfig.java +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/config/HandlerConfig.java @@ -22,6 +22,7 @@ import dev.vality.adapter.flow.lib.serde.ParametersSerializer; import dev.vality.adapter.flow.lib.serde.TemporaryContextDeserializer; import dev.vality.adapter.flow.lib.serde.TemporaryContextSerializer; import dev.vality.adapter.flow.lib.service.*; +import dev.vality.adapter.flow.lib.service.factory.IntentResultFactory; import dev.vality.adapter.flow.lib.service.factory.SimpleIntentResultFactory; import dev.vality.adapter.flow.lib.service.factory.SimpleRecurrentIntentResultFactory; import dev.vality.adapter.flow.lib.utils.AdapterProperties; @@ -187,7 +188,7 @@ public class HandlerConfig { @Bean public ExitModelToProxyResultConverter exitModelToProxyResultConverter( - SimpleIntentResultFactory intentResultFactory, + IntentResultFactory intentResultFactory, TemporaryContextSerializer temporaryContextSerializer, ResultIntentResolver resultIntentResolver, ExitStateModelToTemporaryContextConverter exitStateModelToTemporaryContextConverter) { diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/full/three/ds/config/FullThreeDsFlowConfig.java b/src/test/java/dev/vality/adapter/flow/lib/flow/full/three/ds/config/FullThreeDsFlowConfig.java index 74aad0d..f306f08 100644 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/full/three/ds/config/FullThreeDsFlowConfig.java +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/full/three/ds/config/FullThreeDsFlowConfig.java @@ -15,6 +15,7 @@ import dev.vality.adapter.flow.lib.flow.full.GenerateTokenResultIntentResolverIm import dev.vality.adapter.flow.lib.flow.full.ResultIntentResolverImpl; import dev.vality.adapter.flow.lib.handler.CommonHandler; import dev.vality.adapter.flow.lib.handler.ServerFlowHandler; +import dev.vality.adapter.flow.lib.handler.ServerFlowHandlerImpl; import dev.vality.adapter.flow.lib.handler.payment.*; import dev.vality.adapter.flow.lib.model.BaseResponseModel; import dev.vality.adapter.flow.lib.model.EntryStateModel; @@ -53,7 +54,7 @@ public class FullThreeDsFlowConfig { StepResolver fullThreeDsAllVersionsStepResolverImpl, CtxToEntryModelConverter ctxToEntryModelConverter, ExitModelToProxyResultConverter exitModelToProxyResultConverter) { - return new ServerFlowHandler<>( + return new ServerFlowHandlerImpl<>( getHandlers(client, entryModelToBaseRequestModelConverter, baseProcessor), fullThreeDsAllVersionsStepResolverImpl, ctxToEntryModelConverter, @@ -69,7 +70,7 @@ public class FullThreeDsFlowConfig { RecCtxToEntryModelConverter recCtxToEntryStateModelConverter, ExitModelToRecTokenProxyResultConverter exitModelToRecTokenProxyResultConverter ) { - return new ServerFlowHandler<>( + return new ServerFlowHandlerImpl<>( getHandlers(client, entryModelToBaseRequestModelConverter, baseProcessor), generateTokenFullThreeDsAllVersionsStepResolverImpl, recCtxToEntryStateModelConverter, diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/qr/ErrorPaymentQrTest.java b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/ErrorPaymentQrTest.java new file mode 100644 index 0000000..af8cbaf --- /dev/null +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/ErrorPaymentQrTest.java @@ -0,0 +1,54 @@ +package dev.vality.adapter.flow.lib.flow.qr; + +import dev.vality.adapter.flow.lib.flow.AbstractPaymentTest; +import dev.vality.adapter.flow.lib.flow.qr.config.QrRedirectWithPollingDsFlowConfig; +import dev.vality.adapter.flow.lib.flow.utils.BeanUtils; +import dev.vality.adapter.flow.lib.flow.utils.MockUtil; +import dev.vality.adapter.flow.lib.model.BaseResponseModel; +import dev.vality.damsel.proxy_provider.PaymentContext; +import dev.vality.damsel.proxy_provider.PaymentProxyResult; +import org.apache.thrift.TException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = QrRedirectWithPollingDsFlowConfig.class) +@TestPropertySource(properties = {"server.rest.port=8083", + "error-mapping.file=classpath:fixture/errors.json"}) +public class ErrorPaymentQrTest extends AbstractPaymentTest { + + @BeforeEach + public void setUp() throws TException { + MockitoAnnotations.openMocks(this); + MockUtil.mockAllWithout3Ds(cdsStorageClient, benderClient); + + BaseResponseModel baseResponseModel = BeanUtils.createBaseResponseModel(); + baseResponseModel.setErrorCode("rem_error_21"); + baseResponseModel.setErrorMessage("Remote service error!"); + + Mockito.when(client.auth(any())).thenReturn(baseResponseModel); + Mockito.when(client.pay(any())).thenReturn(baseResponseModel); + } + + @Test + public void testErrorPayment() throws TException { + // pay + PaymentContext paymentContext = MockUtil.buildPaymentContext(String.valueOf(new Date().getTime()), + MockUtil.buildOptionsOneStage()); + + PaymentProxyResult paymentProxyResult = serverHandlerLogDecorator.processPayment(paymentContext); + assertTrue(paymentProxyResult.getIntent().isSetFinish()); + assertTrue(paymentProxyResult.getIntent().getFinish().getStatus().isSetFailure()); + } +} diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/qr/PaymentSuccessQrTest.java b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/PaymentSuccessQrTest.java new file mode 100644 index 0000000..3f4ede6 --- /dev/null +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/PaymentSuccessQrTest.java @@ -0,0 +1,99 @@ +package dev.vality.adapter.flow.lib.flow.qr; + +import com.fasterxml.jackson.core.JsonProcessingException; +import dev.vality.adapter.flow.lib.constant.OptionFields; +import dev.vality.adapter.flow.lib.constant.Stage; +import dev.vality.adapter.flow.lib.constant.Status; +import dev.vality.adapter.flow.lib.constant.Step; +import dev.vality.adapter.flow.lib.flow.AbstractPaymentTest; +import dev.vality.adapter.flow.lib.flow.qr.config.QrRedirectWithPollingDsFlowConfig; +import dev.vality.adapter.flow.lib.flow.utils.BeanUtils; +import dev.vality.adapter.flow.lib.flow.utils.MockUtil; +import dev.vality.adapter.flow.lib.model.BaseResponseModel; +import dev.vality.damsel.proxy_provider.PaymentContext; +import dev.vality.damsel.proxy_provider.PaymentProxyResult; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +@Slf4j +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = QrRedirectWithPollingDsFlowConfig.class) +@TestPropertySource(properties = {"error-mapping.file=classpath:fixture/errors.json", + "adapter.callbackUrl=http://localhost:8080/test", + "server.rest.endpoint=adapter", + "server.rest.port=8083"}) +public class PaymentSuccessQrTest extends AbstractPaymentTest { + + @BeforeEach + public void setUp() throws TException { + MockitoAnnotations.openMocks(this); + + MockUtil.mockIdGenerator(benderClient); + + BaseResponseModel baseResponseModel = BeanUtils.createBaseResponseModel(); + baseResponseModel.setQrDisplayData(BeanUtils.createQrDisplayData()); + baseResponseModel.setStatus(Status.NEED_REDIRECT); + + Mockito.when(client.auth(any())).thenReturn(baseResponseModel); + Mockito.when(client.pay(any())).thenReturn(baseResponseModel); + + BaseResponseModel successResponseModel = BeanUtils.createBaseResponseModel(); + Mockito.when(client.capture(any())).thenReturn(successResponseModel); + Mockito.when(client.refund(any())).thenReturn(successResponseModel); + Mockito.when(client.status(any())).thenReturn(successResponseModel); + } + + @Test + public void testOneStage() throws TException, IOException { + // auth + Map options = MockUtil.buildOptionsOneStage(); + testQr(options); + } + + @Test + public void testTwoStage() throws TException, IOException { + // auth + Map options = MockUtil.buildOptionsTwoStage(); + testQr(options); + } + + private void testQr(Map options) throws TException, JsonProcessingException { + PaymentContext paymentContext = MockUtil.buildPaymentContextPaymentTerminal("invoice_id", options); + + PaymentProxyResult paymentProxyResult = serverHandlerLogDecorator.processPayment(paymentContext); + assertTrue(paymentProxyResult.getIntent().getSuspend().getUserInteraction().isSetQrCodeDisplayRequest()); + assertEquals(Step.CHECK_STATUS, + temporaryContextDeserializer.read(paymentProxyResult.getNextState()).getNextStep()); + + //checkStatus + paymentProxyResult = processWithDoNothingSuccessResult(paymentContext, paymentProxyResult); + + //capture + if (Stage.ONE.equals(options.get(OptionFields.STAGE.name()))) { + paymentProxyResult = checkSuccessCapture(paymentContext, paymentProxyResult, new byte[] {}); + processWithDoNothingSuccessResult(paymentContext, paymentProxyResult); + } else { + paymentProxyResult = processCaptureWithCheckStatusResult(paymentContext, paymentProxyResult, new byte[] {}); + processWithCheckStatusResult(paymentContext, paymentProxyResult); + } + + //refund + checkSuccessRefund(1100L, paymentContext, paymentProxyResult); + } + +} diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/qr/config/QrRedirectWithPollingDsFlowConfig.java b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/config/QrRedirectWithPollingDsFlowConfig.java new file mode 100644 index 0000000..3fa4b0d --- /dev/null +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/qr/config/QrRedirectWithPollingDsFlowConfig.java @@ -0,0 +1,116 @@ +package dev.vality.adapter.flow.lib.flow.qr.config; + +import dev.vality.adapter.common.mapper.ErrorMapping; +import dev.vality.adapter.flow.lib.client.RemoteClient; +import dev.vality.adapter.flow.lib.converter.base.EntryModelToBaseRequestModelConverter; +import dev.vality.adapter.flow.lib.converter.entry.CtxToEntryModelConverter; +import dev.vality.adapter.flow.lib.converter.exit.ExitModelToProxyResultConverter; +import dev.vality.adapter.flow.lib.flow.RecurrentResultIntentResolver; +import dev.vality.adapter.flow.lib.flow.ResultIntentResolver; +import dev.vality.adapter.flow.lib.flow.StepResolver; +import dev.vality.adapter.flow.lib.flow.simple.SimpleRedirectWithPollingResultIntentResolver; +import dev.vality.adapter.flow.lib.flow.simple.SimpleRedirectWithPollingStepResolverImpl; +import dev.vality.adapter.flow.lib.flow.simple.UnsupportedGenerateTokenResultIntentResolver; +import dev.vality.adapter.flow.lib.flow.simple.UnsupportedGenerateTokenStepResolverImpl; +import dev.vality.adapter.flow.lib.handler.CommonHandler; +import dev.vality.adapter.flow.lib.handler.ServerFlowHandler; +import dev.vality.adapter.flow.lib.handler.ServerFlowHandlerImpl; +import dev.vality.adapter.flow.lib.handler.payment.*; +import dev.vality.adapter.flow.lib.model.BaseResponseModel; +import dev.vality.adapter.flow.lib.model.EntryStateModel; +import dev.vality.adapter.flow.lib.model.ExitStateModel; +import dev.vality.adapter.flow.lib.processor.*; +import dev.vality.adapter.flow.lib.serde.ParametersSerializer; +import dev.vality.adapter.flow.lib.service.ExponentialBackOffPollingService; +import dev.vality.adapter.flow.lib.service.PollingInfoService; +import dev.vality.adapter.flow.lib.service.TagManagementService; +import dev.vality.adapter.flow.lib.service.factory.IntentResultQrPaymentFactory; +import dev.vality.adapter.flow.lib.utils.TimerProperties; +import dev.vality.damsel.proxy_provider.PaymentContext; +import dev.vality.damsel.proxy_provider.PaymentProxyResult; +import dev.vality.damsel.proxy_provider.RecurrentTokenContext; +import dev.vality.damsel.proxy_provider.RecurrentTokenProxyResult; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.util.List; + +@Configuration +public class QrRedirectWithPollingDsFlowConfig { + + @Bean + public StepResolver stepResolverImpl() { + return new SimpleRedirectWithPollingStepResolverImpl(); + } + + @Bean + public StepResolver generateTokenStepResolverImpl() { + return new UnsupportedGenerateTokenStepResolverImpl(); + } + + @Bean + public ServerFlowHandler serverFlowHandler( + RemoteClient client, + EntryModelToBaseRequestModelConverter entryModelToBaseRequestModelConverter, + Processor baseProcessor, + StepResolver stepResolverImpl, + CtxToEntryModelConverter ctxToEntryModelConverter, + ExitModelToProxyResultConverter exitModelToProxyResultConverter) { + return new ServerFlowHandlerImpl<>( + getHandlers(client, entryModelToBaseRequestModelConverter, baseProcessor), + stepResolverImpl, + ctxToEntryModelConverter, + exitModelToProxyResultConverter); + } + + @Bean + @Primary + public Processor baseProcessor() { + ErrorProcessor errorProcessor = new ErrorProcessor(); + SuccessFinishProcessor baseProcessor = new SuccessFinishProcessor(errorProcessor); + QrDisplayProcessor qrDisplayProcessor = new QrDisplayProcessor(baseProcessor); + return new RetryProcessor(qrDisplayProcessor); + } + + @Bean + public RecurrentResultIntentResolver recurrentResultIntentResolver() { + return new UnsupportedGenerateTokenResultIntentResolver(); + } + + @Bean + public ResultIntentResolver resultIntentResolver(IntentResultQrPaymentFactory intentResultFactory) { + return new SimpleRedirectWithPollingResultIntentResolver(intentResultFactory); + } + + @Bean + public ServerFlowHandler generateTokenFlowHandler() { + return new ServerFlowHandler<>() { + }; + } + + @Bean + public IntentResultQrPaymentFactory intentResultFactory( + TimerProperties timerProperties, + TagManagementService tagManagementService, + ParametersSerializer parametersSerializer, + PollingInfoService pollingInfoService, + ErrorMapping errorMapping, + ExponentialBackOffPollingService exponentialBackOffPollingService) { + return new IntentResultQrPaymentFactory(timerProperties, tagManagementService, + parametersSerializer, pollingInfoService, errorMapping, exponentialBackOffPollingService); + } + + private List> getHandlers( + RemoteClient client, + EntryModelToBaseRequestModelConverter entryModelToBaseRequestModelConverter, + Processor baseProcessor) { + return List.of(new AuthHandler(client, entryModelToBaseRequestModelConverter, baseProcessor), + new CancelHandler(client, entryModelToBaseRequestModelConverter, baseProcessor), + new CaptureHandler(client, entryModelToBaseRequestModelConverter, baseProcessor), + new StatusHandler(client, entryModelToBaseRequestModelConverter, baseProcessor), + new DoNothingHandler(), + new PaymentHandler(client, entryModelToBaseRequestModelConverter, baseProcessor), + new RefundHandler(client, entryModelToBaseRequestModelConverter, baseProcessor)); + } +} diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/GenerateToken3ds1Test.java b/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/GenerateToken3ds1Test.java deleted file mode 100644 index 1aae56f..0000000 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/GenerateToken3ds1Test.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.vality.adapter.flow.lib.flow.simple.redirect; - -import com.fasterxml.jackson.core.JsonProcessingException; -import dev.vality.adapter.flow.lib.constant.Status; -import dev.vality.adapter.flow.lib.constant.Step; -import dev.vality.adapter.flow.lib.flow.AbstractGenerateTokenTest; -import dev.vality.adapter.flow.lib.flow.simple.redirect.config.SimpleRedirectWithPollingDsFlowConfig; -import dev.vality.adapter.flow.lib.flow.utils.BeanUtils; -import dev.vality.adapter.flow.lib.flow.utils.MockUtil; -import dev.vality.adapter.flow.lib.model.BaseResponseModel; -import dev.vality.damsel.proxy_provider.RecurrentTokenContext; -import dev.vality.damsel.proxy_provider.RecurrentTokenProxyResult; -import org.apache.thrift.TException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import java.util.Date; -import java.util.Map; - -import static dev.vality.adapter.flow.lib.flow.full.three.ds.ForwardRecurrentPaymentNon3dsTest.RECURRENT_TOKEN; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = SimpleRedirectWithPollingDsFlowConfig.class) -@TestPropertySource(properties = {"server.rest.port=8083", - "error-mapping.file=classpath:fixture/errors.json", - "service.secret.enabled=true"}) -public class GenerateToken3ds1Test extends AbstractGenerateTokenTest { - - @BeforeEach - public void setUp() throws TException { - MockitoAnnotations.openMocks(this); - MockUtil.mockAllWithout3Ds(cdsStorageClient, benderClient); - - BaseResponseModel baseResponseModel = BeanUtils.createBaseResponseModel(); - baseResponseModel.setThreeDsData(BeanUtils.create3Ds1(baseResponseModel)); - baseResponseModel.setStatus(Status.NEED_REDIRECT); - - BaseResponseModel baseResponseModelRec = BeanUtils.createBaseResponseModel(); - baseResponseModelRec.setRecurrentToken(RECURRENT_TOKEN); - - Mockito.when(client.generateToken(any())).thenReturn(baseResponseModel); - Mockito.when(client.status(any())).thenReturn(baseResponseModelRec); - } - - @Test - public void testPaymentOneStage() throws TException, JsonProcessingException { - // pay - Map options = MockUtil.buildOptionsOneStage(); - - RecurrentTokenContext paymentContext = - MockUtil.buildRecurrentTokenContext(String.valueOf(new Date().getTime()), - options); - RecurrentTokenProxyResult paymentProxyResult = serverHandlerLogDecorator.generateToken(paymentContext); - assertTrue(paymentProxyResult.getIntent().getSuspend().getUserInteraction().isSetRedirect()); - assertEquals(Step.CHECK_STATUS, - temporaryContextDeserializer.read(paymentProxyResult.getNextState()).getNextStep()); - - //checkStatus - paymentProxyResult = processWithDoNothingSuccessResult(paymentContext, paymentProxyResult); - } - -} diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/config/SimpleRedirectWithPollingDsFlowConfig.java b/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/config/SimpleRedirectWithPollingDsFlowConfig.java index ed63d7e..51a7b4e 100644 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/config/SimpleRedirectWithPollingDsFlowConfig.java +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/simple/redirect/config/SimpleRedirectWithPollingDsFlowConfig.java @@ -15,6 +15,7 @@ import dev.vality.adapter.flow.lib.flow.simple.SimpleRedirectWithPollingResultIn import dev.vality.adapter.flow.lib.flow.simple.SimpleRedirectWithPollingStepResolverImpl; import dev.vality.adapter.flow.lib.handler.CommonHandler; import dev.vality.adapter.flow.lib.handler.ServerFlowHandler; +import dev.vality.adapter.flow.lib.handler.ServerFlowHandlerImpl; import dev.vality.adapter.flow.lib.handler.payment.*; import dev.vality.adapter.flow.lib.model.BaseResponseModel; import dev.vality.adapter.flow.lib.model.EntryStateModel; @@ -52,7 +53,7 @@ public class SimpleRedirectWithPollingDsFlowConfig { StepResolver stepResolverImpl, CtxToEntryModelConverter ctxToEntryModelConverter, ExitModelToProxyResultConverter exitModelToProxyResultConverter) { - return new ServerFlowHandler<>( + return new ServerFlowHandlerImpl<>( getHandlers(client, entryModelToBaseRequestModelConverter, baseProcessor), stepResolverImpl, ctxToEntryModelConverter, @@ -78,7 +79,7 @@ public class SimpleRedirectWithPollingDsFlowConfig { StepResolver generateTokenStepResolverImpl, RecCtxToEntryModelConverter recCtxToEntryStateModelConverter, ExitModelToRecTokenProxyResultConverter exitModelToRecTokenProxyResultConverter) { - return new ServerFlowHandler<>( + return new ServerFlowHandlerImpl<>( getHandlers(client, entryModelToBaseRequestModelConverter, baseProcessor), generateTokenStepResolverImpl, recCtxToEntryStateModelConverter, diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/utils/BeanUtils.java b/src/test/java/dev/vality/adapter/flow/lib/flow/utils/BeanUtils.java index 2b66d63..a720801 100644 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/utils/BeanUtils.java +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/utils/BeanUtils.java @@ -5,6 +5,7 @@ import dev.vality.adapter.common.utils.CommonConverter; import dev.vality.adapter.flow.lib.constant.Status; import dev.vality.adapter.flow.lib.constant.ThreeDsType; import dev.vality.adapter.flow.lib.model.BaseResponseModel; +import dev.vality.adapter.flow.lib.model.QrDisplayData; import dev.vality.adapter.flow.lib.model.ThreeDsData; import java.nio.ByteBuffer; @@ -41,6 +42,13 @@ public class BeanUtils { .build(); } + public static QrDisplayData createQrDisplayData() { + return QrDisplayData.builder() + .tagId("testTagId") + .qrUrl("http://localhost/3ds") + .build(); + } + public static ThreeDsData create3Ds2FullCheck() { HashMap parameters = new HashMap<>(); parameters.put(THREE_DS_METHOD_DATA, "test_threeDsMethodData"); diff --git a/src/test/java/dev/vality/adapter/flow/lib/flow/utils/MockUtil.java b/src/test/java/dev/vality/adapter/flow/lib/flow/utils/MockUtil.java index e3605bc..fd6361e 100644 --- a/src/test/java/dev/vality/adapter/flow/lib/flow/utils/MockUtil.java +++ b/src/test/java/dev/vality/adapter/flow/lib/flow/utils/MockUtil.java @@ -171,6 +171,38 @@ public class MockUtil { .setOptions(options); } + public static PaymentContext buildPaymentContextPaymentTerminal(String invoiceId, Map options) { + return new PaymentContext() + .setSession(new Session() + .setTarget(TargetInvoicePaymentStatus.processed( + new InvoicePaymentProcessed()))) + .setPaymentInfo(new PaymentInfo() + .setInvoice(new Invoice() + .setId(invoiceId) + .setDetails(new InvoiceDetails() + .setDescription("details"))) + .setPayment(new InvoicePayment() + .setId("payment_id") + .setCreatedAt("2016-03-22T06:12:27Z") + .setPaymentResource(PaymentResource.disposable_payment_resource( + new DisposablePaymentResource() + .setClientInfo(new ClientInfo() + .setIpAddress("185.31.132.50")) + .setPaymentTool(buildPaymentToolPaymentTerminal()))) + .setCost(new Cash() + .setAmount(1200) + .setCurrency(new Currency() + .setSymbolicCode("RUB") + .setNumericCode((short) 643))) + .setContactInfo(new ContactInfo() + .setEmail("kkkk@kkk.ru") + .setPhoneNumber("89037772299")) + ) + ) + + .setOptions(options); + } + public static Map buildOptionsOneStage() { return Map.of(OptionFields.STAGE.name(), Stage.ONE); } @@ -190,6 +222,12 @@ public class MockUtil { ); } + public static PaymentTool buildPaymentToolPaymentTerminal() { + return PaymentTool.payment_terminal( + new PaymentTerminal() + ); + } + public static PaymentContext buildRecurrentPaymentContext(String invoiceId, String token) { PaymentContext paymentContext = MockUtil.buildPaymentContext(invoiceId, MockUtil.buildOptionsOneStage());