diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb08ff7 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ca5a761 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "build_utils"] + path = build_utils + url = git@github.com:rbkmoney/build_utils.git + branch = master diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..c638432 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,13 @@ +#!groovy +build('adapter-common-lib', 'docker-host') { + checkoutRepo() + loadBuildUtils() + + def javaLibPipeline + runStage('load JavaLib pipeline') { + javaLibPipeline = load("build_utils/jenkins_lib/pipeJavaLib.groovy") + } + + def buildImageTag = "fcf116dd775cc2e91bffb6a36835754e3f2d5321" + javaLibPipeline(buildImageTag) +} \ No newline at end of file diff --git a/build_utils b/build_utils new file mode 160000 index 0000000..ea4aa04 --- /dev/null +++ b/build_utils @@ -0,0 +1 @@ +Subproject commit ea4aa042f482551d624fd49a570d28488f479e93 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..950fe4b --- /dev/null +++ b/pom.xml @@ -0,0 +1,198 @@ + + + + 4.0.0 + jar + + com.rbkmoney + adapter-common-lib + 0.0.1-SNAPSHOT + + + + + UTF-8 + 8 + bc95d0d6dc13c693acd2b274531a7d604b877bf3 + ${env.REGISTRY} + 0.3.6 + 1.7.25 + 3.9 + 1.12 + 1.283-27b28aa + 2.1.9 + 1.1.15 + 2.1.9 + 1.0.2 + + + + + org.apache.commons + commons-lang3 + ${apache.commons.lang3.version} + + + commons-codec + commons-codec + ${apache.commons.codec.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.projectlombok + lombok + 1.18.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.8 + + + + + org.springframework + spring-core + 5.1.7.RELEASE + + + org.springframework + spring-web + 5.1.7.RELEASE + + + + + com.rbkmoney + damsel + ${damsel.version} + + + com.rbkmoney + damsel-utils + ${damsel.utils.version} + + + com.rbkmoney.woody + woody-thrift + ${woody.version} + + + com.rbkmoney.proxy-libs + hellgate-adapter-client + ${hellgate-adapter-client.version} + + + com.rbkmoney + error-mapping-java + ${error-mapping-java.version} + + + + + org.springframework.boot + spring-boot-starter-test + 2.1.5.RELEASE + test + + + junit + junit + 4.12 + test + + + javax.servlet + javax.servlet-api + 4.0.1 + + + javax.validation + validation-api + 2.0.1.Final + + + org.springframework.boot + spring-boot + 2.1.1.RELEASE + + + org.springframework.boot + spring-boot-actuator + 2.1.1.RELEASE + + + + + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/groups/public + + false + releases + RBKmoney releases repository + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/releases + default + + + true + snapshots + RBKmoney snapshots repository + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/snapshots + default + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.0 + + + attach-sources + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.2 + + ${sonar.jacoco.reportPath} + true + + + + agent + + prepare-agent + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + + diff --git a/src/main/java/com/rbkmoney/adapter/common/Validator.java b/src/main/java/com/rbkmoney/adapter/common/Validator.java new file mode 100644 index 0000000..40e589f --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/Validator.java @@ -0,0 +1,7 @@ +package com.rbkmoney.adapter.common; + +public interface Validator { + + void validate(O object); + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/component/LoggingInterceptor.java b/src/main/java/com/rbkmoney/adapter/common/component/LoggingInterceptor.java new file mode 100644 index 0000000..c6c4528 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/component/LoggingInterceptor.java @@ -0,0 +1,105 @@ +package com.rbkmoney.adapter.common.component; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StreamUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class LoggingInterceptor implements ClientHttpRequestInterceptor { + + private AtomicInteger requestNumberSequence = new AtomicInteger(0); + + public LoggingInterceptor() { + log.warn("Warning! LoggingInterceptor can write bank card data in RAW"); + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + int requestNumber = requestNumberSequence.incrementAndGet(); + logRequest(requestNumber, request, body); + ClientHttpResponse response = execution.execute(request, body); + response = new BufferedClientHttpResponse(response); + logResponse(requestNumber, response); + return response; + } + + private void logRequest(int requestNumber, HttpRequest request, byte[] body) { + if (log.isDebugEnabled()) { + String prefix = requestNumber + " > "; + log.debug("{} Request: {} {}", prefix, request.getMethod(), request.getURI()); + log.debug("{} Headers: {}", prefix, request.getHeaders()); + if (body.length > 0) { + log.debug("{} Body: \n{}", prefix, new String(body, StandardCharsets.UTF_8)); + } + } + } + + private void logResponse(int requestNumber, ClientHttpResponse response) throws IOException { + if (log.isDebugEnabled()) { + String prefix = requestNumber + " < "; + log.debug("{} Response: {} {} {}", prefix, response.getStatusCode(), response.getStatusCode().name(), response.getStatusText()); + log.debug("{} Headers: {}", prefix, response.getHeaders()); + String body = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8); + if (body.length() > 0) { + log.debug("{} Body: \n{}", prefix, body); + } + } + } + + /** + * Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result). + */ + private class BufferedClientHttpResponse implements ClientHttpResponse { + + private final ClientHttpResponse response; + private byte[] body; + + public BufferedClientHttpResponse(ClientHttpResponse response) { + this.response = response; + } + + @Override + public HttpStatus getStatusCode() throws IOException { + return response.getStatusCode(); + } + + @Override + public int getRawStatusCode() throws IOException { + return response.getRawStatusCode(); + } + + @Override + public String getStatusText() throws IOException { + return response.getStatusText(); + } + + @Override + public void close() { + response.close(); + } + + @Override + public InputStream getBody() throws IOException { + if (body == null) { + body = StreamUtils.copyToByteArray(response.getBody()); + } + return new ByteArrayInputStream(body); + } + + @Override + public HttpHeaders getHeaders() { + return response.getHeaders(); + } + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/component/NetworkFilterComponent.java b/src/main/java/com/rbkmoney/adapter/common/component/NetworkFilterComponent.java new file mode 100644 index 0000000..dff9319 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/component/NetworkFilterComponent.java @@ -0,0 +1,77 @@ +package com.rbkmoney.adapter.common.component; + +import com.rbkmoney.woody.api.flow.WFlow; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class NetworkFilterComponent { + + public static final String HEALTH = "/actuator/health"; + + public FilterRegistrationBean externalPortRestrictingFilter(int restPort, String restEndpoint) { + Filter filter = new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String servletPath = request.getServletPath(); + if ((request.getLocalPort() == restPort) + && !(servletPath.startsWith(restEndpoint) || servletPath.startsWith(HEALTH))) { + response.sendError(404, "Unknown address"); + return; + } + filterChain.doFilter(request, response); + } + }; + + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(filter); + filterRegistrationBean.setOrder(-100); + filterRegistrationBean.setName("httpPortFilter"); + filterRegistrationBean.addUrlPatterns("/*"); + return filterRegistrationBean; + } + + public FilterRegistrationBean woodyFilter(int restPort, String restEndpoint) { + WFlow wFlow = new WFlow(); + Filter filter = new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if ((request.getLocalPort() == restPort) + && request.getServletPath().startsWith(restEndpoint)) { + wFlow.createServiceFork(() -> { + try { + filterChain.doFilter(request, response); + } catch (IOException | ServletException e) { + sneakyThrow(e); + } + }).run(); + return; + } + filterChain.doFilter(request, response); + } + + private T sneakyThrow(Throwable t) throws E { + throw (E) t; + } + }; + + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(filter); + filterRegistrationBean.setOrder(-50); + filterRegistrationBean.setName("woodyFilter"); + filterRegistrationBean.addUrlPatterns(restEndpoint + "*"); + return filterRegistrationBean; + } +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/common/component/RestTemplateComponent.java b/src/main/java/com/rbkmoney/adapter/common/component/RestTemplateComponent.java new file mode 100644 index 0000000..67fc3ca --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/component/RestTemplateComponent.java @@ -0,0 +1,74 @@ +package com.rbkmoney.adapter.common.component; + +import com.rbkmoney.woody.api.trace.ContextUtils; +import com.rbkmoney.woody.api.trace.context.TraceContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.MediaType; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class RestTemplateComponent { + + public RestTemplate getSimpleRestTemplate(MetricsRestTemplateCustomizer metricsRestTemplateCustomizer, + int networkTimeout) { + HttpComponentsClientHttpRequestFactory requestFactory = getRequestFactory(getSimpleHttpClient()); + RestTemplateBuilder restTemplateBuilder = getRestTemplateBuilder(requestFactory, metricsRestTemplateCustomizer); + return getRestTemplate(restTemplateBuilder, networkTimeout); + } + + public RestTemplate getRestTemplateWithConverters(MetricsRestTemplateCustomizer metricsRestTemplateCustomizer, + List> messageConverterList, + int networkTimeout) { + HttpComponentsClientHttpRequestFactory requestFactory = getRequestFactory(getSimpleHttpClient()); + RestTemplateBuilder restTemplateBuilder = getRestTemplateBuilder(requestFactory, metricsRestTemplateCustomizer); + RestTemplate restTemplate = getRestTemplate(restTemplateBuilder, networkTimeout); + restTemplate.setMessageConverters(messageConverterList); + return restTemplate; + } + + public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder, int networkTimeout) { + int executionTimeout = + ContextUtils.getExecutionTimeout(TraceContext.getCurrentTraceData().getServiceSpan(), networkTimeout); + return restTemplateBuilder + .setConnectTimeout(Duration.ofMillis(executionTimeout)) + .setReadTimeout(Duration.ofMillis(executionTimeout)) + .build(); + } + + public RestTemplateBuilder getRestTemplateBuilder(HttpComponentsClientHttpRequestFactory requestFactory, + MetricsRestTemplateCustomizer metricsRestTemplateCustomizer) { + return new RestTemplateBuilder() + .requestFactory(() -> requestFactory) + .additionalCustomizers(metricsRestTemplateCustomizer); + } + + public HttpComponentsClientHttpRequestFactory getRequestFactory(CloseableHttpClient httpClient) { + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + return requestFactory; + } + + public CloseableHttpClient getSimpleHttpClient() { + return HttpClients.custom() + .disableAutomaticRetries() + .build(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/common/constants/ThreeDsFields.java b/src/main/java/com/rbkmoney/adapter/common/constants/ThreeDsFields.java new file mode 100644 index 0000000..d9d334d --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/constants/ThreeDsFields.java @@ -0,0 +1,14 @@ +package com.rbkmoney.adapter.common.constants; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ThreeDsFields { + + public static final String MD = "MD"; + public static final String PA_REQ = "PaReq"; + public static final String PA_RES = "PaRes"; + public static final String TERM_URL = "TermUrl"; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/controller/AdapterController.java b/src/main/java/com/rbkmoney/adapter/common/controller/AdapterController.java new file mode 100644 index 0000000..43a0ab2 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/controller/AdapterController.java @@ -0,0 +1,59 @@ +package com.rbkmoney.adapter.common.controller; + +import com.rbkmoney.adapter.common.model.Callback; +import com.rbkmoney.adapter.common.serializer.CallbackSerializer; +import com.rbkmoney.adapter.helpers.hellgate.HellgateAdapterClient; +import com.rbkmoney.adapter.helpers.hellgate.exception.HellgateException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.function.BiFunction; + +@Slf4j +@RequiredArgsConstructor +public abstract class AdapterController { + + private final HellgateAdapterClient hgClient; + + private final CallbackSerializer callbackSerializer; + + public String receivePaymentIncomingParameters(HttpServletRequest servletRequest, + HttpServletResponse servletResponse) throws IOException { + return processCallback(servletRequest, servletResponse, hgClient::processPaymentCallback); + } + + public String receiveRecurrentIncomingParameters(HttpServletRequest servletRequest, + HttpServletResponse servletResponse) throws IOException { + return processCallback(servletRequest, servletResponse, hgClient::processRecurrentTokenCallback); + } + + private String processCallback(HttpServletRequest servletRequest, + HttpServletResponse servletResponse, + BiFunction hgFunction) throws IOException { + String resp = ""; + Callback callbackObj = callbackSerializer.read(servletRequest); + log.info("ProcessCallback {}", callbackObj); + try { + String tag = callbackObj.getMd(); + ByteBuffer callback = ByteBuffer.wrap(callbackSerializer.writeByte(callbackObj)); + ByteBuffer response = hgFunction.apply(tag, callback); + resp = new String(response.array(), StandardCharsets.UTF_8); + } catch (HellgateException e) { + log.warn("Failed handle callback for recurrent", e); + } catch (Exception e) { + log.error("Failed handle callback for recurrent", e); + } + if (StringUtils.hasText(callbackObj.getTerminationUri())) { + servletResponse.sendRedirect(callbackObj.getTerminationUri()); + } + log.info("ProcessCallback response {}", resp); + return resp; + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/enums/Step.java b/src/main/java/com/rbkmoney/adapter/common/enums/Step.java new file mode 100644 index 0000000..d5625dc --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/enums/Step.java @@ -0,0 +1,24 @@ +package com.rbkmoney.adapter.common.enums; + +public enum Step { + + PRE_AUTH, + AUTH, + FINISH_THREE_DS, + AWAIT_CALLBACK, + CANCEL, + REFUND, + CHECK_STATUS, + CAPTURE, + + RECURRENT, + AUTH_RECURRENT, + + GENERATE_TOKEN_AUTH, + GENERATE_TOKEN_FINISH_THREE_DS, + GENERATE_TOKEN_CAPTURE, + GENERATE_TOKEN_REFUND, + GENERATE_TOKEN_CHECK_STATUS, + GENERATE_TOKEN_FINISH + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/enums/TargetStatus.java b/src/main/java/com/rbkmoney/adapter/common/enums/TargetStatus.java new file mode 100644 index 0000000..3fc2389 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/enums/TargetStatus.java @@ -0,0 +1,10 @@ +package com.rbkmoney.adapter.common.enums; + +public enum TargetStatus { + + PROCESSED, + CAPTURED, + CANCELLED, + REFUNDED + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/exception/UnknownTargetStatusException.java b/src/main/java/com/rbkmoney/adapter/common/exception/UnknownTargetStatusException.java new file mode 100644 index 0000000..9d40326 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/exception/UnknownTargetStatusException.java @@ -0,0 +1,8 @@ +package com.rbkmoney.adapter.common.exception; + +public class UnknownTargetStatusException extends RuntimeException { + + public UnknownTargetStatusException() { + super("Unknown target status!"); + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/exception/UnsupportedMethodException.java b/src/main/java/com/rbkmoney/adapter/common/exception/UnsupportedMethodException.java new file mode 100644 index 0000000..9d3a075 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/exception/UnsupportedMethodException.java @@ -0,0 +1,11 @@ +package com.rbkmoney.adapter.common.exception; + +import org.apache.thrift.TException; + +public class UnsupportedMethodException extends TException { + + public UnsupportedMethodException() { + super("Unsupported method"); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandler.java b/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandler.java new file mode 100644 index 0000000..de9fa83 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandler.java @@ -0,0 +1,11 @@ +package com.rbkmoney.adapter.common.handler; + +import org.apache.thrift.TException; + +public interface CommonHandler { + + boolean isHandle(E model); + + T handle(E context) throws TException; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandlerImpl.java b/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandlerImpl.java new file mode 100644 index 0000000..52b0a22 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/CommonHandlerImpl.java @@ -0,0 +1,25 @@ +package com.rbkmoney.adapter.common.handler; + +import com.rbkmoney.adapter.common.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 implements CommonHandler { + + private final Function requestFunction; + private final Converter converter; + private final Processor processor; + + @Override + public T handle(E entryStateModel) { + P request = converter.convert(entryStateModel); + R response = requestFunction.apply(request); + return processor.process(response, entryStateModel); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/ServerHandlerLogDecorator.java b/src/main/java/com/rbkmoney/adapter/common/handler/ServerHandlerLogDecorator.java new file mode 100644 index 0000000..06eefce --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/ServerHandlerLogDecorator.java @@ -0,0 +1,79 @@ +package com.rbkmoney.adapter.common.handler; + +import com.rbkmoney.damsel.proxy_provider.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; + +import java.nio.ByteBuffer; + +import static com.rbkmoney.java.damsel.utils.extractors.ProxyProviderPackageExtractors.*; +import static com.rbkmoney.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 = 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 = context.getTokenInfo().getPaymentTool().getId(); + 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 = extractInvoiceId(context); + String invoicePaymentStatus = extractTargetInvoicePaymentStatus(context); + log.info("Process payment handle {} start with invoiceId {}", invoicePaymentStatus, invoiceId); + try { + PaymentProxyResult proxyResult = handler.processPayment(context); + log.info("Process payment handle {} finished with invoiceId {} and proxyResult {}", + invoicePaymentStatus, invoiceId, proxyResult); + return proxyResult; + } catch (Exception e) { + String message = String.format("Failed handle %s process payment for operation with invoiceId %s", + invoicePaymentStatus, invoiceId); + logMessage(e, message); + throw e; + } + } + + @Override + public PaymentCallbackResult handlePaymentCallback(ByteBuffer byteBuffer, + PaymentContext context) throws TException { + String invoiceId = context.getPaymentInfo().getInvoice().getId(); + 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); + } + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/callback/CallbackHandler.java b/src/main/java/com/rbkmoney/adapter/common/handler/callback/CallbackHandler.java new file mode 100644 index 0000000..731c023 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/callback/CallbackHandler.java @@ -0,0 +1,9 @@ +package com.rbkmoney.adapter.common.handler.callback; + +import java.nio.ByteBuffer; + +public interface CallbackHandler { + + T handleCallback(ByteBuffer byteBuffer, R context); + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/callback/PaymentCallbackHandler.java b/src/main/java/com/rbkmoney/adapter/common/handler/callback/PaymentCallbackHandler.java new file mode 100644 index 0000000..ffea0e3 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/callback/PaymentCallbackHandler.java @@ -0,0 +1,49 @@ +package com.rbkmoney.adapter.common.handler.callback; + +import com.rbkmoney.adapter.common.enums.Step; +import com.rbkmoney.adapter.common.model.AdapterContext; +import com.rbkmoney.adapter.common.model.Callback; +import com.rbkmoney.adapter.common.properties.TimerProperties; +import com.rbkmoney.adapter.common.serializer.AdapterSerializer; +import com.rbkmoney.adapter.common.serializer.CallbackSerializer; +import com.rbkmoney.damsel.proxy_provider.PaymentCallbackProxyResult; +import com.rbkmoney.damsel.proxy_provider.PaymentCallbackResult; +import com.rbkmoney.damsel.proxy_provider.PaymentContext; +import com.rbkmoney.java.damsel.utils.extractors.OptionsExtractors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + +import static com.rbkmoney.java.damsel.utils.creators.ProxyProviderPackageCreators.createCallbackResult; +import static com.rbkmoney.java.damsel.utils.creators.ProxyProviderPackageCreators.createIntentWithSleepIntent; + +@Slf4j +@RequiredArgsConstructor +public class PaymentCallbackHandler implements CallbackHandler { + + private final AdapterSerializer adapterSerializer; + + private final CallbackSerializer callbackSerializer; + + private final TimerProperties timerProperties; + + @Override + public PaymentCallbackResult handleCallback(ByteBuffer callback, PaymentContext context) { + AdapterContext adapterContext = adapterSerializer.getAdapterContext(context); + adapterContext.setNextStep(Step.FINISH_THREE_DS); + Callback callbackObj = callbackSerializer.read(callback.array()); + adapterContext.setPaRes(callbackObj.getPaRes()); + adapterContext.setMd(callbackObj.getMd()); + + int timerPollingDelay = + OptionsExtractors.extractPollingDelay(context.getOptions(), timerProperties.getPollingDelay()); + byte[] callbackResponse = new byte[0]; + return createCallbackResult( + callbackResponse, + new PaymentCallbackProxyResult() + .setIntent(createIntentWithSleepIntent(timerPollingDelay)) + .setNextState(adapterSerializer.writeByte(adapterContext)) + ); + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/handler/callback/RecurrentTokenCallbackHandler.java b/src/main/java/com/rbkmoney/adapter/common/handler/callback/RecurrentTokenCallbackHandler.java new file mode 100644 index 0000000..dd2873d --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/handler/callback/RecurrentTokenCallbackHandler.java @@ -0,0 +1,51 @@ +package com.rbkmoney.adapter.common.handler.callback; + +import com.rbkmoney.adapter.common.enums.Step; +import com.rbkmoney.adapter.common.model.AdapterContext; +import com.rbkmoney.adapter.common.model.Callback; +import com.rbkmoney.adapter.common.properties.TimerProperties; +import com.rbkmoney.adapter.common.serializer.AdapterSerializer; +import com.rbkmoney.adapter.common.serializer.CallbackSerializer; +import com.rbkmoney.damsel.proxy_provider.RecurrentTokenCallbackResult; +import com.rbkmoney.damsel.proxy_provider.RecurrentTokenContext; +import com.rbkmoney.damsel.proxy_provider.RecurrentTokenIntent; +import com.rbkmoney.damsel.proxy_provider.RecurrentTokenProxyResult; +import com.rbkmoney.java.damsel.utils.extractors.OptionsExtractors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; + +import static com.rbkmoney.java.damsel.utils.creators.BasePackageCreators.createTimerTimeout; +import static com.rbkmoney.java.damsel.utils.creators.ProxyProviderPackageCreators.createRecurrentTokenCallbackResult; +import static com.rbkmoney.java.damsel.utils.creators.ProxyProviderPackageCreators.createSleepIntent; + +@Slf4j +@RequiredArgsConstructor +public class RecurrentTokenCallbackHandler implements CallbackHandler { + + private final AdapterSerializer adapterSerializer; + + private final CallbackSerializer callbackSerializer; + + private final TimerProperties timerProperties; + + @Override + public RecurrentTokenCallbackResult handleCallback(ByteBuffer callback, RecurrentTokenContext context) { + AdapterContext adapterContext = adapterSerializer.getAdapterContext(context); + adapterContext.setNextStep(Step.GENERATE_TOKEN_FINISH_THREE_DS); + Callback callbackObj = callbackSerializer.read(callback.array()); + adapterContext.setPaRes(callbackObj.getPaRes()); + adapterContext.setMd(callbackObj.getMd()); + + int timerPollingDelay = + OptionsExtractors.extractPollingDelay(context.getOptions(), timerProperties.getPollingDelay()); + byte[] callbackResponse = new byte[0]; + return createRecurrentTokenCallbackResult( + callbackResponse, + new RecurrentTokenProxyResult() + .setIntent(RecurrentTokenIntent.sleep(createSleepIntent(createTimerTimeout(timerPollingDelay)))) + .setNextState(adapterSerializer.writeByte(adapterContext)) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleErrorMapping.java b/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleErrorMapping.java new file mode 100644 index 0000000..01d3b87 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleErrorMapping.java @@ -0,0 +1,27 @@ +package com.rbkmoney.adapter.common.mapper; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.error.mapping.ErrorMapping; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +@RequiredArgsConstructor +public class SimpleErrorMapping { + + private final Resource filePath; + + private final String patternReason; + + public ErrorMapping getErrorMapping() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + + ErrorMapping errorMapping = new ErrorMapping(filePath.getInputStream(), patternReason, mapper); + errorMapping.validateMappingFormat(); + return errorMapping; + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleObjectMapper.java b/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleObjectMapper.java new file mode 100644 index 0000000..e12762a --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/mapper/SimpleObjectMapper.java @@ -0,0 +1,23 @@ +package com.rbkmoney.adapter.common.mapper; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.adapter.common.mapper.naming.strategy.UpperSnakeCaseStrategy; + +public class SimpleObjectMapper { + + public ObjectMapper getSimpleObjectMapper() { + return new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + public ObjectMapper getUpperSnakeCaseStrategyMapper() { + return new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setPropertyNamingStrategy(new UpperSnakeCaseStrategy()) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/mapper/naming/strategy/UpperSnakeCaseStrategy.java b/src/main/java/com/rbkmoney/adapter/common/mapper/naming/strategy/UpperSnakeCaseStrategy.java new file mode 100644 index 0000000..06fd46c --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/mapper/naming/strategy/UpperSnakeCaseStrategy.java @@ -0,0 +1,58 @@ +package com.rbkmoney.adapter.common.mapper.naming.strategy; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; + +public class UpperSnakeCaseStrategy extends PropertyNamingStrategy { + @Override + public String nameForField(MapperConfig config, AnnotatedField field, String defaultName) { + return convert(field.getName()); + } + + @Override + public String nameForGetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { + return convert(method.getName()); + } + + @Override + public String nameForSetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { + return convert(method.getName()); + } + + private String convert(String inputString) { + if (inputString == null) { + return inputString; + } + String input = inputString.substring(3); + + if (input.length() > 1 && Character.isUpperCase(input.charAt(0)) && Character.isUpperCase(input.charAt(1))) { + input = input.substring(0, 1).toLowerCase() + input.substring(1); + } + int length = input.length(); + StringBuilder result = new StringBuilder(length * 2); + int resultLength = 0; + boolean wasPrevTranslated = false; + for (int i = 0; i < length; i++) { + char c = input.charAt(i); + if (i > 0 || c != '_') { // skip first starting underscore + if (Character.isUpperCase(c)) { + if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_') { + result.append('_'); + resultLength++; + } + c = Character.toLowerCase(c); + wasPrevTranslated = true; + } + else { + wasPrevTranslated = false; + } + result.append(c); + resultLength++; + } + } + String finalString = resultLength > 0 ? result.toString() : input; + return finalString.toUpperCase(); + } +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/common/model/AdapterContext.java b/src/main/java/com/rbkmoney/adapter/common/model/AdapterContext.java new file mode 100644 index 0000000..4cb5af7 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/model/AdapterContext.java @@ -0,0 +1,25 @@ +package com.rbkmoney.adapter.common.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.rbkmoney.adapter.common.enums.Step; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AdapterContext { + + private String md; + private String paReq; + private String paRes; + private String acsUrl; + private String termUrl; + + private Step nextStep; + +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/common/model/Callback.java b/src/main/java/com/rbkmoney/adapter/common/model/Callback.java new file mode 100644 index 0000000..272ec14 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/model/Callback.java @@ -0,0 +1,18 @@ +package com.rbkmoney.adapter.common.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class Callback { + + @JsonProperty(value = "MD") + private String md; + + @JsonProperty(value = "PaRes") + private String paRes; + + @JsonProperty(value = "termination_uri") + private String terminationUri; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/model/RecToken.java b/src/main/java/com/rbkmoney/adapter/common/model/RecToken.java new file mode 100644 index 0000000..6062f99 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/model/RecToken.java @@ -0,0 +1,16 @@ +package com.rbkmoney.adapter.common.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RecToken { + + private Map recTokenMap; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/processor/Processor.java b/src/main/java/com/rbkmoney/adapter/common/processor/Processor.java new file mode 100644 index 0000000..64054e4 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/processor/Processor.java @@ -0,0 +1,7 @@ +package com.rbkmoney.adapter.common.processor; + +public interface Processor { + + R process(T response, E context); + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/properties/AdapterProperties.java b/src/main/java/com/rbkmoney/adapter/common/properties/AdapterProperties.java new file mode 100644 index 0000000..3457bc5 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/properties/AdapterProperties.java @@ -0,0 +1,24 @@ +package com.rbkmoney.adapter.common.properties; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; + +@Getter +@Setter +public abstract class AdapterProperties { + + @NotEmpty + private String url; + + @NotEmpty + private String callbackUrl; + + @NotEmpty + private String pathCallbackUrl; + + @NotEmpty + private String pathRecurrentCallbackUrl; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/properties/TimerProperties.java b/src/main/java/com/rbkmoney/adapter/common/properties/TimerProperties.java new file mode 100644 index 0000000..52edf06 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/properties/TimerProperties.java @@ -0,0 +1,21 @@ +package com.rbkmoney.adapter.common.properties; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; + +@Getter +@Setter +public abstract class TimerProperties { + + @NotNull + private int redirectTimeout; + + @NotNull + private int maxTimePolling; + + @NotNull + private int pollingDelay; + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/serializer/AdapterSerializer.java b/src/main/java/com/rbkmoney/adapter/common/serializer/AdapterSerializer.java new file mode 100644 index 0000000..b9bf685 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/serializer/AdapterSerializer.java @@ -0,0 +1,47 @@ +package com.rbkmoney.adapter.common.serializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.adapter.common.model.AdapterContext; +import com.rbkmoney.damsel.proxy_provider.PaymentContext; +import com.rbkmoney.damsel.proxy_provider.RecurrentTokenContext; + +import java.io.IOException; + +public class AdapterSerializer extends StateSerializer { + + public AdapterSerializer(ObjectMapper mapper) { + super(mapper); + } + + @Override + public AdapterContext read(byte[] data) { + if (data == null) { + return new AdapterContext(); + } + try { + return getMapper().readValue(data, AdapterContext.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public AdapterContext getAdapterContext(Object context) { + AdapterContext adapterContext = new AdapterContext(); + byte[] state = getState(context); + if (state != null && state.length > 0) { + return this.read(state); + } + return adapterContext; + } + + public static byte[] getState(Object context) { + if (context instanceof RecurrentTokenContext) { + if (((RecurrentTokenContext) context).getSession() == null) { + return new byte[0]; + } + return ((RecurrentTokenContext) context).getSession().getState(); + } + return ((PaymentContext) context).getSession().getState(); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/serializer/CallbackSerializer.java b/src/main/java/com/rbkmoney/adapter/common/serializer/CallbackSerializer.java new file mode 100644 index 0000000..521e99b --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/serializer/CallbackSerializer.java @@ -0,0 +1,40 @@ +package com.rbkmoney.adapter.common.serializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.adapter.common.model.Callback; + +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; + +public class CallbackSerializer extends StateSerializer { + + public CallbackSerializer(ObjectMapper mapper) { + super(mapper); + } + + @Override + public Callback read(byte[] data) { + if (data == null) { + return new Callback(); + } + try { + return getMapper().readValue(data, Callback.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + public Callback read(HttpServletRequest request) { + Map stringMap = Optional.ofNullable(request.getParameterMap()) + .orElseGet(HashMap::new) + .entrySet().stream() + .collect(Collectors.toMap(k -> k.getKey().trim(), + v -> v.getValue()[0])); + return mapper.convertValue(stringMap, Callback.class); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/serializer/RecTokenSerializer.java b/src/main/java/com/rbkmoney/adapter/common/serializer/RecTokenSerializer.java new file mode 100644 index 0000000..30a6e4d --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/serializer/RecTokenSerializer.java @@ -0,0 +1,26 @@ +package com.rbkmoney.adapter.common.serializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.adapter.common.model.RecToken; + +import java.io.IOException; +import java.util.Base64; + +public class RecTokenSerializer extends StateSerializer { + + public RecTokenSerializer(ObjectMapper mapper) { + super(mapper); + } + + @Override + public RecToken read(String data) { + if (data == null) { + return new RecToken(); + } + try { + return getMapper().readValue(Base64.getDecoder().decode(data), RecToken.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/serializer/Serializer.java b/src/main/java/com/rbkmoney/adapter/common/serializer/Serializer.java new file mode 100644 index 0000000..9544e34 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/serializer/Serializer.java @@ -0,0 +1,13 @@ +package com.rbkmoney.adapter.common.serializer; + +public interface Serializer { + + byte[] writeByte(Object obj); + + String writeString(Object obj); + + TState read(byte[] data); + + TState read(String data); + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/serializer/StateSerializer.java b/src/main/java/com/rbkmoney/adapter/common/serializer/StateSerializer.java new file mode 100644 index 0000000..0236cc7 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/serializer/StateSerializer.java @@ -0,0 +1,46 @@ +package com.rbkmoney.adapter.common.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 implements Serializer { + + 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); + } + } + + @Override + public T read(byte[] data) { + throw new RuntimeException("Not supported"); + } + + @Override + public T read(String data) { + throw new RuntimeException("Not supported"); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/service/ThreeDsPropertiesService.java b/src/main/java/com/rbkmoney/adapter/common/service/ThreeDsPropertiesService.java new file mode 100644 index 0000000..48f14ab --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/service/ThreeDsPropertiesService.java @@ -0,0 +1,9 @@ +package com.rbkmoney.adapter.common.service; + +import java.util.Map; + +public interface ThreeDsPropertiesService { + + Map initProperties(T stateModel); + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/utils/converter/Base62.java b/src/main/java/com/rbkmoney/adapter/common/utils/converter/Base62.java new file mode 100644 index 0000000..59bb3b1 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/utils/converter/Base62.java @@ -0,0 +1,75 @@ +package com.rbkmoney.adapter.common.utils.converter; + +/** @see "https://github.com/dukky/Base62/blob/master/base62/src/im/duk/base62/Base62.java" */ +public class Base62 { + + private String characters; + + /** + * Constructs a Base62 object with the default charset (0..9a..zA..Z). + */ + public Base62() { + this("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + } + + /** + * Constructs a Base62 object with a custom charset. + * + * @param characters + * the charset to use. Must be 62 characters. + * @throws IllegalArgumentException if the supplied charset is not 62 characters long. + */ + public Base62(String characters) { + if (!(characters.length() == 62)) { + throw new IllegalArgumentException("Invalid string length, must be 62."); + } + this.characters = characters; + } + + /** + * Encodes a decimal value to a Base62 String. + * + * @param b10 + * the decimal value to encode, must be nonnegative. + * @return the number encoded as a Base62 String. + */ + public String encodeBase10(Long b10) { + if (b10 < 0) { + throw new IllegalArgumentException("b10 must be nonnegative"); + } + String ret = ""; + while (b10 > 0) { + ret = characters.charAt((int) (b10 % 62)) + ret; + b10 /= 62; + } + return ret; + + } + + /** + * Decodes a Base62 String returning a long. + * + * @param b62 + * the Base62 String to decode. + * @return the decoded number as a long. + * @throws IllegalArgumentException + * if the given String contains characters not + * specified in the constructor. + */ + public long decodeBase62(String b62) { + for (char character : b62.toCharArray()) { + if (!characters.contains(String.valueOf(character))) { + throw new IllegalArgumentException("Invalid character(s) in string: " + character); + } + } + long ret = 0; + b62 = new StringBuffer(b62).reverse().toString(); + long count = 1; + for (char character : b62.toCharArray()) { + ret += characters.indexOf(character) * count; + count *= 62; + } + return ret; + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/utils/converter/CardDataUtils.java b/src/main/java/com/rbkmoney/adapter/common/utils/converter/CardDataUtils.java new file mode 100644 index 0000000..ddbb4e4 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/utils/converter/CardDataUtils.java @@ -0,0 +1,53 @@ +package com.rbkmoney.adapter.common.utils.converter; + +import com.rbkmoney.damsel.cds.ExpDate; +import com.rbkmoney.damsel.cds.SessionData; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Calendar; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CardDataUtils { + + public static String getYearFromExpDate(Short year) { + return String.format("%1$02d", year % 100); + } + + public static String getYearFromExpDate(ExpDate expDate) { + return getYearFromExpDate(expDate.getYear()); + } + + public static String getFullDateFromExpDate(ExpDate expDate) { + int correctYear = expDate.getYear() / 100 == 0 ? expDate.getYear() + 2000 : expDate.getYear(); + return String.format("%1$04d%2$02d%3$02d", correctYear, expDate.getMonth(), getDayOfMonth(expDate)); + } + + public static Integer getDayOfMonth(ExpDate expDate) { + Calendar calendar = Calendar.getInstance(); + calendar.set(expDate.getYear(), expDate.getMonth(), -1); + return calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + } + + public static String getMonthFromExpDate(Byte month) { + return String.format("%1$02d", month); + } + + public static String getMonthFromExpDate(ExpDate expDate) { + return String.format("%02d", expDate.getMonth()); + } + + public static String getMonthFromByte(byte month) { + return String.format("%02d", month); + } + + public static String getCvv2(SessionData sessionData) { + if (sessionData == null + || sessionData.getAuthData() == null + || !sessionData.getAuthData().isSetCardSecurityCode()) { + return null; + } + return sessionData.getAuthData().getCardSecurityCode().getValue(); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/utils/converter/PaymentDataConverterUtils.java b/src/main/java/com/rbkmoney/adapter/common/utils/converter/PaymentDataConverterUtils.java new file mode 100644 index 0000000..ac29ac3 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/utils/converter/PaymentDataConverterUtils.java @@ -0,0 +1,78 @@ +package com.rbkmoney.adapter.common.utils.converter; + +import com.rbkmoney.adapter.common.enums.TargetStatus; +import com.rbkmoney.damsel.domain.TargetInvoicePaymentStatus; +import com.rbkmoney.damsel.proxy_provider.PaymentContext; +import com.rbkmoney.damsel.proxy_provider.PaymentInfo; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.Random; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class PaymentDataConverterUtils { + + private static final String DEFAULT_ID = "1"; + + public static String makeHex16length() { + return String.format("%016X", new Random().nextLong()).toUpperCase(); + } + + public static BigDecimal getFormattedAmount(long amount) { + return new BigDecimal(amount).movePointLeft(2); + } + + public static String prepareOrder(String invoiceId) { + return prepareOrder(invoiceId, DEFAULT_ID); + } + + public static String prepareOrder(PaymentInfo paymentInfo) { + return prepareOrder(paymentInfo.getInvoice().getId(), paymentInfo.getPayment().getId()); + } + + public static String prepareOrder(String invoiceId, String paymentId) { + return Long.toString(new Base62().decodeBase62(invoiceId) ^ Integer.parseInt(paymentId)); + } + + public static String extractEmail(PaymentInfo paymentInfo) { + if (paymentInfo == null + || paymentInfo.getPayment() == null + || paymentInfo.getPayment().getContactInfo() == null + || paymentInfo.getPayment().getContactInfo().getEmail() == null) { + return ""; + } else { + return paymentInfo.getPayment().getContactInfo().getEmail(); + } + } + + public static TargetStatus getTargetStatus(PaymentContext paymentContext) { + if (paymentContext == null) { + throw new IllegalArgumentException("PaymentContext cannot be empty"); + } else if (paymentContext.getSession() == null) { + throw new IllegalArgumentException("Payment context session cannot be empty"); + } else { + return getTargetStatus(paymentContext.getSession().getTarget()); + } + } + + public static TargetStatus getTargetStatus(TargetInvoicePaymentStatus targetInvoicePaymentStatus) { + if (targetInvoicePaymentStatus == null) { + throw new IllegalArgumentException("Argument targetInvoicePaymentStatus cannot be empty"); + } + TargetStatus targetStatus; + if (targetInvoicePaymentStatus.isSetProcessed()) { + targetStatus = TargetStatus.PROCESSED; + } else if (targetInvoicePaymentStatus.isSetCancelled()) { + targetStatus = TargetStatus.CANCELLED; + } else if (targetInvoicePaymentStatus.isSetCaptured()) { + targetStatus = TargetStatus.CAPTURED; + } else if (targetInvoicePaymentStatus.isSetRefunded()) { + targetStatus = TargetStatus.REFUNDED; + } else { + throw new IllegalStateException("Unknown target status: " + targetInvoicePaymentStatus); + } + return targetStatus; + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/common/utils/converter/RedirectUtils.java b/src/main/java/com/rbkmoney/adapter/common/utils/converter/RedirectUtils.java new file mode 100644 index 0000000..bb806f3 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/utils/converter/RedirectUtils.java @@ -0,0 +1,15 @@ +package com.rbkmoney.adapter.common.utils.converter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +public class RedirectUtils { + + public static String getCallbackUrl(String callbackUrl, String path) { + return UriComponentsBuilder.fromUriString(callbackUrl) + .path(path) + .build() + .toUriString(); + } +} diff --git a/src/main/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryption.java b/src/main/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryption.java new file mode 100644 index 0000000..d3151a7 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryption.java @@ -0,0 +1,74 @@ +package com.rbkmoney.adapter.common.utils.encryption; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.codec.binary.Hex; +import org.springframework.util.MultiValueMap; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class HmacEncryption { + + public static final String HMAC_SHA1 = "HmacSHA1"; + + public static final String HMAC_SHA256 = "HmacSHA256"; + + public static String calculateHMacSha1(String data, String key) { + return calculateHMAC(data, key, HMAC_SHA1); + } + + public static String calculateHMacSha256(String data, String key) { + return calculateHMAC(data, key, HMAC_SHA256); + } + + public static String calculateHMAC(String data, String key, String algorithm) { + + try { + byte[] decodedKey = Hex.decodeHex(key.toCharArray()); + SecretKeySpec keySpec = new SecretKeySpec(decodedKey, algorithm); + Mac mac = Mac.getInstance(algorithm); + mac.init(keySpec); + + byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = mac.doFinal(dataBytes); + + return new String(new Hex().encode(signatureBytes)); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static String prepareDataForHmac(String[] fields, MultiValueMap params) { + StringBuilder dataHmac = new StringBuilder(); + Arrays.asList(fields) + .forEach(field -> { + if (params.get(field) != null + && !params.get(field).isEmpty() + && params.get(field).get(0) != null + && !params.get(field).get(0).isEmpty() + ) { + dataHmac.append(params.get(field).get(0).length()); + dataHmac.append(params.get(field).get(0)); + } else { + dataHmac.append("-"); + } + } + ); + + return dataHmac.toString(); + } + + public static String sign(String[] fieldsForSign, + MultiValueMap params, + String key, + String algorithm) { + String dataHmac = prepareDataForHmac(fieldsForSign, params); + String pSign = calculateHMAC(dataHmac, key, algorithm); + return pSign.toUpperCase(); + } + +} diff --git a/src/test/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryptionTest.java b/src/test/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryptionTest.java new file mode 100644 index 0000000..513af2e --- /dev/null +++ b/src/test/java/com/rbkmoney/adapter/common/utils/encryption/HmacEncryptionTest.java @@ -0,0 +1,22 @@ +package com.rbkmoney.adapter.common.utils.encryption; + +import org.junit.Test; + +import static com.rbkmoney.adapter.common.utils.encryption.HmacEncryption.calculateHMacSha256; +import static org.junit.Assert.assertEquals; + +public class HmacEncryptionTest { + + private String message = "511.483USD677144616IT Books. Qty: 217Books Online Inc." + + "14www.sample.com1512345678901234589999999919pgw@mail.sample.com11--" + + "142003010515302116F2B2DD7E603A7ADA33https://www.sample.com/shop/reply"; + private String secret = "00112233445566778899AABBCCDDEEFF"; + + @Test + public void testHMacSha256() { + String expectedHMac = "3fd9ee801d3aeda5d33af83580279ef920ad78e8883a0b9bc3942c74129db2f0"; + String hmac = calculateHMacSha256(message, secret); + assertEquals("HMAC SHA256 is not equal expected", expectedHMac, hmac); + } + +}