Init integration logic (#1)

This commit is contained in:
struga 2022-02-22 16:26:01 +03:00 committed by GitHub
parent 5ba0c3628a
commit a7872508fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 7283 additions and 0 deletions

10
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Maven Build Artifact
on:
pull_request:
branches:
- '*'
jobs:
build:
uses: valitydev/base-workflow/.github/workflows/maven-library-build.yml@v1

16
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Maven Deploy Artifact
on:
push:
branches:
- 'master'
- 'main'
jobs:
deploy:
uses: valitydev/base-workflow/.github/workflows/maven-library-deploy.yml@v1
secrets:
server-username: ${{ secrets.OSSRH_USERNAME }}
server-password: ${{ secrets.OSSRH_TOKEN }}
deploy-secret-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
deploy-secret-key-password: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}

78
.gitignore vendored Normal file
View File

@ -0,0 +1,78 @@
# Created by .ignore support plugin (hsz.mobi)
.eunit
deps
*.o
*.beam
*.plt
erl_crash.dump
ebin/*.beam
rel/example_project
.concrete/DEV_MODE
.rebar
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
*.iws
*.ipr
*.iml
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
env.list

View File

@ -0,0 +1,18 @@
### Имплементация клиента?
Во все методы клиента приходит полная модель со всеми необходимыми полями для вызова:
[BaseRequestModel](../src/main/java/dev/vality/adapter/flow/lib/model/BaseRequestModel.java)
После формирования необходимой внутренней модели и внешнего вызова, вы должны сформировать следующую модель:
[BaseResponseModel](../src/main/java/dev/vality/adapter/flow/lib/model/BaseResponseModel.java)
Необходимо учитывать следующие особенности:
* Можно влиять на выбор следующего шага с помощью
поля [BaseResponseModel.status](../src/main/java/dev/vality/adapter/flow/lib/constant/Status.java).
* Также если был выбран статус <b>NEED_RETRY</b>, есть возможность выбрать конкретную версию процесса, если он
существует в рамках вашего общего взаимодействия. Необходимо сверяться с общим флоу и необходимостью идти в ту или
иную ветку.
* При указании errorCode, в не зависимости от передаваемого статуса, процесс будет завершон с ошибкой.

12
docs/flow_using_lib.md Normal file
View File

@ -0,0 +1,12 @@
### Как использовать бибилиотеку?
1. Необходимо провести аналитику и определить какой процесс вам подходит. Список процессов:
* [Процесс с полным прохождением 3дс версии 1.0 и 2.0](./flows/Full Three Ds V1 V2 Flow Steps.md)
* [Простой процесс с редиректом и поллингом статусов после всех операций](./flows/Full Three Ds V1 V2 Flow Steps.md)
2. Если подходящий вам процесс найден, процесс продолжается по следующему пути:
1. Подготавливается конфигурация под spring-boot [настройка конфигурации](./spring-boot-configuration.md).
2. Релизовать, соответствующие выбранному флоу, методы
из [RemoteClient](../src/main/java/dev/vality/adapter/flow/lib/client/RemoteClient.java) [инструкция](./client_implementations_manual.md)
3. Реализовать валидаторы для ваших входных параметров, уникальных для вашего адаптера и настраевымых командой
поддержки поддержкой(options - на уровне протокола damsel) [интерфейс](../src/main/java/dev/vality/adapter/flow/lib/validator/Validator.java)
4. Реализация тестов

View File

@ -0,0 +1 @@
### Процесс с полным прохождением 3дс версии 1.0 и 2.0

View File

@ -0,0 +1 @@
### Простой процесс с редиректом и поллингом статусов после всех операций

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 119 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1 @@
### Конфигурация Spring Boot?

247
pom.xml Normal file
View File

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.vality</groupId>
<artifactId>library-parent-pom</artifactId>
<version>1.0.2</version>
<relativePath/>
</parent>
<artifactId>adapter-flow-lib</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>adapter-flow-lib</name>
<description>Adapter flows libs for reuse</description>
<url>https://github.com/valitydev/adapter-flow-lib.git</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<email>devs@vality.dev</email>
<organization>Vality</organization>
<organizationUrl>https://vality.dev</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/valitydev/library-parent-pom.git</connection>
<developerConnection>scm:git:ssh://github.com/valitydev/library-parent-pom.git</developerConnection>
<url>https://github.com/valitydev/library-parent-pom/tree/master</url>
</scm>
<properties>
<dockerfile.registry>${env.REGISTRY}</dockerfile.registry>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>15</java.version>
<shared.resources.version>0.3.6</shared.resources.version>
<bender-proto.version>1.20-be9cdeb</bender-proto.version>
<apache.commons.lang3.version>3.10</apache.commons.lang3.version>
<apache.commons.codec.version>1.15</apache.commons.codec.version>
<slf4j.version>1.7.33</slf4j.version>
<lombok.version>1.18.22</lombok.version>
<logback.version>1.2.10</logback.version>
<logstash-logback-encoder.version>7.0.1</logstash-logback-encoder.version>
<jackson.version>2.11.2</jackson.version>
<woody.version>[1.0.4,)</woody.version>
<damsel.version>1.544-dcd92dd</damsel.version>
<cds-proto.version>1.62-07f2b0f</cds-proto.version>
<error-mapping-java.version>1.0.6</error-mapping-java.version>
<adapter-client-lib.version>1.0.1</adapter-client-lib.version>
<adapter-common-lib.version>1.0.0</adapter-common-lib.version>
<junit-jupiter-engine.version>5.8.2</junit-jupiter-engine.version>
<nop-rolling.version>0.0.1</nop-rolling.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<spring.version>5.3.14</spring.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
<validation-api.version>2.0.1.Final</validation-api.version>
<error-mapping-java.version>1.0.0</error-mapping-java.version>
<jackson.version>2.11.2</jackson.version>
<spring-boot-starter.version>2.6.3</spring-boot-starter.version>
</properties>
<dependencies>
<!-- Third party libs -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot-starter.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${apache.commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${apache.commons.codec.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-logback-encoder.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--Vality libs-->
<dependency>
<groupId>dev.vality</groupId>
<artifactId>damsel</artifactId>
<version>${damsel.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.vality.woody</groupId>
<artifactId>woody-thrift</artifactId>
<version>${woody.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.vality.adapter-client-lib</groupId>
<artifactId>cds-client-storage</artifactId>
<version>${adapter-client-lib.version}</version>
</dependency>
<dependency>
<groupId>dev.vality.adapter-client-lib</groupId>
<artifactId>hellgate-adapter-client</artifactId>
<version>${adapter-client-lib.version}</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>bender-proto</artifactId>
<version>${bender-proto.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>error-mapping-java</artifactId>
<version>${error-mapping-java.version}</version>
</dependency>
<!-- Test -->
<!--Spring libs-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-starter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot-starter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot-starter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter-engine.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.vality.geck</groupId>
<artifactId>serializer</artifactId>
<version>0.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.vality.logback</groupId>
<artifactId>nop-rolling</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,53 @@
package dev.vality.adapter.flow.lib.client;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
import dev.vality.adapter.flow.lib.model.BaseResponseModel;
/**
* This interface is used to call an external system in order to perform some operation in the payment flow.
* Not all methods are required for flows.
*/
public interface RemoteClient {
default BaseResponseModel auth(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel generateToken(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel pay(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel capture(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel cancel(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel refund(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel finish3ds(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel check3dsV2(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel finish3dsV2(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
default BaseResponseModel status(BaseRequestModel request) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,11 @@
package dev.vality.adapter.flow.lib.constant;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MetaData {
public static final String META_REC_TOKEN = "meta_rec_token";
}

View File

@ -0,0 +1,7 @@
package dev.vality.adapter.flow.lib.constant;
public enum OptionFields {
STAGE
}

View File

@ -0,0 +1,13 @@
package dev.vality.adapter.flow.lib.constant;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum RedirectFields {
TERM_URL("TermUrl");
private final String value;
}

View File

@ -0,0 +1,11 @@
package dev.vality.adapter.flow.lib.constant;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Stage {
public static final String ONE = "ONE";
}

View File

@ -0,0 +1,22 @@
package dev.vality.adapter.flow.lib.constant;
public enum Status {
/**
* The operation will complete successfully or move on to the next step if a step exists.
*/
SUCCESS,
/**
* The operation will fail and exit.
*/
ERROR,
/**
* The operation will redirect if there are options for the redirect.
*/
NEED_REDIRECT,
/**
* This status need for cycle or retry operation (example: polling status).
*/
NEED_RETRY
}

View File

@ -0,0 +1,18 @@
package dev.vality.adapter.flow.lib.constant;
public enum Step {
AUTH,
PAY,
GENERATE_TOKEN,
FINISH_THREE_DS_V1,
FINISH_THREE_DS_V2,
CANCEL,
REFUND,
CHECK_STATUS,
CHECK_STATUS_3DS_V2,
CHECK_NEED_3DS_V2,
CAPTURE,
DO_NOTHING
}

View File

@ -0,0 +1,8 @@
package dev.vality.adapter.flow.lib.constant;
public enum TargetStatus {
PROCESSED,
CAPTURED,
CANCELLED,
REFUNDED
}

View File

@ -0,0 +1,9 @@
package dev.vality.adapter.flow.lib.constant;
public enum ThreeDsType {
V1,
V2_SIMPLE,
V2_FULL
}

View File

@ -0,0 +1,33 @@
package dev.vality.adapter.flow.lib.controller;
import dev.vality.adapter.flow.lib.service.ThreeDsAdapterService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@RestController
@RequestMapping({"/${server.rest.endpoint}"})
@RequiredArgsConstructor
public class ThreeDsCallbackController {
private final ThreeDsAdapterService threeDsAdapterService;
@PostMapping({"term-url"})
public String receivePaymentIncomingParameters(HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
return this.threeDsAdapterService.receivePaymentIncomingParameters(servletRequest, servletResponse);
}
@PostMapping({"recurrent-term-url"})
public String receiveRecurrentIncomingParameters(HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
return this.threeDsAdapterService.receiveRecurrentIncomingParameters(servletRequest, servletResponse);
}
}

View File

@ -0,0 +1,18 @@
package dev.vality.adapter.flow.lib.converter;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.model.TemporaryContext;
import org.springframework.core.convert.converter.Converter;
public class ExitStateModelToTemporaryContextConverter implements Converter<ExitStateModel, TemporaryContext> {
@Override
public TemporaryContext convert(ExitStateModel source) {
return TemporaryContext.builder()
.providerTrxId(source.getProviderTrxId())
.nextStep(source.getNextStep())
.pollingInfo(source.getPollingInfo())
.build();
}
}

View File

@ -0,0 +1,17 @@
package dev.vality.adapter.flow.lib.converter.base;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
@RequiredArgsConstructor
public class EntryModelToBaseRequestModelConverter implements Converter<EntryStateModel, BaseRequestModel> {
@Override
public BaseRequestModel convert(EntryStateModel entryStateModel) {
return entryStateModel.getBaseRequestModel();
}
}

View File

@ -0,0 +1,145 @@
package dev.vality.adapter.flow.lib.converter.entry;
import dev.vality.adapter.flow.lib.constant.MetaData;
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.IdGenerator;
import dev.vality.adapter.flow.lib.service.TemporaryContextService;
import dev.vality.adapter.flow.lib.utils.CallbackUrlExtractor;
import dev.vality.adapter.flow.lib.utils.CardDataUtils;
import dev.vality.adapter.flow.lib.utils.TargetStatusResolver;
import dev.vality.cds.client.storage.CdsClientStorage;
import dev.vality.cds.client.storage.utils.BankCardExtractor;
import dev.vality.cds.storage.Auth3DS;
import dev.vality.cds.storage.CardData;
import dev.vality.cds.storage.SessionData;
import dev.vality.damsel.domain.BankCard;
import dev.vality.damsel.domain.TransactionInfo;
import dev.vality.damsel.proxy_provider.*;
import dev.vality.java.cds.utils.model.CardDataProxyModel;
import dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators;
import dev.vality.java.damsel.utils.extractors.ProxyProviderPackageExtractors;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
public class CtxToEntryModelConverter implements Converter<PaymentContext, EntryStateModel> {
private final CdsClientStorage cdsStorage;
private final TemporaryContextDeserializer temporaryContextDeserializer;
private final IdGenerator idGenerator;
private final TemporaryContextService temporaryContextService;
private final CallbackUrlExtractor callbackUrlExtractor;
@Override
public EntryStateModel convert(PaymentContext context) {
var paymentInfo = context.getPaymentInfo();
var payment = paymentInfo.getPayment();
var temporaryContext = temporaryContextService.getTemporaryContext(context, temporaryContextDeserializer);
var paymentResource = payment.getPaymentResource();
var mobilePaymentDataBuilder = MobilePaymentData.builder();
var cardDataBuilder = dev.vality.adapter.flow.lib.model.CardData.builder();
Step currentStep = temporaryContext.getNextStep();
TargetStatus targetStatus = TargetStatusResolver.extractTargetStatus(context.getSession().getTarget());
initPaymentData(context, paymentResource, mobilePaymentDataBuilder, cardDataBuilder, currentStep, targetStatus);
TransactionInfo trx = payment.getTrx();
RecurrentPaymentData recurrentPaymentData = initRecurrentPaymentData(payment, paymentResource, trx);
Map<String, String> adapterConfigurations = context.getOptions();
return EntryStateModel.builder()
.baseRequestModel(BaseRequestModel.builder().recurrentPaymentData(recurrentPaymentData)
.mobilePaymentData(mobilePaymentDataBuilder.build())
.cardData(cardDataBuilder.build())
.refundData(initRefundData(paymentInfo))
.paymentId(idGenerator.get(paymentInfo.getInvoice().getId()))
.createdAt(paymentInfo.getPayment().getCreatedAt())
.currency(payment.getCost().getCurrency().getSymbolicCode())
.amount(payment.getCost().getAmount())
.details(paymentInfo.getInvoice().getDetails().getDescription())
.payerInfo(PayerInfo.builder()
.ip(ProxyProviderPackageCreators.extractIpAddress(context))
.build())
.adapterConfigurations(adapterConfigurations)
.providerTrxId(trx != null ? trx.getId() : temporaryContext.getProviderTrxId())
.savedData(trx != null ? trx.getExtra() : new HashMap<>())
.successRedirectUrl(getSuccessRedirectUrl(payment, adapterConfigurations))
.build())
.targetStatus(targetStatus)
.currentStep(currentStep)
.build();
}
private String getSuccessRedirectUrl(InvoicePayment payment, Map<String, String> adapterConfigurations) {
return callbackUrlExtractor.getSuccessRedirectUrl(
adapterConfigurations,
payment.isSetPayerSessionInfo()
? payment.getPayerSessionInfo().getRedirectUrl()
: null);
}
private void initPaymentData(PaymentContext context, PaymentResource paymentResource,
MobilePaymentData.MobilePaymentDataBuilder<?, ?> mobilePaymentDataBuilder,
dev.vality.adapter.flow.lib.model.CardData.CardDataBuilder<?, ?> cardDataBuilder,
Step currentStep, TargetStatus targetStatus) {
if (paymentResource.isSetDisposablePaymentResource()
&& currentStep == null
&& targetStatus == TargetStatus.PROCESSED) {
SessionData sessionData = cdsStorage.getSessionData(context);
if (sessionData.isSetAuthData() && sessionData.getAuthData().isSetAuth3ds()) {
Auth3DS auth3ds = sessionData.getAuthData().getAuth3ds();
mobilePaymentDataBuilder.cryptogram(auth3ds.getCryptogram())
.eci(auth3ds.getEci());
} else {
CardDataProxyModel cardData = getCardData(context, paymentResource);
cardDataBuilder.cardHolder(cardData.getCardholderName())
.pan(cardData.getPan())
.cvv2(CardDataUtils.extractCvv2(sessionData))
.expYear(cardData.getExpYear())
.expMonth(cardData.getExpMonth());
}
}
}
private RefundData initRefundData(PaymentInfo paymentInfo) {
RefundData refundData = null;
if (paymentInfo.isSetRefund()) {
InvoicePaymentRefund refund = paymentInfo.getRefund();
refundData = RefundData.builder()
.id(refund.getId())
.amount(refund.getCash().getAmount())
.build();
}
return refundData;
}
private RecurrentPaymentData initRecurrentPaymentData(InvoicePayment payment,
PaymentResource paymentResource,
TransactionInfo transactionInfo) {
var recurrentPaymentDataBuilder = RecurrentPaymentData.builder()
.makeRecurrent(payment.make_recurrent);
if (paymentResource.isSetRecurrentPaymentResource()) {
recurrentPaymentDataBuilder.recToken(paymentResource.getRecurrentPaymentResource().getRecToken());
} else if (transactionInfo != null && transactionInfo.getExtra() != null
&& transactionInfo.getExtra().containsKey(MetaData.META_REC_TOKEN)) {
recurrentPaymentDataBuilder.recToken(transactionInfo.getExtra().get(MetaData.META_REC_TOKEN));
}
return recurrentPaymentDataBuilder.build();
}
private CardDataProxyModel getCardData(PaymentContext context, PaymentResource paymentResource) {
if (paymentResource.isSetDisposablePaymentResource()) {
String cardToken = ProxyProviderPackageExtractors.extractBankCardToken(paymentResource);
CardData cardData = cdsStorage.getCardData(cardToken);
BankCard bankCard = ProxyProviderPackageExtractors.extractBankCard(context);
return BankCardExtractor.initCardDataProxyModel(bankCard, cardData);
}
return cdsStorage.getCardData(context);
}
}

View File

@ -0,0 +1,125 @@
package dev.vality.adapter.flow.lib.converter.entry;
import dev.vality.adapter.flow.lib.constant.MetaData;
import dev.vality.adapter.flow.lib.model.*;
import dev.vality.adapter.flow.lib.serde.TemporaryContextDeserializer;
import dev.vality.adapter.flow.lib.service.IdGenerator;
import dev.vality.adapter.flow.lib.service.TemporaryContextService;
import dev.vality.adapter.flow.lib.utils.CardDataUtils;
import dev.vality.cds.client.storage.CdsClientStorage;
import dev.vality.cds.client.storage.utils.BankCardExtractor;
import dev.vality.cds.storage.Auth3DS;
import dev.vality.cds.storage.CardData;
import dev.vality.cds.storage.SessionData;
import dev.vality.damsel.domain.BankCard;
import dev.vality.damsel.domain.DisposablePaymentResource;
import dev.vality.damsel.domain.PaymentTool;
import dev.vality.damsel.domain.TransactionInfo;
import dev.vality.damsel.proxy_provider.RecurrentPaymentTool;
import dev.vality.damsel.proxy_provider.RecurrentTokenContext;
import dev.vality.java.cds.utils.model.CardDataProxyModel;
import dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators;
import dev.vality.java.damsel.utils.extractors.ProxyProviderPackageExtractors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import java.util.HashMap;
import java.util.UUID;
@Slf4j
@RequiredArgsConstructor
public class RecCtxToEntryModelConverter implements Converter<RecurrentTokenContext, EntryStateModel> {
private final TemporaryContextDeserializer temporaryContextDeserializer;
private final CdsClientStorage cdsStorage;
private final IdGenerator idGenerator;
private final TemporaryContextService temporaryContextService;
@Override
public EntryStateModel convert(RecurrentTokenContext context) {
var generalExitStateModel = temporaryContextService.getTemporaryContext(
context, temporaryContextDeserializer);
var tokenInfo = context.getTokenInfo();
var recurrentPaymentTool = tokenInfo.getPaymentTool();
var paymentResource = recurrentPaymentTool.getPaymentResource();
var paymentTool = paymentResource.getPaymentTool();
validatePaymentTool(paymentTool);
var entryStateModelBuilder =
EntryStateModel.builder();
var mobilePaymentDataBuilder = MobilePaymentData.builder();
var cardDataBuilder = dev.vality.adapter.flow.lib.model.CardData.builder();
initPaymentData(context, generalExitStateModel, paymentResource, mobilePaymentDataBuilder, cardDataBuilder);
TransactionInfo transactionInfo = tokenInfo.getTrx();
Long orderId = idGenerator.get(tokenInfo.getPaymentTool().getId());
return entryStateModelBuilder
.baseRequestModel(BaseRequestModel.builder()
.recurrentPaymentData(RecurrentPaymentData
.builder()
.makeRecurrent(true)
.recToken(transactionInfo != null && transactionInfo.getExtra() != null
? transactionInfo.getExtra().get(MetaData.META_REC_TOKEN)
: null)
.build())
.mobilePaymentData(mobilePaymentDataBuilder.build())
.cardData(cardDataBuilder.build())
.refundData(initRefundData(recurrentPaymentTool, orderId))
.paymentId(orderId)
.currency(recurrentPaymentTool.getMinimalPaymentCost().getCurrency().getSymbolicCode())
.amount(recurrentPaymentTool.getMinimalPaymentCost().getAmount())
.details(recurrentPaymentTool.getId())
.payerInfo(PayerInfo.builder()
.ip(ProxyProviderPackageCreators.extractIpAddress(context))
.build())
.adapterConfigurations(context.getOptions())
.providerTrxId(transactionInfo != null ? transactionInfo.getId() : null)
.savedData(transactionInfo == null || transactionInfo.getExtra() == null
? new HashMap<>()
: transactionInfo.getExtra())
.build())
.currentStep(generalExitStateModel.getNextStep())
.build();
}
private void initPaymentData(RecurrentTokenContext context, TemporaryContext generalExitStateModel,
DisposablePaymentResource paymentResource,
MobilePaymentData.MobilePaymentDataBuilder<?, ?> mobilePaymentDataBuilder,
dev.vality.adapter.flow.lib.model.CardData.CardDataBuilder<?, ?> cardDataBuilder) {
if (generalExitStateModel == null || generalExitStateModel.getNextStep() == null) {
SessionData sessionData = cdsStorage.getSessionData(context);
if (sessionData.getAuthData().isSetAuth3ds()) {
Auth3DS auth3ds = sessionData.getAuthData().getAuth3ds();
mobilePaymentDataBuilder.cryptogram(auth3ds.getCryptogram())
.eci(auth3ds.getEci());
} else {
CardDataProxyModel cardData = getCardData(context, paymentResource);
cardDataBuilder.cardHolder(cardData.getCardholderName())
.pan(cardData.getPan())
.cvv2(CardDataUtils.extractCvv2(sessionData))
.expYear(cardData.getExpYear())
.expMonth(cardData.getExpMonth());
}
}
}
private void validatePaymentTool(PaymentTool paymentTool) {
if (!paymentTool.isSetBankCard()) {
throw new IllegalArgumentException("Wrong recurrentPaymentTool. It should be bank card");
}
}
private RefundData initRefundData(RecurrentPaymentTool recurrentPaymentTool, Long orderId) {
return RefundData.builder()
.id(String.valueOf(orderId))
.amount(recurrentPaymentTool.getMinimalPaymentCost().getAmount())
.build();
}
private CardDataProxyModel getCardData(RecurrentTokenContext context, DisposablePaymentResource paymentResource) {
String cardToken = ProxyProviderPackageExtractors.extractBankCardToken(paymentResource);
CardData cardData = cdsStorage.getCardData(cardToken);
BankCard bankCard = ProxyProviderPackageExtractors.extractBankCard(context);
return BankCardExtractor.initCardDataProxyModel(bankCard, cardData);
}
}

View File

@ -0,0 +1,42 @@
package dev.vality.adapter.flow.lib.converter.exit;
import dev.vality.adapter.flow.lib.converter.ExitStateModelToTemporaryContextConverter;
import dev.vality.adapter.flow.lib.flow.ResultIntentResolver;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.serde.TemporaryContextSerializer;
import dev.vality.adapter.flow.lib.service.IntentResultFactory;
import dev.vality.damsel.proxy_provider.Intent;
import dev.vality.damsel.proxy_provider.PaymentProxyResult;
import dev.vality.java.damsel.utils.creators.DomainPackageCreators;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
@Slf4j
@RequiredArgsConstructor
public class ExitModelToProxyResultConverter implements Converter<ExitStateModel, PaymentProxyResult> {
private final IntentResultFactory intentResultFactory;
private final TemporaryContextSerializer serializer;
private final ResultIntentResolver resultIntentResolver;
private final ExitStateModelToTemporaryContextConverter contextConverter;
@Override
public PaymentProxyResult convert(ExitStateModel exitStateModel) {
if (StringUtils.hasText(exitStateModel.getErrorCode())) {
return new PaymentProxyResult(intentResultFactory.createFinishIntentFailed(exitStateModel));
}
Intent intent = resultIntentResolver.initIntentByStep(exitStateModel);
return new PaymentProxyResult(intent)
.setNextState(serializer.writeByte(contextConverter.convert(exitStateModel)))
.setTrx(DomainPackageCreators.createTransactionInfo(
exitStateModel.getProviderTrxId(), exitStateModel.getTrxExtra())
);
}
}

View File

@ -0,0 +1,43 @@
package dev.vality.adapter.flow.lib.converter.exit;
import dev.vality.adapter.flow.lib.converter.ExitStateModelToTemporaryContextConverter;
import dev.vality.adapter.flow.lib.flow.RecurrentResultIntentResolver;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.serde.TemporaryContextSerializer;
import dev.vality.adapter.flow.lib.service.RecurrentIntentResultFactory;
import dev.vality.damsel.proxy_provider.RecurrentTokenIntent;
import dev.vality.damsel.proxy_provider.RecurrentTokenProxyResult;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import static dev.vality.java.damsel.utils.creators.DomainPackageCreators.createTransactionInfo;
@RequiredArgsConstructor
public class ExitModelToRecTokenProxyResultConverter implements Converter<ExitStateModel, RecurrentTokenProxyResult> {
private final RecurrentIntentResultFactory recurrentIntentResultFactory;
private final TemporaryContextSerializer serializer;
private final RecurrentResultIntentResolver recurrentResultIntentResolver;
private final ExitStateModelToTemporaryContextConverter contextConverter;
@Override
public RecurrentTokenProxyResult convert(ExitStateModel exitStateModel) {
if (exitStateModel.getErrorCode() != null) {
return new RecurrentTokenProxyResult(
recurrentIntentResultFactory.createFinishIntentFailed(
exitStateModel.getErrorCode(),
exitStateModel.getErrorMessage())
);
}
RecurrentTokenIntent intent = recurrentResultIntentResolver.initIntentByStep(exitStateModel);
return new RecurrentTokenProxyResult(intent)
.setNextState(serializer.writeByte(contextConverter.convert(exitStateModel)))
.setTrx(createTransactionInfo(
exitStateModel.getProviderTrxId(), exitStateModel.getTrxExtra())
);
}
}

View File

@ -0,0 +1,24 @@
package dev.vality.adapter.flow.lib.exception;
public class DataNotCorrespondStateException extends RuntimeException {
public DataNotCorrespondStateException() {
}
public DataNotCorrespondStateException(String message) {
super(message);
}
public DataNotCorrespondStateException(String message, Throwable cause) {
super(message, cause);
}
public DataNotCorrespondStateException(Throwable cause) {
super(cause);
}
public DataNotCorrespondStateException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,19 @@
package dev.vality.adapter.flow.lib.exception;
public class DeserializationException extends RuntimeException {
public DeserializationException() {
super();
}
public DeserializationException(String message) {
super(message);
}
public DeserializationException(Throwable cause) {
super(cause);
}
public DeserializationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,23 @@
package dev.vality.adapter.flow.lib.exception;
public class UnknownHandlerForStepException extends RuntimeException {
public UnknownHandlerForStepException() {
}
public UnknownHandlerForStepException(String message) {
super(message);
}
public UnknownHandlerForStepException(String message, Throwable cause) {
super(message, cause);
}
public UnknownHandlerForStepException(Throwable cause) {
super(cause);
}
public UnknownHandlerForStepException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,8 @@
package dev.vality.adapter.flow.lib.exception;
public class UnknownTargetStatusException extends RuntimeException {
public UnknownTargetStatusException() {
super("Unknown target status!");
}
}

View File

@ -0,0 +1,20 @@
package dev.vality.adapter.flow.lib.flow;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.utils.StageFlowResolver;
public abstract class AbstractGenerateTokenStepResolver<T extends EntryStateModel, R extends ExitStateModel>
implements StepResolver<T, R> {
@Override
public Step resolveCurrentStep(EntryStateModel stateModel) {
Step currentStep = stateModel.getCurrentStep();
if (currentStep != null) {
return currentStep;
}
return StageFlowResolver.isOneStageFlow(stateModel) ? Step.PAY : Step.AUTH;
}
}

View File

@ -0,0 +1,25 @@
package dev.vality.adapter.flow.lib.flow;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.utils.StageFlowResolver;
public abstract class AbstractPaymentStepResolver<T extends EntryStateModel, R extends ExitStateModel>
implements StepResolver<T, R> {
@Override
public Step resolveCurrentStep(T entryStateModel) {
Step currentStep = entryStateModel.getCurrentStep();
if (currentStep != null) {
return currentStep;
}
return switch (entryStateModel.getTargetStatus()) {
case PROCESSED -> StageFlowResolver.isOneStageFlow(entryStateModel) ? Step.PAY : Step.AUTH;
case CAPTURED -> StageFlowResolver.isOneStageFlow(entryStateModel) ? Step.DO_NOTHING : Step.CAPTURE;
case CANCELLED -> Step.CANCEL;
case REFUNDED -> Step.REFUND;
};
}
}

View File

@ -0,0 +1,10 @@
package dev.vality.adapter.flow.lib.flow;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.damsel.proxy_provider.RecurrentTokenIntent;
public interface RecurrentResultIntentResolver {
RecurrentTokenIntent initIntentByStep(ExitStateModel exitStateModel);
}

View File

@ -0,0 +1,10 @@
package dev.vality.adapter.flow.lib.flow;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.damsel.proxy_provider.Intent;
public interface ResultIntentResolver {
Intent initIntentByStep(ExitStateModel exitStateModel);
}

View File

@ -0,0 +1,13 @@
package dev.vality.adapter.flow.lib.flow;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
public interface StepResolver<T extends EntryStateModel, R extends ExitStateModel> {
Step resolveCurrentStep(T entryStateModel);
Step resolveNextStep(R exitStateModel);
}

View File

@ -0,0 +1,39 @@
package dev.vality.adapter.flow.lib.flow.full;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.AbstractPaymentStepResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
public class FullThreeDsAllVersionsStepResolverImpl
extends AbstractPaymentStepResolver<EntryStateModel, ExitStateModel> {
@Override
public Step resolveNextStep(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step step = entryStateModel.getCurrentStep();
switch (step) {
case AUTH, PAY:
if (ThreeDsBranchResolver.isRedirectForThreeDsV1(exitStateModel)) {
return Step.FINISH_THREE_DS_V1;
} else if (ThreeDsBranchResolver.isRedirectForThreeDsV2Simple(exitStateModel)) {
return Step.FINISH_THREE_DS_V2;
} else if (ThreeDsBranchResolver.isRedirectForThreeDsV2Full(exitStateModel)) {
return Step.CHECK_NEED_3DS_V2;
} else {
return Step.DO_NOTHING;
}
case CHECK_NEED_3DS_V2:
if (ThreeDsBranchResolver.isRedirectForThreeDsV2Full(exitStateModel)) {
return Step.FINISH_THREE_DS_V2;
} else {
return Step.DO_NOTHING;
}
case FINISH_THREE_DS_V1, FINISH_THREE_DS_V2, CANCEL, REFUND, CAPTURE:
return Step.DO_NOTHING;
default:
return step;
}
}
}

View File

@ -0,0 +1,43 @@
package dev.vality.adapter.flow.lib.flow.full;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.AbstractGenerateTokenStepResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
public class GenerateTokenFullThreeDsAllVersionsStepResolverImpl extends
AbstractGenerateTokenStepResolver<EntryStateModel, ExitStateModel> {
@Override
public Step resolveNextStep(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step step = entryStateModel.getCurrentStep();
switch (step) {
case AUTH, PAY:
if (ThreeDsBranchResolver.isRedirectForThreeDsV1(exitStateModel)) {
return Step.FINISH_THREE_DS_V1;
} else if (ThreeDsBranchResolver.isRedirectForThreeDsV2Simple(exitStateModel)) {
return Step.FINISH_THREE_DS_V2;
} else if (ThreeDsBranchResolver.isRedirectForThreeDsV2Full(exitStateModel)) {
return Step.CHECK_NEED_3DS_V2;
} else {
return Step.CAPTURE;
}
case CHECK_NEED_3DS_V2:
if (ThreeDsBranchResolver.isRedirectForThreeDsV2Full(exitStateModel)) {
return Step.FINISH_THREE_DS_V2;
} else {
return Step.CAPTURE;
}
case FINISH_THREE_DS_V1, FINISH_THREE_DS_V2:
return Step.CAPTURE;
case CAPTURE:
return Step.REFUND;
case REFUND:
return Step.DO_NOTHING;
default:
return step;
}
}
}

View File

@ -0,0 +1,27 @@
package dev.vality.adapter.flow.lib.flow.full;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.RecurrentResultIntentResolver;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.service.RecurrentIntentResultFactory;
import dev.vality.damsel.proxy_provider.RecurrentTokenIntent;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class GenerateTokenResultIntentResolverImpl implements RecurrentResultIntentResolver {
private final RecurrentIntentResultFactory recurrentIntentResultFactory;
@Override
public RecurrentTokenIntent initIntentByStep(ExitStateModel exitStateModel) {
Step nextStep = exitStateModel.getNextStep();
return switch (nextStep) {
case REFUND, CAPTURE -> recurrentIntentResultFactory.createSleepIntentForReinvocation();
case FINISH_THREE_DS_V1, FINISH_THREE_DS_V2 -> recurrentIntentResultFactory.createIntentWithSuspension(
exitStateModel);
case DO_NOTHING -> recurrentIntentResultFactory.createFinishIntent(exitStateModel.getRecToken());
default -> throw new IllegalStateException("Wrong state: " + nextStep);
};
}
}

View File

@ -0,0 +1,42 @@
package dev.vality.adapter.flow.lib.flow.full;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.ResultIntentResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.service.IntentResultFactory;
import dev.vality.damsel.proxy_provider.Intent;
import lombok.RequiredArgsConstructor;
import static dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators.createFinishIntentSuccess;
@RequiredArgsConstructor
public class ResultIntentResolverImpl implements ResultIntentResolver {
private final IntentResultFactory intentResultFactory;
@Override
public Intent initIntentByStep(ExitStateModel exitStateModel) {
Step nextStep = exitStateModel.getNextStep();
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step currentStep = entryStateModel.getCurrentStep();
return switch (nextStep) {
case FINISH_THREE_DS_V1, CHECK_NEED_3DS_V2, FINISH_THREE_DS_V2 -> intentResultFactory
.createSuspendIntentWithFailedAfterTimeout(exitStateModel);
case DO_NOTHING -> createIntentByCurrentStep(exitStateModel, currentStep);
case CAPTURE, REFUND, CANCEL -> createFinishIntentSuccess();
default -> throw new IllegalStateException("Wrong nextStep: " + nextStep);
};
}
private Intent createIntentByCurrentStep(ExitStateModel exitStateModel, Step currentStep) {
return switch (currentStep) {
case CHECK_NEED_3DS_V2, FINISH_THREE_DS_V1, FINISH_THREE_DS_V2,
DO_NOTHING, PAY, AUTH, CAPTURE -> intentResultFactory
.createFinishIntentSuccessWithCheckToken(exitStateModel);
case REFUND, CANCEL -> createFinishIntentSuccess();
default -> throw new IllegalStateException("Wrong currentStep: " + currentStep);
};
}
}

View File

@ -0,0 +1,30 @@
package dev.vality.adapter.flow.lib.flow.full;
import dev.vality.adapter.flow.lib.constant.Status;
import dev.vality.adapter.flow.lib.constant.ThreeDsType;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ThreeDsBranchResolver {
public static boolean isRedirectForThreeDsV2Full(ExitStateModel exitStateModel) {
return isRedirectForThreeDsType(exitStateModel, ThreeDsType.V2_FULL);
}
public static boolean isRedirectForThreeDsV2Simple(ExitStateModel exitStateModel) {
return isRedirectForThreeDsType(exitStateModel, ThreeDsType.V2_SIMPLE);
}
public static boolean isRedirectForThreeDsV1(ExitStateModel exitStateModel) {
return isRedirectForThreeDsType(exitStateModel, ThreeDsType.V1);
}
private static boolean isRedirectForThreeDsType(ExitStateModel exitStateModel, ThreeDsType threeDsType) {
return exitStateModel.getLastOperationStatus() == Status.NEED_REDIRECT
&& exitStateModel.getThreeDsData() != null
&& exitStateModel.getThreeDsData().getThreeDsType() == threeDsType;
}
}

View File

@ -0,0 +1,33 @@
package dev.vality.adapter.flow.lib.flow.simple;
import dev.vality.adapter.flow.lib.constant.Status;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.AbstractGenerateTokenStepResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
public class GenerateTokenSimpleRedirectWithPollingStepResolverImpl extends
AbstractGenerateTokenStepResolver<EntryStateModel, ExitStateModel> {
@Override
public Step resolveCurrentStep(EntryStateModel stateModel) {
Step currentStep = stateModel.getCurrentStep();
if (currentStep != null) {
return currentStep;
}
return Step.GENERATE_TOKEN;
}
@Override
public Step resolveNextStep(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step step = entryStateModel.getCurrentStep();
return switch (step) {
case GENERATE_TOKEN -> Step.CHECK_STATUS;
case CHECK_STATUS -> exitStateModel.getLastOperationStatus() == Status.NEED_RETRY
? Step.CHECK_STATUS
: Step.DO_NOTHING;
default -> Step.DO_NOTHING;
};
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.flow.simple;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.RecurrentResultIntentResolver;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.service.RecurrentIntentResultFactory;
import dev.vality.damsel.proxy_provider.RecurrentTokenIntent;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SimpleRedirectGenerateTokenResultIntentResolver implements RecurrentResultIntentResolver {
private final RecurrentIntentResultFactory recurrentIntentResultFactory;
@Override
public RecurrentTokenIntent initIntentByStep(ExitStateModel exitStateModel) {
Step nextStep = exitStateModel.getNextStep();
Step currentStep = exitStateModel.getEntryStateModel().getCurrentStep();
return switch (nextStep) {
case CHECK_STATUS -> currentStep == Step.GENERATE_TOKEN
? recurrentIntentResultFactory.createIntentWithSuspension(exitStateModel)
: recurrentIntentResultFactory.createSleepIntentWithExponentialPolling(exitStateModel);
case DO_NOTHING -> recurrentIntentResultFactory.createFinishIntent(exitStateModel.getRecToken());
default -> throw new IllegalStateException("Wrong state: " + nextStep);
};
}
}

View File

@ -0,0 +1,24 @@
package dev.vality.adapter.flow.lib.flow.simple;
import dev.vality.adapter.flow.lib.constant.Status;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.AbstractPaymentStepResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
public class SimpleRedirectWIthPollingStepResolverImpl
extends AbstractPaymentStepResolver<EntryStateModel, ExitStateModel> {
@Override
public Step resolveNextStep(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step step = entryStateModel.getCurrentStep();
return switch (step) {
case AUTH, PAY, CAPTURE, REFUND, CANCEL -> Step.CHECK_STATUS;
case CHECK_STATUS -> exitStateModel.getLastOperationStatus() == Status.NEED_RETRY
? Step.CHECK_STATUS
: Step.DO_NOTHING;
default -> step;
};
}
}

View File

@ -0,0 +1,41 @@
package dev.vality.adapter.flow.lib.flow.simple;
import dev.vality.adapter.flow.lib.constant.Status;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.flow.ResultIntentResolver;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.service.IntentResultFactory;
import dev.vality.damsel.proxy_provider.Intent;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SimpleRedirectWithPollingResultIntentResolver implements ResultIntentResolver {
private final IntentResultFactory intentResultFactory;
@Override
public Intent initIntentByStep(ExitStateModel exitStateModel) {
Step nextStep = exitStateModel.getNextStep();
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
Step currentStep = entryStateModel.getCurrentStep();
return switch (nextStep) {
case CHECK_STATUS -> exitStateModel.getLastOperationStatus() == Status.NEED_REDIRECT
? intentResultFactory.createSuspendIntentWithCallbackAfterTimeout(exitStateModel)
: intentResultFactory.createSleepIntentWithExponentialPolling(exitStateModel);
case DO_NOTHING -> createIntentByCurrentStep(exitStateModel, currentStep);
case REFUND, CANCEL -> intentResultFactory.createFinishIntentSuccess();
default -> throw new IllegalStateException("Wrong nextStep: " + nextStep);
};
}
private Intent createIntentByCurrentStep(ExitStateModel exitStateModel, Step currentStep) {
return switch (currentStep) {
case CHECK_STATUS, CHECK_NEED_3DS_V2, FINISH_THREE_DS_V1, FINISH_THREE_DS_V2, DO_NOTHING,
PAY, AUTH -> intentResultFactory.createFinishIntentSuccessWithCheckToken(exitStateModel);
case REFUND, CANCEL -> intentResultFactory.createFinishIntentSuccess();
default -> throw new IllegalStateException("Wrong currentStep: " + currentStep);
};
}
}

View File

@ -0,0 +1,11 @@
package dev.vality.adapter.flow.lib.handler;
import org.apache.thrift.TException;
public interface CommonHandler<T, E> {
boolean isHandle(E model);
T handle(E context) throws TException;
}

View File

@ -0,0 +1,25 @@
package dev.vality.adapter.flow.lib.handler;
import dev.vality.adapter.flow.lib.processor.Processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import java.util.function.Function;
@Slf4j
@RequiredArgsConstructor
public abstract class CommonHandlerImpl<P, R, E, T> implements CommonHandler<T, E> {
private final Function<P, R> requestFunction;
private final Converter<E, P> converter;
private final Processor<T, R, E> processor;
@Override
public T handle(E entryStateModel) {
P request = converter.convert(entryStateModel);
R response = requestFunction.apply(request);
return processor.process(response, entryStateModel);
}
}

View File

@ -0,0 +1,48 @@
package dev.vality.adapter.flow.lib.handler;
import dev.vality.adapter.flow.lib.handler.callback.CallbackHandler;
import dev.vality.adapter.flow.lib.validator.AdapterConfigurationValidator;
import dev.vality.damsel.proxy_provider.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TException;
import java.nio.ByteBuffer;
@Slf4j
@RequiredArgsConstructor
public class ProxyProviderServiceImpl implements ProviderProxySrv.Iface {
private final CallbackHandler<PaymentCallbackResult, PaymentContext> paymentCallbackHandler;
private final CallbackHandler<RecurrentTokenCallbackResult, RecurrentTokenContext> recurrentTokenCallbackHandler;
private final ServerFlowHandler<PaymentContext, PaymentProxyResult> serverFlowHandler;
private final ServerFlowHandler<RecurrentTokenContext, RecurrentTokenProxyResult> generateTokenFlowHandler;
private final AdapterConfigurationValidator adapterConfigurationValidator;
@Override
public RecurrentTokenProxyResult generateToken(RecurrentTokenContext context) throws TException {
adapterConfigurationValidator.validate(context.getOptions());
return generateTokenFlowHandler.handle(context);
}
@Override
public RecurrentTokenCallbackResult handleRecurrentTokenCallback(
ByteBuffer byteBuffer,
RecurrentTokenContext context
) throws TException {
return recurrentTokenCallbackHandler.handleCallback(byteBuffer, context);
}
@Override
public PaymentProxyResult processPayment(PaymentContext context) throws TException {
adapterConfigurationValidator.validate(context.getOptions());
return serverFlowHandler.handle(context);
}
@Override
public PaymentCallbackResult handlePaymentCallback(ByteBuffer byteBuffer, PaymentContext context)
throws TException {
return paymentCallbackHandler.handleCallback(byteBuffer, context);
}
}

View File

@ -0,0 +1,43 @@
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 ServerFlowHandler<T, R> {
private final List<CommonHandler<ExitStateModel, EntryStateModel>> handlers;
private final StepResolver<EntryStateModel, ExitStateModel> stepResolver;
private final Converter<T, EntryStateModel> entryConverter;
private final Converter<ExitStateModel, R> 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);
}
private EntryStateModel prepareEntryState(Converter<T, EntryStateModel> entryConverter, T context) {
EntryStateModel entryStateModel = entryConverter.convert(context);
entryStateModel.setCurrentStep(stepResolver.resolveCurrentStep(entryStateModel));
return entryStateModel;
}
}

View File

@ -0,0 +1,83 @@
package dev.vality.adapter.flow.lib.handler;
import dev.vality.adapter.flow.lib.utils.PaymentResourceTypeResolver;
import dev.vality.damsel.proxy_provider.*;
import dev.vality.java.damsel.utils.extractors.ProxyProviderPackageExtractors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TException;
import java.nio.ByteBuffer;
import static dev.vality.java.damsel.utils.verification.ProxyProviderVerification.isUndefinedResultOrUnavailable;
@Slf4j
@RequiredArgsConstructor
public class ServerHandlerLogDecorator implements ProviderProxySrv.Iface {
private final ProviderProxySrv.Iface handler;
@Override
public RecurrentTokenProxyResult generateToken(RecurrentTokenContext context) throws TException {
String recurrentId = ProxyProviderPackageExtractors.extractRecurrentId(context);
log.info("Generate token started with recurrentId {}", recurrentId);
try {
RecurrentTokenProxyResult proxyResult = handler.generateToken(context);
log.info("Generate token finished {} with recurrentId {}", proxyResult, recurrentId);
return proxyResult;
} catch (Exception ex) {
String message = "Failed handle generate token with recurrentId " + recurrentId;
logMessage(ex, message);
throw ex;
}
}
@Override
public RecurrentTokenCallbackResult handleRecurrentTokenCallback(ByteBuffer byteBuffer,
RecurrentTokenContext context) throws TException {
String recurrentId = ProxyProviderPackageExtractors.extractRecurrentId(context);
log.info("handleRecurrentTokenCallback: start with recurrentId {}", recurrentId);
RecurrentTokenCallbackResult result = handler.handleRecurrentTokenCallback(byteBuffer, context);
log.info("handleRecurrentTokenCallback end {} with recurrentId {}", result, recurrentId);
return result;
}
@Override
public PaymentProxyResult processPayment(PaymentContext context) throws TException {
String invoiceId = ProxyProviderPackageExtractors.extractInvoiceId(context);
String invoicePaymentStatus = ProxyProviderPackageExtractors.extractTargetInvoicePaymentStatus(context);
String paymentResourceType = PaymentResourceTypeResolver.extractPaymentResourceType(context);
log.info("Process payment handle resource='{}', status='{}' start with invoiceId {}", paymentResourceType,
invoicePaymentStatus, invoiceId);
try {
PaymentProxyResult proxyResult = handler.processPayment(context);
log.info("Process payment handle resource='{}', status='{}' finished with invoiceId {} and proxyResult {}",
paymentResourceType, invoicePaymentStatus, invoiceId, proxyResult);
return proxyResult;
} catch (Exception e) {
String message = String.format(
"Failed handle resource=%s, status=%s process payment for operation with invoiceId %s",
paymentResourceType, invoicePaymentStatus, invoiceId);
logMessage(e, message);
throw e;
}
}
@Override
public PaymentCallbackResult handlePaymentCallback(ByteBuffer byteBuffer,
PaymentContext context) throws TException {
String invoiceId = ProxyProviderPackageExtractors.extractInvoiceId(context);
log.info("handlePaymentCallback start with invoiceId {}", invoiceId);
PaymentCallbackResult result = handler.handlePaymentCallback(byteBuffer, context);
log.info("handlePaymentCallback finish {} with invoiceId {}", result, invoiceId);
return result;
}
private static void logMessage(Exception ex, String message) {
if (isUndefinedResultOrUnavailable(ex)) {
log.warn(message, ex);
} else {
log.error(message, ex);
}
}
}

View File

@ -0,0 +1,58 @@
package dev.vality.adapter.flow.lib.handler;
import dev.vality.adapter.flow.lib.logback.mdc.MdcContext;
import dev.vality.damsel.proxy_provider.*;
import lombok.RequiredArgsConstructor;
import org.apache.thrift.TException;
import org.slf4j.MDC;
import java.nio.ByteBuffer;
@RequiredArgsConstructor
public class ServerHandlerMdcDecorator implements ProviderProxySrv.Iface {
private final ProviderProxySrv.Iface serverHandlerLogDecorator;
public RecurrentTokenProxyResult generateToken(RecurrentTokenContext recurrentTokenContext) throws TException {
MdcContext.mdcPutContext(recurrentTokenContext);
try {
return serverHandlerLogDecorator.generateToken(recurrentTokenContext);
} finally {
MDC.clear();
}
}
@Override
public RecurrentTokenCallbackResult handleRecurrentTokenCallback(ByteBuffer byteBuffer,
RecurrentTokenContext recurrentTokenContext)
throws TException {
MdcContext.mdcPutContext(recurrentTokenContext);
try {
return serverHandlerLogDecorator.handleRecurrentTokenCallback(byteBuffer, recurrentTokenContext);
} finally {
MDC.clear();
}
}
@Override
public PaymentProxyResult processPayment(PaymentContext paymentContext) throws TException {
MdcContext.mdcPutContext(paymentContext);
try {
return serverHandlerLogDecorator.processPayment(paymentContext);
} finally {
MDC.clear();
}
}
@Override
public PaymentCallbackResult handlePaymentCallback(ByteBuffer byteBuffer, PaymentContext paymentContext)
throws TException {
MdcContext.mdcPutContext(paymentContext);
try {
return serverHandlerLogDecorator.handlePaymentCallback(byteBuffer, paymentContext);
} finally {
MDC.clear();
}
}
}

View File

@ -0,0 +1,9 @@
package dev.vality.adapter.flow.lib.handler.callback;
import java.nio.ByteBuffer;
public interface CallbackHandler<T, R> {
T handleCallback(ByteBuffer byteBuffer, R context);
}

View File

@ -0,0 +1,37 @@
package dev.vality.adapter.flow.lib.handler.callback;
import dev.vality.adapter.flow.lib.model.TemporaryContext;
import dev.vality.adapter.flow.lib.serde.Deserializer;
import dev.vality.adapter.flow.lib.serde.StateSerializer;
import dev.vality.adapter.flow.lib.service.TemporaryContextService;
import dev.vality.damsel.proxy_provider.PaymentCallbackProxyResult;
import dev.vality.damsel.proxy_provider.PaymentCallbackResult;
import dev.vality.damsel.proxy_provider.PaymentContext;
import dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
@Slf4j
@RequiredArgsConstructor
public class PaymentCallbackHandler implements CallbackHandler<PaymentCallbackResult, PaymentContext> {
private final Deserializer<TemporaryContext> temporaryContextDeserializer;
private final StateSerializer<TemporaryContext> adapterSerializer;
private final TemporaryContextService temporaryContextService;
public PaymentCallbackResult handleCallback(ByteBuffer callback, PaymentContext context) {
var generalExitStateModel = initTemporaryContext(callback, context);
byte[] callbackResponse = new byte[0];
return ProxyProviderPackageCreators.createCallbackResult(callbackResponse, (new PaymentCallbackProxyResult())
.setIntent(ProxyProviderPackageCreators.createIntentWithSleepIntent(0))
.setNextState(this.adapterSerializer.writeByte(generalExitStateModel)));
}
private TemporaryContext initTemporaryContext(ByteBuffer callback, PaymentContext context) {
var temporaryContext = temporaryContextService.getTemporaryContext(context, this.temporaryContextDeserializer);
return temporaryContextService.appendThreeDsParametersToContext(callback, temporaryContext);
}
}

View File

@ -0,0 +1,43 @@
package dev.vality.adapter.flow.lib.handler.callback;
import dev.vality.adapter.flow.lib.model.TemporaryContext;
import dev.vality.adapter.flow.lib.serde.Deserializer;
import dev.vality.adapter.flow.lib.serde.StateSerializer;
import dev.vality.adapter.flow.lib.service.TemporaryContextService;
import dev.vality.damsel.proxy_provider.RecurrentTokenCallbackResult;
import dev.vality.damsel.proxy_provider.RecurrentTokenContext;
import dev.vality.damsel.proxy_provider.RecurrentTokenIntent;
import dev.vality.damsel.proxy_provider.RecurrentTokenProxyResult;
import dev.vality.java.damsel.utils.creators.BasePackageCreators;
import dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
@Slf4j
@RequiredArgsConstructor
public class RecurrentTokenCallbackHandler
implements CallbackHandler<RecurrentTokenCallbackResult, RecurrentTokenContext> {
private final Deserializer<TemporaryContext> adapterDeserializer;
private final StateSerializer<TemporaryContext> adapterSerializer;
private final TemporaryContextService temporaryContextService;
public RecurrentTokenCallbackResult handleCallback(ByteBuffer callback, RecurrentTokenContext context) {
var generalExitStateModel = initTemporaryContext(callback, context);
byte[] callbackResponse = new byte[0];
return ProxyProviderPackageCreators.createRecurrentTokenCallbackResult(callbackResponse,
(new RecurrentTokenProxyResult()).setIntent(RecurrentTokenIntent
.sleep(ProxyProviderPackageCreators.createSleepIntent(
BasePackageCreators.createTimerTimeout(0))))
.setNextState(this.adapterSerializer.writeByte(generalExitStateModel))
);
}
private TemporaryContext initTemporaryContext(ByteBuffer callback, RecurrentTokenContext context) {
var temporaryContext = temporaryContextService.getTemporaryContext(context, this.adapterDeserializer);
return temporaryContextService.appendThreeDsParametersToContext(callback, temporaryContext);
}
}

View File

@ -0,0 +1,29 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class AuthHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public AuthHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::auth, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.AUTH == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class CancelHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public CancelHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::cancel, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.CANCEL == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class CaptureHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public CaptureHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::capture, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.CAPTURE == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class Check3dsV2Handler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public Check3dsV2Handler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::check3dsV2, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.CHECK_NEED_3DS_V2 == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,30 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandler;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import org.apache.thrift.TException;
public class DoNothingHandler implements CommonHandler<ExitStateModel, EntryStateModel> {
@Override
public ExitStateModel handle(EntryStateModel entryStateModel) throws TException {
var exitStateModel = new ExitStateModel();
exitStateModel.setEntryStateModel(entryStateModel);
exitStateModel.setProviderTrxId(entryStateModel.getBaseRequestModel().getProviderTrxId());
exitStateModel.setNextStep(Step.DO_NOTHING);
exitStateModel.setTrxExtra(entryStateModel.getBaseRequestModel().getSavedData());
if (entryStateModel.getBaseRequestModel().getRecurrentPaymentData().isMakeRecurrent()) {
exitStateModel.setRecToken(entryStateModel.getBaseRequestModel().getRecurrentPaymentData().getRecToken());
}
return exitStateModel;
}
@Override
public boolean isHandle(EntryStateModel model) {
return model.getCurrentStep() == Step.DO_NOTHING;
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class Finish3dsHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public Finish3dsHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::finish3ds, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.FINISH_THREE_DS_V1 == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class Finish3dsV2Handler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public Finish3dsV2Handler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::finish3dsV2, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.FINISH_THREE_DS_V2 == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,29 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class GenerateTokenHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public GenerateTokenHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::generateToken, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.GENERATE_TOKEN == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class PaymentHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public PaymentHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::pay, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.PAY == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class RefundHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public RefundHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::refund, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.REFUND == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.handler.payment;
import dev.vality.adapter.flow.lib.client.RemoteClient;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.handler.CommonHandlerImpl;
import dev.vality.adapter.flow.lib.model.BaseRequestModel;
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.Processor;
import org.springframework.core.convert.converter.Converter;
public class StatusHandler
extends CommonHandlerImpl<BaseRequestModel, BaseResponseModel, EntryStateModel, ExitStateModel> {
public StatusHandler(
RemoteClient client,
Converter<EntryStateModel, BaseRequestModel> converter,
Processor<ExitStateModel, BaseResponseModel, EntryStateModel> responseProcessorChain
) {
super(client::status, converter, responseProcessorChain);
}
@Override
public boolean isHandle(EntryStateModel entryStateModel) {
return Step.CHECK_STATUS == entryStateModel.getCurrentStep();
}
}

View File

@ -0,0 +1,92 @@
package dev.vality.adapter.flow.lib.logback.mask;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
import lombok.RequiredArgsConstructor;
import org.slf4j.Marker;
import java.util.Map;
@RequiredArgsConstructor
public class MaskedEvent implements ILoggingEvent {
private final ILoggingEvent event;
private final String message;
@Override
public String getThreadName() {
return event.getThreadName();
}
@Override
public Level getLevel() {
return event.getLevel();
}
@Override
public String getMessage() {
return event.getMessage();
}
@Override
public Object[] getArgumentArray() {
return new Object[0];
}
@Override
public String getFormattedMessage() {
return message;
}
@Override
public String getLoggerName() {
return event.getLoggerName();
}
@Override
public LoggerContextVO getLoggerContextVO() {
return event.getLoggerContextVO();
}
@Override
public IThrowableProxy getThrowableProxy() {
return event.getThrowableProxy();
}
@Override
public StackTraceElement[] getCallerData() {
return new StackTraceElement[0];
}
@Override
public boolean hasCallerData() {
return true;
}
@Override
public Marker getMarker() {
return event.getMarker();
}
@Override
public Map<String, String> getMDCPropertyMap() {
return event.getMDCPropertyMap();
}
@Override
public Map<String, String> getMdc() {
return event.getMDCPropertyMap();
}
@Override
public long getTimeStamp() {
return event.getTimeStamp();
}
@Override
public void prepareForDeferredProcessing() {
// Do nothing
}
}

View File

@ -0,0 +1,29 @@
package dev.vality.adapter.flow.lib.logback.mask;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MaskingMessageWithPattern {
public static String maskMessage(String message, Pattern multilinePattern) {
if (multilinePattern == null) {
return message;
}
StringBuilder sb = new StringBuilder(message);
Matcher matcher = multilinePattern.matcher(sb);
while (matcher.find()) {
IntStream.rangeClosed(1, matcher.groupCount())
.filter(group -> matcher.group(group) != null)
.forEach(
group -> IntStream.range(matcher.start(group), matcher.end(group))
.forEach(i -> sb.setCharAt(i, '*'))
);
}
return sb.toString();
}
}

View File

@ -0,0 +1,26 @@
package dev.vality.adapter.flow.lib.logback.mask;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class PatternMaskingLayout extends PatternLayout {
private Pattern multilinePattern;
private List<String> maskPatterns = new ArrayList<>();
private static final String DELIMITER = "|";
public void addMaskPattern(String maskPattern) {
maskPatterns.add(maskPattern);
multilinePattern = Pattern.compile(String.join(DELIMITER, maskPatterns), Pattern.MULTILINE);
}
@Override
public String doLayout(ILoggingEvent event) {
return super.doLayout(new MaskedEvent(event,
MaskingMessageWithPattern.maskMessage(event.getFormattedMessage(), multilinePattern)));
}
}

View File

@ -0,0 +1,42 @@
package dev.vality.adapter.flow.lib.logback.mask;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;
import net.logstash.logback.composite.AbstractFieldJsonProvider;
import net.logstash.logback.composite.FieldNamesAware;
import net.logstash.logback.composite.JsonWritingUtils;
import net.logstash.logback.fieldnames.LogstashFieldNames;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class PatternMaskingMessageJsonProvider extends AbstractFieldJsonProvider<ILoggingEvent>
implements FieldNamesAware<LogstashFieldNames> {
private Pattern multilinePattern;
private List<String> maskPatterns = new ArrayList<>();
private static final String FIELD_MESSAGE = "message";
private static final String DELIMITER = "|";
public void addMaskPattern(String maskPattern) {
maskPatterns.add(maskPattern);
multilinePattern = Pattern.compile(String.join(DELIMITER, maskPatterns), Pattern.MULTILINE);
}
public PatternMaskingMessageJsonProvider() {
setFieldName(FIELD_MESSAGE);
}
@Override
public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
JsonWritingUtils.writeStringField(generator, getFieldName(),
MaskingMessageWithPattern.maskMessage(event.getFormattedMessage(), multilinePattern)
);
}
@Override
public void setFieldNames(LogstashFieldNames fieldNames) {
setFieldName(fieldNames.getMessage());
}
}

View File

@ -0,0 +1,63 @@
package dev.vality.adapter.flow.lib.logback.mdc;
import dev.vality.damsel.domain.TransactionInfo;
import dev.vality.damsel.proxy_provider.PaymentContext;
import dev.vality.damsel.proxy_provider.RecurrentTokenContext;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.slf4j.MDC;
import java.util.Map;
import java.util.regex.Pattern;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MdcContext {
public static void mdcPutContext(RecurrentTokenContext context, String[] fieldsToPutInMdc) {
TransactionInfo transactionInfo = context.getTokenInfo().getTrx();
mdcPutContextTransactionInfo(transactionInfo, fieldsToPutInMdc);
}
public static void mdcPutContext(PaymentContext context, String[] fieldsToPutInMdc) {
TransactionInfo transactionInfo = context.getPaymentInfo().getPayment().getTrx();
mdcPutContextTransactionInfo(transactionInfo, fieldsToPutInMdc);
}
public static void mdcPutContext(PaymentContext context) {
TransactionInfo transactionInfo = context.getPaymentInfo().getPayment().getTrx();
mdcPutContextTransactionInfo(transactionInfo);
}
public static void mdcPutContext(RecurrentTokenContext context) {
TransactionInfo transactionInfo = context.getTokenInfo().getTrx();
mdcPutContextTransactionInfo(transactionInfo);
}
public static void mdcPutContextTransactionInfo(TransactionInfo transactionInfo, String[] fieldsToPutInMdc) {
if (transactionInfo != null) {
Map<String, String> trxextra = transactionInfo.getExtra();
for (String field : fieldsToPutInMdc) {
MDC.put(field, trxextra.get(field));
}
}
}
public static void mdcPutContextTransactionInfo(TransactionInfo transactionInfo) {
if (transactionInfo != null) {
Map<String, String> trxextra = transactionInfo.getExtra();
String maskPattern = "\\b\\d{6}([\\d\\s]{2,9})\\d{4}\\b";
Pattern pattern = Pattern.compile(maskPattern);
for (Map.Entry<String, String> extra : trxextra.entrySet()) {
if (pattern.matcher(extra.getValue()).find()) {
MDC.put(extra.getKey(), extra.getValue());
}
}
}
}
public static void mdcRemoveContext(String[] fieldsToPutInMdc) {
for (String field : fieldsToPutInMdc) {
MDC.remove(field);
}
}
}

View File

@ -0,0 +1,89 @@
package dev.vality.adapter.flow.lib.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
/**
* Base class with all needed fields for pay and other operations
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class BaseRequestModel {
/**
* Uniq Long identifier for payment.
*/
private Long paymentId;
/**
* Uniq identifier from provider (if exists), not come in first methods.
*/
private String providerTrxId;
/**
* Amount in Long format and in minimum payment units (example: cents).
*/
private Long amount;
/**
* Currency in symbolic formats (example: "USD").
*/
private String currency;
/**
* Timestamp RFC 3339.
*
* The string must contain the date and time in UTC in the following format:
* `2016-03-22T06:12:27Z`
*/
private String createdAt;
/**
* Description payment from merchant.
*/
private String details;
/**
* Card data, can be empty on some steps and recurrent or mobile operations.
*/
private CardData cardData;
/**
* Info about payer, send from merchant and can be empty.
*/
private PayerInfo payerInfo;
/**
* Data for mobile pay methods.
*/
private MobilePaymentData mobilePaymentData;
/**
* Data for recurrent payments.
*/
private RecurrentPaymentData recurrentPaymentData;
/**
* Full needed for refund data.
*/
private RefundData refundData;
/**
* The URL to which to redirect if the 3ds stream completed successfully.
*/
private String successRedirectUrl;
/**
* The URL to which to redirect if the 3ds stream completed fails.
*/
private String failedRedirectUrl;
/**
* All static parameters from configuration by support team.
* (options - in old naming)
*/
private Map<String, String> adapterConfigurations;
/**
* Data that you send to save on previous steps.
*/
private Map<String, String> savedData;
/**
* Data that comes in a callback from mpi after the 3ds step.
* Parameter names can be unique to your implementation, only you can know.
*/
private Map<String, String> threeDsDataFromMpiCallback;
}

View File

@ -0,0 +1,42 @@
package dev.vality.adapter.flow.lib.model;
import dev.vality.adapter.flow.lib.constant.Status;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Data
@SuperBuilder
public class BaseResponseModel {
/**
* Result status operation, by this status flow choose stop, retry or redirect branch.
*/
private Status status;
/**
* Error code from external system or your implementation
*/
private String errorCode;
/**
* Error description
*/
private String errorMessage;
/**
* Uniq identifier from provider (if exists).
*/
private String providerTrxId;
/**
* Token for pay in recurrent operations.
*/
private String recurrentToken;
/**
* Data that you want to use in next steps.
*/
private Map<String, String> saveData;
/**
* Data for choose 3ds flow and parameters for redirects.
*/
private ThreeDsData threeDsData;
}

View File

@ -0,0 +1,26 @@
package dev.vality.adapter.flow.lib.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class CardData {
@ToString.Exclude
private String pan;
@ToString.Exclude
private Byte expMonth;
@ToString.Exclude
private Short expYear;
@ToString.Exclude
private String cvv2;
@ToString.Exclude
private String cardHolder;
}

View File

@ -0,0 +1,22 @@
package dev.vality.adapter.flow.lib.model;
import dev.vality.adapter.flow.lib.constant.Step;
import dev.vality.adapter.flow.lib.constant.TargetStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class EntryStateModel {
private BaseRequestModel baseRequestModel;
private Step currentStep;
private PollingInfo startedPollingInfo;
private TargetStatus targetStatus;
}

View File

@ -0,0 +1,33 @@
package dev.vality.adapter.flow.lib.model;
import dev.vality.adapter.flow.lib.constant.Status;
import dev.vality.adapter.flow.lib.constant.Step;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ExitStateModel {
private Status lastOperationStatus;
private String errorCode;
private String errorMessage;
private Step nextStep;
private EntryStateModel entryStateModel;
private String providerTrxId;
private Map<String, String> trxExtra;
private PollingInfo pollingInfo;
private ThreeDsData threeDsData;
private String recToken;
}

View File

@ -0,0 +1,18 @@
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 MobilePaymentData {
private String tokenProvider;
private String cryptogram;
private String eci;
}

View File

@ -0,0 +1,18 @@
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 PayerInfo {
private String ip;
private String email;
private String phone;
}

View File

@ -0,0 +1,21 @@
package dev.vality.adapter.flow.lib.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.time.Instant;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PollingInfo {
@JsonProperty(value = "start_date_time_polling")
private Instant startDateTimePolling;
@JsonProperty(value = "max_date_time_polling")
private Instant maxDateTimePolling;
}

View File

@ -0,0 +1,17 @@
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 RecurrentPaymentData {
private String recToken;
private boolean makeRecurrent;
}

View File

@ -0,0 +1,17 @@
package dev.vality.adapter.flow.lib.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RefundData {
private Long amount;
private String id;
}

View File

@ -0,0 +1,22 @@
package dev.vality.adapter.flow.lib.model;
import dev.vality.adapter.flow.lib.constant.Step;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class TemporaryContext {
private Step nextStep;
private String providerTrxId;
private PollingInfo pollingInfo;
private Map<String, String> threeDsData;
}

View File

@ -0,0 +1,31 @@
package dev.vality.adapter.flow.lib.model;
import dev.vality.adapter.flow.lib.constant.ThreeDsType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ThreeDsData {
/**
* Use type to select desired 3ds flow.
*/
private ThreeDsType threeDsType;
/**
* Url for redirect user.
*/
private String acsUrl;
/**
* Parameters that would send in redirect request.
* Name and value of parameters don't change and forward send in your format.
*/
private Map<String, String> parameters;
}

View File

@ -0,0 +1,34 @@
package dev.vality.adapter.flow.lib.processor;
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 ErrorProcessor implements Processor<ExitStateModel, BaseResponseModel, EntryStateModel> {
@Override
public ExitStateModel process(BaseResponseModel response, EntryStateModel entryStateModel) {
log.debug("Start error process response: {} entryStateModel: {}", response, entryStateModel);
if (response != null && ErrorUtils.isError(response)) {
ExitStateModel exitStateModel = new ExitStateModel();
exitStateModel.setErrorCode(String.valueOf(response.getErrorCode()));
exitStateModel.setErrorMessage(response.getErrorMessage());
log.debug("Finish error process response: {} entryStateModel: {}", response, entryStateModel);
return exitStateModel;
}
ExitStateModel exitStateModel = new ExitStateModel();
exitStateModel.setErrorCode("unknown_error");
exitStateModel.setErrorMessage("Unknown error reason!");
exitStateModel.setEntryStateModel(entryStateModel);
log.debug("Finish error process response: {} entryStateModel: {}", response, entryStateModel);
return exitStateModel;
}
}

View File

@ -0,0 +1,7 @@
package dev.vality.adapter.flow.lib.processor;
public interface Processor<R, T, E> {
R process(T response, E context);
}

View File

@ -0,0 +1,38 @@
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 RedirectProcessor implements Processor<ExitStateModel, BaseResponseModel, EntryStateModel> {
private final Processor<ExitStateModel, BaseResponseModel, EntryStateModel> nextProcessor;
@Override
public ExitStateModel process(BaseResponseModel response, EntryStateModel entryStateModel) {
if (response.getStatus() == Status.NEED_REDIRECT
&& !ErrorUtils.isError(response)
&& response.getThreeDsData() != null
&& response.getThreeDsData().getThreeDsType() != null) {
log.debug("Start redirect process response: {} entryStateModel: {}", response, entryStateModel);
ExitStateModel exitStateModel = new ExitStateModel();
exitStateModel.setThreeDsData(response.getThreeDsData());
exitStateModel.setLastOperationStatus(response.getStatus());
exitStateModel.setProviderTrxId(response.getProviderTrxId());
log.debug("Finish redirect 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);
}
}

View File

@ -0,0 +1,34 @@
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 RetryProcessor implements Processor<ExitStateModel, BaseResponseModel, EntryStateModel> {
private final Processor<ExitStateModel, BaseResponseModel, EntryStateModel> nextProcessor;
@Override
public ExitStateModel process(BaseResponseModel response, EntryStateModel entryStateModel) {
if (response.getStatus() == Status.NEED_RETRY
&& !ErrorUtils.isError(response)) {
log.debug("Start redirect process response: {} entryStateModel: {}", response, entryStateModel);
ExitStateModel exitStateModel = new ExitStateModel();
exitStateModel.setLastOperationStatus(response.getStatus());
log.debug("Finish redirect 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);
}
}

View File

@ -0,0 +1,63 @@
package dev.vality.adapter.flow.lib.processor;
import dev.vality.adapter.flow.lib.constant.MetaData;
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;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
public class SuccessFinishProcessor
implements Processor<ExitStateModel, BaseResponseModel, EntryStateModel> {
private final Processor<ExitStateModel, BaseResponseModel, EntryStateModel> nextProcessor;
@Override
public ExitStateModel process(BaseResponseModel response, EntryStateModel entryStateModel) {
if (response.getStatus() == Status.SUCCESS
&& !ErrorUtils.isError(response)) {
log.debug("Start success process response: {} entryStateModel: {}", response, entryStateModel);
ExitStateModel exitStateModel = new ExitStateModel();
exitStateModel.setProviderTrxId(response.getProviderTrxId());
exitStateModel.setLastOperationStatus(response.getStatus());
Map<String, String> saveData = response.getSaveData();
if (entryStateModel.getBaseRequestModel().getRecurrentPaymentData() != null
&& entryStateModel.getBaseRequestModel().getRecurrentPaymentData().isMakeRecurrent()) {
if (saveData == null) {
saveData = new HashMap<>();
}
String recToken = initRecurrentToken(response, entryStateModel);
saveData.put(MetaData.META_REC_TOKEN, recToken);
exitStateModel.setRecToken(recToken);
}
exitStateModel.setTrxExtra(saveData);
log.debug("Finish success 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);
}
private String initRecurrentToken(BaseResponseModel response, EntryStateModel entryStateModel) {
if (StringUtils.hasText(response.getRecurrentToken())) {
return response.getRecurrentToken();
} else if (entryStateModel.getBaseRequestModel().getRecurrentPaymentData() != null
&& StringUtils.hasText(entryStateModel.getBaseRequestModel().getRecurrentPaymentData()
.getRecToken())) {
return entryStateModel.getBaseRequestModel().getRecurrentPaymentData().getRecToken();
}
return null;
}
}

View File

@ -0,0 +1,9 @@
package dev.vality.adapter.flow.lib.serde;
public interface Deserializer<T> {
T read(byte[] data);
T read(String data);
}

View File

@ -0,0 +1,45 @@
package dev.vality.adapter.flow.lib.serde;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.vality.adapter.flow.lib.exception.DeserializationException;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class ParametersDeserializer implements Deserializer<Map<String, String>> {
private final ObjectMapper mapper;
public Map<String, String> read(byte[] data) {
if (data == null) {
return new HashMap<>();
} else {
try {
return mapper.readValue(data, new TypeReference<HashMap<String, String>>() {
});
} catch (IOException var3) {
throw new IllegalArgumentException(var3);
}
}
}
public Map<String, String> read(String data) {
throw new DeserializationException("Deserialization not supported");
}
public Map<String, String> read(HttpServletRequest request) {
return Optional.ofNullable(request.getParameterMap())
.orElseGet(HashMap::new)
.entrySet().stream()
.collect(Collectors.toMap(k -> k.getKey().trim(),
v -> v.getValue()[0]));
}
}

View File

@ -0,0 +1,11 @@
package dev.vality.adapter.flow.lib.serde;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class ParametersSerializer extends StateSerializer<Map<String, String>> {
public ParametersSerializer(ObjectMapper mapper) {
super(mapper);
}
}

View File

@ -0,0 +1,9 @@
package dev.vality.adapter.flow.lib.serde;
public interface Serializer<T> {
byte[] writeByte(T obj);
String writeString(T obj);
}

View File

@ -0,0 +1,37 @@
package dev.vality.adapter.flow.lib.serde;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.SerializationException;
import java.io.IOException;
import java.util.Base64;
@Getter
@Setter
@AllArgsConstructor
public abstract class StateSerializer<T> implements Serializer<T> {
protected final ObjectMapper mapper;
@Override
public byte[] writeByte(Object obj) {
try {
return mapper.writeValueAsBytes(obj);
} catch (IOException e) {
throw new SerializationException(e);
}
}
@Override
public String writeString(Object obj) {
try {
return Base64.getEncoder().encodeToString(getMapper().writeValueAsBytes(obj));
} catch (IOException e) {
throw new SerializationException(e);
}
}
}

View File

@ -0,0 +1,37 @@
package dev.vality.adapter.flow.lib.serde;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.vality.adapter.flow.lib.exception.DeserializationException;
import dev.vality.adapter.flow.lib.model.TemporaryContext;
import java.io.IOException;
public class TemporaryContextDeserializer implements Deserializer<TemporaryContext> {
private final ObjectMapper mapper;
public TemporaryContext read(byte[] data) {
if (data == null) {
return new TemporaryContext();
} else {
try {
return this.getMapper().readValue(data, TemporaryContext.class);
} catch (IOException var3) {
throw new IllegalArgumentException(var3);
}
}
}
public TemporaryContext read(String data) {
throw new DeserializationException("Deserialization not supported");
}
public ObjectMapper getMapper() {
return this.mapper;
}
public TemporaryContextDeserializer(ObjectMapper mapper) {
this.mapper = mapper;
}
}

View File

@ -0,0 +1,10 @@
package dev.vality.adapter.flow.lib.serde;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.vality.adapter.flow.lib.model.TemporaryContext;
public class TemporaryContextSerializer extends StateSerializer<TemporaryContext> {
public TemporaryContextSerializer(ObjectMapper mapper) {
super(mapper);
}
}

View File

@ -0,0 +1,46 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.utils.backoff.BackOffExecution;
import dev.vality.adapter.flow.lib.utils.backoff.ExponentialBackOff;
import dev.vality.adapter.flow.lib.utils.backoff.TimeOptionsExtractors;
import java.time.Instant;
import java.util.Map;
import static dev.vality.adapter.flow.lib.utils.backoff.ExponentialBackOff.*;
public class ExponentialBackOffPollingService {
public BackOffExecution prepareBackOffExecution(PollingInfo pollingInfo, Map<String, String> options) {
return exponentialBackOff(pollingInfo, options)
.start();
}
public int prepareNextPollingInterval(PollingInfo pollingInfo, Map<String, String> options) {
return exponentialBackOff(pollingInfo, options)
.start()
.nextBackOff()
.intValue();
}
private ExponentialBackOff exponentialBackOff(PollingInfo pollingInfo, Map<String, String> options) {
final Long currentLocalTime = Instant.now().toEpochMilli();
Long startTime = pollingInfo.getStartDateTimePolling() != null
? pollingInfo.getStartDateTimePolling().toEpochMilli()
: currentLocalTime;
Integer exponential = TimeOptionsExtractors.extractExponent(options, DEFAULT_MUTIPLIER);
Integer defaultInitialExponential =
TimeOptionsExtractors.extractDefaultInitialExponential(options, DEFAULT_INITIAL_INTERVAL_SEC);
Integer maxTimeBackOff = TimeOptionsExtractors.extractMaxTimeBackOff(options, DEFAULT_MAX_INTERVAL_SEC);
return new ExponentialBackOff(
startTime,
currentLocalTime,
exponential,
defaultInitialExponential,
maxTimeBackOff);
}
}

View File

@ -0,0 +1,28 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.bender.BenderSrv;
import dev.vality.bender.GenerationResult;
import dev.vality.bender.GenerationSchema;
import dev.vality.bender.SequenceSchema;
import dev.vality.msgpack.Nil;
import dev.vality.msgpack.Value;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@RequiredArgsConstructor
public class IdGenerator {
private static final String SEQ_ID = "orderId";
private final BenderSrv.Iface benderClient;
@org.springframework.beans.factory.annotation.Value("adapter.prefix")
private String adapterPrefix;
@SneakyThrows
public Long get(String invoiceId) {
GenerationSchema schema = GenerationSchema.sequence(new SequenceSchema().setSequenceId(SEQ_ID));
GenerationResult result = benderClient.generateID(adapterPrefix + invoiceId, schema, Value.nl(new Nil()));
return Long.parseLong(result.getInternalId());
}
}

View File

@ -0,0 +1,123 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.adapter.flow.lib.constant.RedirectFields;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.model.ThreeDsData;
import dev.vality.adapter.flow.lib.serde.ParametersSerializer;
import dev.vality.adapter.flow.lib.utils.CallbackUrlExtractor;
import dev.vality.adapter.flow.lib.utils.ThreeDsDataInitializer;
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.error.mapping.ErrorMapping;
import lombok.RequiredArgsConstructor;
import java.nio.ByteBuffer;
import java.util.Map;
import static dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators.createFinishIntentSuccessWithToken;
import static dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators.createPostUserInteraction;
import static dev.vality.java.damsel.utils.extractors.OptionsExtractors.extractRedirectTimeout;
@RequiredArgsConstructor
public class IntentResultFactory {
private final TimerProperties timerProperties;
private final CallbackUrlExtractor callbackUrlExtractor;
private final TagManagementService tagManagementService;
private final ParametersSerializer parametersSerializer;
private final PollingInfoService pollingInfoService;
private final ErrorMapping errorMapping;
private final ExponentialBackOffPollingService exponentialBackOffPollingService;
public Intent createFinishIntentSuccessWithCheckToken(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
if (entryStateModel.getBaseRequestModel().getRecurrentPaymentData() != null
&& entryStateModel.getBaseRequestModel().getRecurrentPaymentData().isMakeRecurrent()) {
return createFinishIntentSuccessWithToken(exitStateModel.getRecToken());
}
return createFinishIntentSuccess();
}
public Intent createSuspendIntentWithFailedAfterTimeout(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
ThreeDsData threeDsData = exitStateModel.getThreeDsData();
Map<String, String> params = ThreeDsDataInitializer.initThreeDsParameters(exitStateModel);
String redirectUrl = entryStateModel.getBaseRequestModel().getSuccessRedirectUrl();
params.put(RedirectFields.TERM_URL.getValue(), callbackUrlExtractor.extractCallbackUrl(redirectUrl));
int timerRedirectTimeout = extractRedirectTimeout(
entryStateModel.getBaseRequestModel().getAdapterConfigurations(),
timerProperties.getRedirectTimeoutMin());
return Intent.suspend(
new SuspendIntent(
tagManagementService.findTag(params),
Timer.timeout(timerRedirectTimeout)
).setUserInteraction(createPostUserInteraction(threeDsData.getAcsUrl(), params))
);
}
public Intent createSuspendIntentWithCallbackAfterTimeout(ExitStateModel exitStateModel) {
Map<String, String> params = ThreeDsDataInitializer.initThreeDsParameters(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);
String redirectUrl = entryStateModel.getBaseRequestModel().getSuccessRedirectUrl();
params.put(RedirectFields.TERM_URL.getValue(), callbackUrlExtractor.extractCallbackUrl(redirectUrl));
ThreeDsData threeDsData = exitStateModel.getThreeDsData();
int timerRedirectTimeout = extractRedirectTimeout(
entryStateModel.getBaseRequestModel().getAdapterConfigurations(),
timerProperties.getRedirectTimeoutMin());
return Intent.suspend(
new SuspendIntent(
tagManagementService.findTag(params),
Timer.timeout(timerRedirectTimeout))
.setTimeoutBehaviour(TimeoutBehaviour.callback(
ByteBuffer.wrap(parametersSerializer.writeByte(params)))
).setUserInteraction(createPostUserInteraction(threeDsData.getAcsUrl(), params))
);
}
public Intent createFinishIntentSuccess() {
return Intent.finish(new FinishIntent(FinishStatus.success(new Success())));
}
public Intent createSleepIntentForReinvocation() {
return Intent.sleep(new SleepIntent(Timer.timeout(0)));
}
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<String, String> adapterConfigurations = entryStateModel.getBaseRequestModel().getAdapterConfigurations();
int nextTimeout =
exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, adapterConfigurations);
return Intent.sleep(
new SleepIntent(Timer.timeout(nextTimeout))
);
}
public Intent createFinishIntentFailed(ExitStateModel exitStateModel) {
return Intent.finish(new FinishIntent(FinishStatus.failure(
errorMapping.mapFailure(exitStateModel.getErrorCode(),
exitStateModel.getErrorMessage()))));
}
public Intent createFinishIntentFailed(String errorCode, String errorMessage) {
return Intent.finish(new FinishIntent(FinishStatus.failure(
errorMapping.mapFailure(errorCode, errorMessage))));
}
}

View File

@ -0,0 +1,47 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.utils.TimerProperties;
import dev.vality.java.damsel.utils.extractors.OptionsExtractors;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@RequiredArgsConstructor
public class PollingInfoService {
private final TimerProperties timerProperties;
public PollingInfo initPollingInfo(EntryStateModel entryStateModel) {
PollingInfo pollingInfo = entryStateModel.getStartedPollingInfo();
if (pollingInfo == null) {
pollingInfo = new PollingInfo();
}
if (pollingInfo.getStartDateTimePolling() == null) {
pollingInfo.setStartDateTimePolling(Instant.now());
}
Instant maxDateTimePolling = calcDeadline(entryStateModel, pollingInfo);
pollingInfo.setMaxDateTimePolling(maxDateTimePolling);
return pollingInfo;
}
public boolean isDeadline(PollingInfo pollingInfo) {
Instant now = Instant.now();
return now.isAfter(pollingInfo.getMaxDateTimePolling());
}
private Instant calcDeadline(EntryStateModel entryStateModel, @NonNull PollingInfo pollingInfo) {
if (pollingInfo.getMaxDateTimePolling() == null) {
Integer maxTimePolling = OptionsExtractors.extractMaxTimePolling(
entryStateModel.getBaseRequestModel().getAdapterConfigurations(),
timerProperties.getMaxTimePollingMin());
return pollingInfo.getStartDateTimePolling().plus(maxTimePolling, ChronoUnit.MINUTES);
}
return pollingInfo.getMaxDateTimePolling();
}
}

View File

@ -0,0 +1,80 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.adapter.flow.lib.constant.RedirectFields;
import dev.vality.adapter.flow.lib.model.EntryStateModel;
import dev.vality.adapter.flow.lib.model.ExitStateModel;
import dev.vality.adapter.flow.lib.model.PollingInfo;
import dev.vality.adapter.flow.lib.model.ThreeDsData;
import dev.vality.adapter.flow.lib.utils.CallbackUrlExtractor;
import dev.vality.adapter.flow.lib.utils.ThreeDsDataInitializer;
import dev.vality.adapter.flow.lib.utils.TimerProperties;
import dev.vality.damsel.base.Timer;
import dev.vality.damsel.proxy_provider.*;
import dev.vality.error.mapping.ErrorMapping;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import static dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators.createPostUserInteraction;
import static dev.vality.java.damsel.utils.creators.ProxyProviderPackageCreators.createRecurrentTokenStatusSuccess;
import static dev.vality.java.damsel.utils.extractors.OptionsExtractors.extractRedirectTimeout;
@RequiredArgsConstructor
public class RecurrentIntentResultFactory {
private final TimerProperties timerProperties;
private final CallbackUrlExtractor callbackUrlExtractor;
private final TagManagementService tagManagementService;
private final PollingInfoService pollingInfoService;
private final ErrorMapping errorMapping;
private final ExponentialBackOffPollingService exponentialBackOffPollingService;
public RecurrentTokenIntent createIntentWithSuspension(ExitStateModel exitStateModel) {
EntryStateModel entryStateModel = exitStateModel.getEntryStateModel();
ThreeDsData threeDsData = exitStateModel.getThreeDsData();
Map<String, String> params = ThreeDsDataInitializer.initThreeDsParameters(exitStateModel);
String redirectUrl = entryStateModel.getBaseRequestModel().getSuccessRedirectUrl();
params.put(RedirectFields.TERM_URL.getValue(), callbackUrlExtractor.extractCallbackUrl(redirectUrl));
int timerRedirectTimeout = extractRedirectTimeout(
entryStateModel.getBaseRequestModel().getAdapterConfigurations(),
timerProperties.getRedirectTimeoutMin());
return RecurrentTokenIntent.suspend(
new SuspendIntent(
tagManagementService.findTag(params),
Timer.timeout(timerRedirectTimeout)
).setUserInteraction(createPostUserInteraction(threeDsData.getAcsUrl(), params))
);
}
public RecurrentTokenIntent createSleepIntentForReinvocation() {
return RecurrentTokenIntent.sleep(new SleepIntent(Timer.timeout(0)));
}
public RecurrentTokenIntent 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<String, String> adapterConfigurations = entryStateModel.getBaseRequestModel().getAdapterConfigurations();
int nextTimeout =
exponentialBackOffPollingService.prepareNextPollingInterval(pollingInfo, adapterConfigurations);
return RecurrentTokenIntent.sleep(
new SleepIntent(Timer.timeout(nextTimeout))
);
}
public RecurrentTokenIntent createFinishIntent(String recToken) {
return RecurrentTokenIntent.finish(
createRecurrentTokenStatusSuccess(recToken)
);
}
public RecurrentTokenIntent createFinishIntentFailed(String errorCode, String errorMessage) {
return RecurrentTokenIntent.finish(new RecurrentTokenFinishIntent(RecurrentTokenFinishStatus.failure(
errorMapping.mapFailure(errorCode, errorMessage)))
);
}
}

View File

@ -0,0 +1,33 @@
package dev.vality.adapter.flow.lib.service;
import dev.vality.adapter.flow.lib.utils.AdapterProperties;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.Optional;
@RequiredArgsConstructor
public class TagManagementService {
private final AdapterProperties adapterProperties;
@SneakyThrows
public String findTag(Map<String, String> parameters) {
Optional<String> first = adapterProperties.getTagGeneratorFieldNames().stream()
.filter(s -> StringUtils.hasText(parameters.get(s)))
.findFirst();
return first.get();
}
@SneakyThrows
public String get(Map<String, String> parameters) {
Optional<String> first = adapterProperties.getTagGeneratorFieldNames().stream()
.filter(s -> StringUtils.hasText(parameters.get(s)))
.findFirst();
return first.get();
}
}

Some files were not shown because too many files have changed in this diff Show More