Merge pull request #1 from rbkmoney/ft/PROX-364/sketch

PROX-364: add libs
This commit is contained in:
Anatoly Cherkasov 2019-11-07 12:11:24 +03:00 committed by GitHub
commit 508fb48ca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1528 additions and 1 deletions

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

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "build_utils"]
path = build_utils
url = git@github.com:rbkmoney/build_utils.git
branch = master

15
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,15 @@
#!groovy
build('adapter-cashreg-spring-boot-starter', 'docker-host') {
checkoutRepo()
loadBuildUtils()
def javaLibPipeline
runStage('load JavaLib pipeline') {
javaLibPipeline = load("build_utils/jenkins_lib/pipeJavaLib.groovy")
}
def buildImageTag = "fcf116dd775cc2e91bffb6a36835754e3f2d5321"
javaLibPipeline(buildImageTag)
}

View File

@ -1 +1,75 @@
# adapter-cashreg-spring-boot-starter
Вспомогательная библиотека для адаптеров взаимодействующих с провайдерами электронных касс по отправке чеков онлайн
### Разработчики
- [Anatoly Cherkasov](https://github.com/avcherkasov)
### Настройки
Добавить в `pom.xml` в зависимости
```
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>adapter-cashreg-spring-boot-starter</artifactId>
<version>${adapter-cashreg-spring-boot-starter.version}</version>
</dependency>
```
В зависимостях также должны быть указаны
```
<dependency>
<groupId>com.rbkmoney.woody</groupId>
<artifactId>woody-thrift</artifactId>
<version>${woody-thrift.version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>damsel</artifactId>
<version>${damsel.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>${ipaddress.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>error-mapping-java</artifactId>
<version>${error-mapping.version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>damsel</artifactId>
<version>${damsel.version}</version>
</dependency>
```

1
build_utils Submodule

@ -0,0 +1 @@
Subproject commit 4a09386542ab4b98317a787fd6e06e0d3a1e38d6

180
pom.xml Normal file
View File

@ -0,0 +1,180 @@
<?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>
<groupId>com.rbkmoney</groupId>
<artifactId>adapter-cashreg-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>8</java.version>
<spring-boot.version>2.1.1.RELEASE</spring-boot.version>
<damsel-utils.version>2.1.9</damsel-utils.version>
<damsel.version>1.303-d99319c</damsel.version>
<serializer.version>0.6.7</serializer.version>
<adapter-common-lib.version>0.0.8</adapter-common-lib.version>
<error-mapping.version>1.0.4</error-mapping.version>
<cashreg-proto.version>1.9-9c79023</cashreg-proto.version>
</properties>
<dependencies>
<!--third party-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.rbkmoney.geck</groupId>
<artifactId>common</artifactId>
<version>0.6.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>4.2.0</version>
<scope>provided</scope>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<!--rbk-->
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>cashreg-proto</artifactId>
<version>${cashreg-proto.version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>adapter-common-lib</artifactId>
<version>${adapter-common-lib.version}</version>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>error-mapping-java</artifactId>
<version>${error-mapping.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.rbkmoney</groupId>
<artifactId>damsel</artifactId>
<version>${damsel.version}</version>
<scope>provided</scope>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<distributionManagement>
<downloadUrl>http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/groups/public</downloadUrl>
<repository>
<uniqueVersion>false</uniqueVersion>
<id>releases</id>
<name>RBKmoney releases repository</name>
<url>http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/releases</url>
<layout>default</layout>
</repository>
<snapshotRepository>
<uniqueVersion>true</uniqueVersion>
<id>snapshots</id>
<name>RBKmoney snapshots repository</name>
<url>http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/snapshots</url>
<layout>default</layout>
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<configuration>
<destFile>${sonar.jacoco.reportPath}</destFile>
<append>true</append>
</configuration>
<executions>
<execution>
<id>agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -0,0 +1,29 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.config;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.converter.ip.ConverterIp;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Usage example:
* <p>
* application.yml
* <pre>
* {@code
* converterIp:
* nat64prefix: "2a04:4a00:5:10ff:4:1:"
* }
* </pre>
*
* @see ConverterIp
*/
@Configuration
public class ConverterIpConfiguration {
@Bean
ConverterIp converterIp(@Value("${converterIp.nat64prefix:" + ConverterIp.NAT_64_PREFIX + "}") String nat64prefix) {
return new ConverterIp(nat64prefix);
}
}

View File

@ -0,0 +1,54 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.config;
import com.rbkmoney.adapter.common.mapper.SimpleErrorMapping;
import com.rbkmoney.error.mapping.ErrorMapping;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.io.IOException;
/**
* Usage example:
* <p>
* application.yml
* <pre>
* {@code
* error-mapping:
* file: classpath:fixture/errors.json
* patternReason: "'%s' - '%s'" # 'code' - 'description'
* }
* </pre>
* <p>
* Depends
* <pre>
* {@code
* @Autowired
* private final ErrorMapping errorMapping;
* }
* </pre>
* <p>
* Code:
* <pre>
* {@code
* Failure failure = errorMapping.mapFailure(code, reason);
* }
* </pre>
*
* @see ErrorMappingConfiguration
*/
@Configuration
public class ErrorMappingConfiguration {
@Value("${error-mapping.file}")
private Resource errorMappingFilePath;
@Value("${error-mapping.patternReason:\"'%s' - '%s'\"}")
private String errorMappingPattern;
@Bean
public ErrorMapping errorMapping() throws IOException {
return new SimpleErrorMapping(errorMappingFilePath, errorMappingPattern).getErrorMapping();
}
}

View File

@ -0,0 +1,39 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
/**
* application.yml
* <pre>
* {@code
* adapter-cashreg:
* url: http://localhost.ru/path/v1/call
* }
* </pre>
* <p>
* Usage example:
* <pre>
* {@code
* @Autowired
* private final AdapterCashRegProperties adapterCashRegProperties;
* }
* </pre>
*/
@Getter
@Setter
@Validated
@Configuration
@ConfigurationProperties("adapter-cashreg")
public class AdapterCashRegProperties {
@NotEmpty
private String url;
}

View File

@ -0,0 +1,44 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
/**
* application.yml
* <pre>
* {@code
* time:
* config:
* maxTimePolling: 600
* pollingDelay: 600
* }
* </pre>
* <p>
* Usage example:
* <pre>
* {@code
* @Autowired
* private final TimerProperties timeProperties;
* }
* </pre>
*/
@Getter
@Setter
@Validated
@Configuration
@ConfigurationProperties("time.config")
public class TimerProperties {
@NotNull
private int maxTimePolling;
@NotNull
private int pollingDelay;
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.constant;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Error {
DEFAULT_ERROR_CODE("error", "error"),
UNKNOWN("unknown_code", "Unknown error!"),
EMPTY_BODY("Empty body", "Empty body"),
SLEEP_TIMEOUT("Sleep timeout", "Max time pool limit reached");
private final String code;
private final String message;
}

View File

@ -0,0 +1,38 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.constant;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum OptionalField {
URL("url"),
GROUP("group"),
LOGIN("login"),
PASS("pass"),
CLIENT_ID("client_id"),
TAX_ID("tax_id"),
TAX_MODE("tax_mode"),
COMPANY_INN("company_inn"),
COMPANY_NAME("company_name"),
COMPANY_ADDRESS("company_address"),
COMPANY_EMAIL("company_email"),
POLLING_DELAY("polling_delay"),
MAX_TIME_POLLING("max_time_polling"),
TIMER_TIMEOUT("timer_timeout"),
TIMER_ADD_TIME("timer_add_time"),
PRIVATE_KEY("private_key"),
KEY("key"),
PAYMENT_METHOD("payment_method"),
PAYMENT_OBJECT("payment_object");
private final String field;
}

View File

@ -0,0 +1,8 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.constant;
public enum TargetType {
DEBIT,
CREDIT,
REFUND_DEBIT,
REFUND_CREDIT
}

View File

@ -0,0 +1,52 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.converter;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.state.deserializer.AdapterDeserializer;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.AdapterState;
import com.rbkmoney.damsel.cashreg.provider.CashRegContext;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* Usage example:
* <pre>
* {@code
* @Autowired
* CashRegAdapterContextConverter cashRegAdapterContextConverter;
* }
* </pre>
*
* <pre>
* {@code
* AdapterContext adapterContext = cashRegAdapterContextConverter.convert(cashRegContext)
* }
* </pre>
*/
@Component
@RequiredArgsConstructor
public class CashRegAdapterContextConverter implements Converter<CashRegContext, AdapterState> {
private static final byte[] DEFAULT_STATE = new byte[0];
private final AdapterDeserializer deserializer;
@Override
public AdapterState convert(CashRegContext context) {
AdapterState adapterContext = new AdapterState();
byte[] state = getState(context);
if (state != null && state.length > 0) {
return deserializer.read(state);
}
return adapterContext;
}
private byte[] getState(CashRegContext context) {
if (context.getSession().isSetState()) {
return context.getSession().getState();
}
return DEFAULT_STATE;
}
}

View File

@ -0,0 +1,105 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.converter;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.extractors.TypeExtractor;
import com.rbkmoney.damsel.cashreg.provider.CashRegContext;
import com.rbkmoney.damsel.cashreg.provider.CashRegProviderSrv;
import com.rbkmoney.damsel.cashreg.provider.CashRegResult;
import com.rbkmoney.woody.api.flow.error.WErrorDefinition;
import com.rbkmoney.woody.api.flow.error.WErrorType;
import com.rbkmoney.woody.api.flow.error.WRuntimeException;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.thrift.impl.http.error.THTransportErrorMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TException;
/**
* Usage example:
* <p>
* Configuration
* <pre>
* {@code
* @Configuration
* public class HandlerConfiguration {
*
* @Bean
* @Autowired
* public CashRegProviderSrv.Iface serverHandlerLogDecorator(CashRegProvider cashRegProvider) {
* return new CashRegAdapterServiceLogDecorator(cashRegProvider);
* }
*
* }
* }
* </pre>
* <p>
* Servlet
* <pre>
* {@code
* @RequiredArgsConstructor
* @WebServlet("/adapter/cashreg/provider_name")
* public class AdapterServlet extends GenericServlet {
*
* private final CashRegProviderSrv.Iface handler;
* private Servlet servlet;
*
* @Override
* public void init(ServletConfig config) throws ServletException {
* super.init(config);
* servlet = new THServiceBuilder().build(CashRegProviderSrv.Iface.class, handler);
* }
*
* @Override
* public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
* servlet.service(request, response);
* }
*
* }
* }
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
public class CashRegAdapterServiceLogDecorator implements CashRegProviderSrv.Iface {
private final CashRegProviderSrv.Iface handler;
@Override
public CashRegResult register(CashRegContext cashRegContext) throws TException {
String cashRegType = TypeExtractor.extractCashRegType(cashRegContext);
String cashRegId = cashRegContext.getCashregId();
log.info("Started: {} with cashRegId {}", cashRegType, cashRegId);
try {
CashRegResult regResult = handler.register(cashRegContext);
log.info("Finished {} with cashRegId {}", cashRegType, cashRegId);
return regResult;
} catch (Exception ex) {
String message = "Failed handle " + cashRegType + " with cashRegId " + cashRegId;
logMessage(ex, message);
throw ex;
}
}
private static void logMessage(Exception ex, String message) {
if (isUndefinedResultOrUnavailable(ex)) {
log.warn(message, ex);
} else {
log.error(message, ex);
}
}
private static boolean isUndefinedResultOrUnavailable(Exception exception) {
WErrorDefinition definition;
if (exception instanceof WRuntimeException) {
definition = ((WRuntimeException) exception).getErrorDefinition();
} else {
THTransportErrorMapper errorMapper = new THTransportErrorMapper();
definition = errorMapper.mapToDef(exception, TraceContext.getCurrentTraceData().getActiveSpan());
}
boolean undefined = definition != null && WErrorType.UNDEFINED_RESULT.getKey().equals(definition.getErrorType().getKey());
boolean unavailable = definition != null && WErrorType.UNAVAILABLE_RESULT.getKey().equals(definition.getErrorType().getKey());
return undefined || unavailable;
}
}

View File

@ -0,0 +1,7 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.exception;
public class UnknownTargetTypeException extends RuntimeException {
public UnknownTargetTypeException() {
super("Unknown target type!");
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.exception;
import org.apache.thrift.TException;
public class UnsupportedMethodException extends TException {
public UnsupportedMethodException() {
super("Unsupported method");
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.exception;
public class ValidationException extends RuntimeException {
public ValidationException() {
super();
}
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, Throwable cause) {
super(message, cause);
}
public ValidationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.flow;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.EntryStateModel;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.ExitStateModel;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.Step;
public interface StepResolver<T extends EntryStateModel, R extends ExitStateModel> {
Step resolveEntry(T stateModel);
Step resolveExit(R stateModel);
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.flow;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.TargetType;
import org.apache.thrift.TUnion;
import java.util.Objects;
public class TargetTypeResolver {
public static TargetType resolve(com.rbkmoney.damsel.cashreg.type.Type type) {
return TargetTypeResolver.unionFieldToEnum(type, TargetType.class);
}
private static <T extends Enum<T>> T unionFieldToEnum(TUnion union, Class<T> enumType) {
Objects.requireNonNull(union, "Union must be set");
return Enum.valueOf(enumType, union.getSetField().getFieldName().toUpperCase());
}
}

View File

@ -0,0 +1,10 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.handler;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.EntryStateModel;
import org.apache.thrift.TException;
public interface CommonHandler<T, R, E extends EntryStateModel> {
boolean isHandler(final E entryStateModel);
T handle(E context) throws TException;
}

View File

@ -0,0 +1,27 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.handler;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.EntryStateModel;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.ExitStateModel;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.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<T extends ExitStateModel, P, R, E extends EntryStateModel> implements CommonHandler<T, R, E> {
private final Function<P, R> requestFunction;
private final Converter<E, P> converter;
private final Processor<T, E, R> 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,30 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.time.Instant;
@Getter
@Setter
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class AdapterState {
private Step nextStep;
@JsonProperty(value = "max_date_time_polling")
private Instant maxDateTimePolling;
@JsonProperty(value = "cashreg_id")
private String cashRegId;
@JsonProperty(value = "receipt_id")
private String receiptId;
}

View File

@ -0,0 +1,17 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Auth {
@ToString.Exclude
private String login;
@ToString.Exclude
private String pass;
}

View File

@ -0,0 +1,17 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
public class Client {
private String email;
private String phone;
private String sno;
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
public class Company {
private String email;
private String sno;
private String inn;
private String paymentAddress;
}

View File

@ -0,0 +1,38 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.TargetType;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Data
@Builder
public class EntryStateModel {
private String cashRegId;
private Auth auth;
private Company company;
private Client client;
private List<Items> items;
private List<Payments> payments;
private List<Vat> vats;
private BigDecimal total;
@ToString.Exclude
private Map<String, String> options;
private String callbackUrl;
private StateModel state;
private TargetType targetType;
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import com.rbkmoney.damsel.cashreg.CashRegInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExitStateModel {
private String errorCode;
private String errorMessage;
private AdapterState adapterContext;
private EntryStateModel entryStateModel;
private String cashRegId;
private CashRegInfo cashRegInfo;
}

View File

@ -0,0 +1,67 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.math.BigDecimal;
@ToString
@Getter
@Setter
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Items {
/**
* Наименование товара
* Максимальная длина строки 64 символа.
*/
private String name;
/**
* Цена в рублях:
* - целая часть не более 8 знаков;
* - дробная часть не более 2 знаков
*/
private BigDecimal price;
/**
* Количество/вес:
* - целая часть не более 8 знаков;
* - дробная часть не более 3 знаков.
*/
private BigDecimal quantity;
/**
* Сумма позиции в рублях:
* - целая часть не более 8 знаков;
* - дробная часть не более 2 знаков.
* Если значение sum меньше/больше значения (price*quantity), то разница является
* скидкой/надбавкой на позицию соответственно. В этих случаях происходит
* перерасчёт поля price для равномерного распределения скидки/надбавки по позициям.
*/
private BigDecimal sum;
/**
* Единица измерения товара, работы, услуги, платежа, выплаты, иного предмета расчета.
* Максимальная длина строки 16 символов.
*/
@JsonProperty("measurement_unit")
private String measurementUnit;
@JsonProperty("payment_method")
private String paymentMethod;
@JsonProperty("payment_object")
private String paymentObject;
@JsonProperty("vat")
private Vat vat;
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Data
@Builder
@AllArgsConstructor
public class OperationModel {
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.math.BigDecimal;
@Getter
@Setter
@Builder
@ToString
public class Payments {
private Integer type;
private BigDecimal sum;
}

View File

@ -0,0 +1,12 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.TargetType;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class StateModel {
private TargetType targetType;
private AdapterState adapterContext;
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum Step {
CHECK_DUPLICATION,
CREATE,
CHECK_STATUS
}

View File

@ -0,0 +1,17 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.math.BigDecimal;
@ToString
@Getter
@Setter
@Builder
public class Vat {
private String type;
private BigDecimal sum;
}

View File

@ -0,0 +1,53 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.processor;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.EntryStateModel;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.ExitStateModel;
/**
* Usage example:
* <p>
* Configuration:
* <pre>
* {@code
* @Configuration
* @RequiredArgsConstructor
* public class ProcessorConfiguration {
*
* @Bean
* public Processor<ExitStateModel, EntryStateModel, CommonResponse> responseProcessorChain() {
* ErrorProcessor errorProcessor = new ErrorProcessor();
* return new SuccessProcessor(errorProcessor);
* }
*
* }
* }
*
* Dummy Success Processor:
* <pre>
* {@code
* @RequiredArgsConstructor
* public class SuccessProcessor implements Processor<ExitStateModel, EntryStateModel, CommonResponse> {
*
* private final Processor<ExitStateModel, EntryStateModel, CommonResponse> nextProcessor;
*
* @Override
* public ExitStateModel process(CommonResponse response, EntryStateModel entryStateModel) {
*
* if(!response.hasError()) {
* return new ExitStateModel();
* }
*
* return nextProcessor.process(response, entryStateModel);
* }
*
* }
* }
* </pre>
*
* @param <T>
* @param <E>
* @param <R>
*/
public interface Processor<T extends ExitStateModel, E extends EntryStateModel, R> {
T process(R response, E context);
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.service;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.ExitStateModel;
import com.rbkmoney.damsel.cashreg.provider.Intent;
public interface IntentService {
Intent getFailureByCode(ExitStateModel exitStateModel);
Intent getFailureByCodeAndDesc(ExitStateModel exitStateModel);
Intent getSuccess(ExitStateModel exitStateModel);
Intent getSleep(ExitStateModel exitStateModel);
}

View File

@ -0,0 +1,57 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.service;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.config.properties.TimerProperties;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.ExitStateModel;
import com.rbkmoney.damsel.cashreg.base.Timer;
import com.rbkmoney.damsel.cashreg.provider.*;
import com.rbkmoney.error.mapping.ErrorMapping;
import com.rbkmoney.java.damsel.utils.extractors.OptionsExtractors;
import lombok.RequiredArgsConstructor;
import java.time.Instant;
/**
* Usage example:
*
* <pre>
* {@code
* IntentServiceImpl intentService = new IntentServiceImpl(errorMapping, timerProperties);
* Intent intent = intentService.getFailureByCode(exitStateModel);
* }
* </pre>
*/
@RequiredArgsConstructor
public class IntentServiceImpl implements IntentService {
private final ErrorMapping errorMapping;
private final TimerProperties timerProperties;
public Intent getFailureByCode(ExitStateModel exitStateModel) {
return Intent.finish(new FinishIntent(FinishStatus.failure(errorMapping.mapFailure(exitStateModel.getErrorCode()))));
}
public Intent getFailureByCodeAndDesc(ExitStateModel exitStateModel) {
return Intent.finish(new FinishIntent(FinishStatus.failure(errorMapping.mapFailure(exitStateModel.getErrorCode(), exitStateModel.getErrorMessage()))));
}
public Intent getSuccess(ExitStateModel exitStateModel) {
return Intent.finish(new FinishIntent(FinishStatus.success(new Success())));
}
public Intent getSleep(ExitStateModel exitStateModel) {
if (exitStateModel.getAdapterContext().getMaxDateTimePolling() == null) {
throw new IllegalArgumentException("Need to specify 'maxTimePoolingMillis' before sleep");
}
if (exitStateModel.getAdapterContext().getMaxDateTimePolling().getEpochSecond() < Instant.now().getEpochSecond()) {
return Intent.finish(new FinishIntent(FinishStatus.failure(errorMapping.mapFailure(
com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.Error.SLEEP_TIMEOUT.getCode(),
com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.Error.SLEEP_TIMEOUT.getMessage()
))));
}
int timerPollingDelay = OptionsExtractors.extractPollingDelay(exitStateModel.getEntryStateModel().getOptions(), timerProperties.getPollingDelay());
return Intent.sleep(new SleepIntent(new Timer(Timer.timeout(timerPollingDelay))));
}
}

View File

@ -0,0 +1,37 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.state.deserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.AdapterState;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Getter
@Setter
@Component
@AllArgsConstructor
public class AdapterDeserializer implements Deserializer<AdapterState> {
private final ObjectMapper mapper;
public AdapterState read(byte[] data) {
if (data == null) {
return new AdapterState();
}
try {
return getMapper().readValue(data, AdapterState.class);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public AdapterState read(String data) {
throw new RuntimeException("Not supported");
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.state.deserializer;
public interface Deserializer<TDState> {
TDState read(byte[] data);
TDState read(String data);
}

View File

@ -0,0 +1,16 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.state.serializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.model.AdapterState;
import com.rbkmoney.adapter.common.serializer.StateSerializer;
import org.springframework.stereotype.Component;
@Component
public class AdapterSerializer extends StateSerializer<AdapterState> {
public AdapterSerializer(ObjectMapper mapper) {
super(mapper);
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.state.serializer;
public interface Serializer<TState> {
byte[] writeByte(Object obj);
String writeString(Object obj);
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.state.serializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
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 IllegalArgumentException(e);
}
}
@Override
public String writeString(Object obj) {
try {
return Base64.getEncoder().encodeToString(getMapper().writeValueAsBytes(obj));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@ -0,0 +1,62 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.converter.ip;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.config.ConverterIpConfiguration;
import inet.ipaddr.AddressStringException;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import inet.ipaddr.ipv6.IPv6Address;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Converter IPv4 to IPv6
* <pre>
* {@code
* ConverterIp converterIp = new ConverterIp("2a04:4a00:5:10ff:4:1:");
* String url = converterIp.replaceIpv4ToIpv6("185.31.132.50"); // 2a04:4a00:5:966:d464:b2f8:a849:3ea8
* }
* </pre>
*
* @see ConverterIpConfiguration
*/
public class ConverterIp {
public static final String NAT_64_PREFIX = "2a04:4a00:5:10ff:4:1:";
private static final String REGEXP_IP = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";
private static final String DEFAULT_NAT_64_PREFIX = "::ffff:";
private String nat64prefix;
public ConverterIp() {
this.nat64prefix = NAT_64_PREFIX;
}
public ConverterIp(String nat64prefix) {
this.nat64prefix = nat64prefix;
}
public String replaceIpv4ToIpv6(String url) {
Pattern p = Pattern.compile(REGEXP_IP);
Matcher m = p.matcher(url);
if (m.find()) {
String ip = m.group(1);
IPAddress address;
try {
address = new IPAddressString(ip).toAddress();
} catch (AddressStringException e) {
throw new IllegalArgumentException(e);
}
IPv6Address ipv6Address = address.toIPv6();
String canonicalAddress = ipv6Address.toCanonicalString();
String suffix = canonicalAddress.replace(DEFAULT_NAT_64_PREFIX, this.nat64prefix);
return url.replace(ip, "[" + suffix + "]");
}
return url;
}
}

View File

@ -0,0 +1,51 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.creators;
import com.rbkmoney.damsel.cashreg.CashRegInfo;
import com.rbkmoney.damsel.cashreg.base.Timer;
import com.rbkmoney.damsel.cashreg.provider.*;
import com.rbkmoney.damsel.domain.Failure;
public class CashRegProviderCreators {
public static Intent createFinishIntentSuccess() {
return Intent.finish(new FinishIntent(createFinishStatusSuccess()));
}
public static Intent createFinishIntentFailure(Failure failure) {
return Intent.finish(new FinishIntent(createFinishStatusFailure(failure)));
}
public static FinishStatus createFinishStatusSuccess() {
return FinishStatus.success(new Success());
}
public static FinishStatus createFinishStatusFailure(Failure failure) {
return FinishStatus.failure(failure);
}
public static Failure createFailure(String code) {
return createFailure(code, code);
}
public static Failure createFailure(String code, String reason) {
return new Failure().setCode(code).setReason(reason);
}
public static CashRegResult createCashRegResult(Intent intent, byte[] state, CashRegInfo cashRegInfo) {
return new CashRegResult().setIntent(intent).setCashregInfo(cashRegInfo).setState(state);
}
public static Intent createIntentWithSleepIntent(Integer timer) {
return Intent.sleep(createSleepIntent(createTimerTimeout(timer)));
}
public static SleepIntent createSleepIntent(Timer timer) {
return new SleepIntent(timer);
}
public static Timer createTimerTimeout(Integer timeout) {
return Timer.timeout(timeout);
}
}

View File

@ -0,0 +1,23 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.extractors;
import com.rbkmoney.adapter.cashreg.spring.boot.starter.constant.OptionalField;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import java.util.Map;
import static java.lang.Integer.parseInt;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class OptionsExtractors {
public static Integer extractPollingDelay(Map<String, String> options, int pollingDelay) {
return parseInt(options.getOrDefault(OptionalField.POLLING_DELAY.getField(), String.valueOf(pollingDelay)));
}
public static Integer extractMaxTimePolling(Map<String, String> options, int maxTimePolling) {
return Integer.parseInt(options.getOrDefault(OptionalField.MAX_TIME_POLLING.getField(), String.valueOf(maxTimePolling)));
}
}

View File

@ -0,0 +1,20 @@
package com.rbkmoney.adapter.cashreg.spring.boot.starter.utils.extractors;
import com.rbkmoney.damsel.cashreg.provider.CashRegContext;
import com.rbkmoney.damsel.cashreg.type.Type;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TypeExtractor {
public static String extractCashRegType(CashRegContext context) {
return extractCashRegType(context.getSession().getType());
}
public static String extractCashRegType(Type type) {
return type.getSetField().getFieldName().toUpperCase();
}
}

View File

@ -0,0 +1,8 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.rbkmoney.adapter.cashreg.spring.boot.starter.config.ErrorMappingConfiguration,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.config.properties.AdapterCashRegProperties,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.config.properties.TimerProperties,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.config.ConverterIpConfiguration,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.state.serializer.AdapterSerializer,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.state.deserializer.AdapterDeserializer,\
com.rbkmoney.adapter.cashreg.spring.boot.starter.converter.CashRegAdapterContextConverter

View File

@ -0,0 +1,8 @@
[
{
"codeRegex":"003",
"descriptionRegex":"Invalid cardholder",
"mapping":"authorization_failed:operation_blocked",
"state": "payment"
}
]