mirror of
https://github.com/valitydev/samsungpay-provider.git
synced 2024-11-06 00:05:22 +00:00
Initial build (#1)
* SP-2: Initial implementation * SP-2: Create trans cmd fixes * SP-2: Working prototype * Add unwrap payment tool result * Clean Readme, rename project in pom, add idea in .gitignore * Fix with logback appenders, add description
This commit is contained in:
parent
065d336b77
commit
c3e1a35bd5
120
.gitignore
vendored
Normal file
120
.gitignore
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Maven template
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
|
||||
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
|
||||
!/.mvn/wrapper/maven-wrapper.jar
|
||||
### Java template
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
### JetBrains template
|
||||
# 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/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
47
Jenkinsfile
vendored
Normal file
47
Jenkinsfile
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
#!groovy
|
||||
build('samsungpay-provider', 'java-maven') {
|
||||
checkoutRepo()
|
||||
|
||||
def serviceName = env.REPO_NAME
|
||||
def mvnArgs = '-DjvmArgs="-Xmx256m"'
|
||||
|
||||
// Run mvn and generate docker file
|
||||
runStage('Maven package') {
|
||||
withCredentials([[$class: 'FileBinding', credentialsId: 'java-maven-settings.xml', variable: 'SETTINGS_XML']]) {
|
||||
def mvn_command_arguments = ' --batch-mode --settings $SETTINGS_XML -P ci ' +
|
||||
" -Dgit.branch=${env.BRANCH_NAME} " +
|
||||
" ${mvnArgs}"
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
sh 'mvn deploy' + mvn_command_arguments
|
||||
} else {
|
||||
sh 'mvn package' + mvn_command_arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def serviceImage;
|
||||
def imgShortName = 'rbkmoney/' + "${serviceName}" + ':' + '$COMMIT_ID';
|
||||
getCommitId()
|
||||
runStage('Build Service image') {
|
||||
serviceImage = docker.build(imgShortName, '-f ./target/Dockerfile ./target')
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME.startsWith('epic')) {
|
||||
runStage('Push Service image') {
|
||||
docker.withRegistry('https://dr.rbkmoney.com/v2/', 'dockerhub-rbkmoneycibot') {
|
||||
serviceImage.push();
|
||||
}
|
||||
// Push under 'withRegistry' generates 2d record with 'long name' in local docker registry.
|
||||
// Untag the long-name
|
||||
sh "docker rmi dr.rbkmoney.com/${imgShortName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
runStage('Remove local image') {
|
||||
// Remove the image to keep Jenkins runner clean.
|
||||
sh "docker rmi ${imgShortName}"
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
# samsungpay-provider
|
||||
# samsungpay-provider
|
||||
Payment tool provider implementation for samsung pay
|
211
pom.xml
Normal file
211
pom.xml
Normal file
@ -0,0 +1,211 @@
|
||||
<?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>
|
||||
<packaging>jar</packaging>
|
||||
<name>Samsung pay</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.rbkmoney.provider</groupId>
|
||||
<artifactId>samsungpay</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.maintainer>Vladimir Pankrashkin <v.pankrashkin@rbkmoney.com></project.maintainer>
|
||||
<server.port>8022</server.port>
|
||||
<server.rest_port>8080</server.rest_port>
|
||||
<exposed.ports>${server.port} ${server.rest_port}</exposed.ports>
|
||||
<dockerfile.base.service.tag>22c57470c4fc47161894f036b7cf9d70f42b75f5</dockerfile.base.service.tag>
|
||||
<damsel.version>1.232-5e54b26</damsel.version>
|
||||
<shared.resources.version>0.3.1</shared.resources.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--RBK libs-->
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.woody</groupId>
|
||||
<artifactId>woody-thrift</artifactId>
|
||||
<version>1.1.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.logback</groupId>
|
||||
<artifactId>nop-rolling</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.geck</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.6.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>shared-resources</artifactId>
|
||||
<version>${shared.resources.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>damsel</artifactId>
|
||||
<version>${damsel.version}</version>
|
||||
</dependency>
|
||||
<!--Thirdparty libs-->
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.4</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.9.1</version>
|
||||
</dependency>
|
||||
<!--Test libs-->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>1.3.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||
<targetPath>${project.build.directory}</targetPath>
|
||||
<includes>
|
||||
<include>Dockerfile</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>Dockerfile</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-remote-resources-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-filtering</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<resourceBundles>
|
||||
<resourceBundle>com.rbkmoney:shared-resources:${shared.resources.version}</resourceBundle>
|
||||
</resourceBundles>
|
||||
<attachToMain>false</attachToMain>
|
||||
<attachToTest>false</attachToTest>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>process</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>-Dfile.encoding=${project.build.sourceEncoding}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,16 @@
|
||||
package com.rbkmoney.provider.samsungpay;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.07.18.
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = "com.rbkmoney.provider.samsungpay")
|
||||
@ServletComponentScan
|
||||
public class SamsungPayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SamsungPayApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package com.rbkmoney.provider.samsungpay.config;
|
||||
|
||||
import com.rbkmoney.damsel.payment_tool_provider.PaymentToolProviderSrv;
|
||||
import com.rbkmoney.provider.samsungpay.iface.decrypt.ProviderHandler;
|
||||
import com.rbkmoney.provider.samsungpay.service.SPayClient;
|
||||
import com.rbkmoney.provider.samsungpay.service.SPayService;
|
||||
import com.rbkmoney.provider.samsungpay.store.SPKeyStore;
|
||||
import com.rbkmoney.woody.api.flow.WFlow;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.07.18.
|
||||
*/
|
||||
@Configuration
|
||||
public class ApplicationConfig {
|
||||
@Bean
|
||||
public SPayClient transactionClient(
|
||||
@Value("${samsung.trans_url_template}") String transactionURLTemplate,
|
||||
@Value("${samsung.cred_url_template}") String credentialsURLTemplate,
|
||||
@Value("${samsung.conn_timeout_ms}") int connTimeoutMs,
|
||||
@Value("${samsung.read_timeout_ms}") int readTimeoutMs,
|
||||
@Value("${samsung.write_timeout_ms}") int writeTimeoutMs) {
|
||||
return new SPayClient(transactionURLTemplate, credentialsURLTemplate, connTimeoutMs, readTimeoutMs, writeTimeoutMs);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SPKeyStore keyStore(@Value("${keys_path}") String keysPath) {
|
||||
return new SPKeyStore(keysPath);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SPayService transactionService(SPayClient SPayClient, SPKeyStore spKeyStore) {
|
||||
return new SPayService(SPayClient, spKeyStore);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PaymentToolProviderSrv.Iface providerHandler(SPayService sPayService) {
|
||||
return new ProviderHandler(sPayService);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public ServletWebServerFactory servletContainer(@Value("${server.rest_port}") int httpPort) {
|
||||
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
|
||||
Connector connector = new Connector();
|
||||
connector.setPort(httpPort);
|
||||
tomcat.addAdditionalTomcatConnectors(connector);
|
||||
return tomcat;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean externalPortRestrictingFilter(@Value("${server.rest_port}") int restPort, @Value("/${server.rest_path_prefix}/") String httpPathPrefix) {
|
||||
Filter filter = new OncePerRequestFilter() {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
if (request.getLocalPort() == restPort) {
|
||||
if (!(request.getServletPath().startsWith(httpPathPrefix) || request.getServletPath().startsWith("/actuator/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;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean woodyFilter(@Value("${server.rest_port}") int restPort, @Value("/${server.rest_path_prefix}/") String httpPathPrefix) {
|
||||
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) {
|
||||
if (request.getServletPath().startsWith(httpPathPrefix)) {
|
||||
wFlow.createServiceFork(() -> {
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (IOException | ServletException e) {
|
||||
sneakyThrow(e);
|
||||
}
|
||||
}).run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private <E extends Throwable, T> T sneakyThrow(Throwable t) throws E {
|
||||
throw (E) t;
|
||||
}
|
||||
};
|
||||
|
||||
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
|
||||
filterRegistrationBean.setFilter(filter);
|
||||
filterRegistrationBean.setOrder(-50);
|
||||
filterRegistrationBean.setName("woodyFilter");
|
||||
filterRegistrationBean.addUrlPatterns(httpPathPrefix+"*");
|
||||
return filterRegistrationBean;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.rbkmoney.provider.samsungpay.config;
|
||||
|
||||
import com.rbkmoney.provider.samsungpay.iface.transaction.DumbRequestTransactionController;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.04.18.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
@Bean
|
||||
public Docket api() {
|
||||
Docket docket = new Docket(DocumentationType.SWAGGER_2)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(DumbRequestTransactionController.class.getPackage().getName()))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
docket.produces(new HashSet(){{add("application/json");}});
|
||||
docket.forCodeGeneration(true);
|
||||
return docket;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public enum AuthMethod {
|
||||
@JsonProperty("3DS")
|
||||
Auth3DS
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.rbkmoney.damsel.domain.BankCardPaymentSystem;
|
||||
|
||||
public enum CardBrand {
|
||||
VISA("VI", BankCardPaymentSystem.visa),
|
||||
MASTERCARD("MC", BankCardPaymentSystem.mastercard);
|
||||
|
||||
private String id;
|
||||
|
||||
private BankCardPaymentSystem paymentSystem;
|
||||
|
||||
CardBrand(String id, BankCardPaymentSystem paymentSystem) {
|
||||
this.id = id;
|
||||
this.paymentSystem = paymentSystem;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public BankCardPaymentSystem getPaymentSystem() {
|
||||
return paymentSystem;
|
||||
}
|
||||
|
||||
public static BankCardPaymentSystem findPaymentSystemById(String id) {
|
||||
for (CardBrand cardBrand : values()) {
|
||||
if (cardBrand.getId().equalsIgnoreCase(id)) {
|
||||
return cardBrand.getPaymentSystem();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public class Certificate {
|
||||
public String usage;
|
||||
public String alias;
|
||||
public String content;
|
||||
|
||||
@JsonCreator
|
||||
public Certificate(
|
||||
@JsonProperty(value = "usage", required = true) String usage,
|
||||
@JsonProperty(value = "alias", required = true) String alias,
|
||||
@JsonProperty(value = "content", required = true) String content) {
|
||||
this.usage = usage;
|
||||
this.alias = alias;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public class CredentialsResponse {
|
||||
public String deviceId;
|
||||
public AuthMethod authMethod;
|
||||
public String last4digits;
|
||||
public String cardBrand;
|
||||
public Data3DS data3DS;
|
||||
public Certificate[] certificates;
|
||||
|
||||
@JsonCreator
|
||||
public CredentialsResponse(
|
||||
@JsonProperty(value = "wallet_dm_id") String deviceId,
|
||||
@JsonProperty(value = "method", required = true) AuthMethod authMethod,
|
||||
@JsonProperty(value = "card_last4digits") String last4digits,
|
||||
@JsonProperty(value = "card_brand") String cardBrand,
|
||||
@JsonProperty(value = "3DS") Data3DS data3DS,
|
||||
@JsonProperty(value = "certificates", required = true) Certificate[] certificates) {
|
||||
this.deviceId = deviceId;
|
||||
this.authMethod = authMethod;
|
||||
this.last4digits = last4digits;
|
||||
this.cardBrand = cardBrand;
|
||||
this.data3DS = data3DS;
|
||||
this.certificates = certificates;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public class Data3DS {
|
||||
@JsonProperty(value = "type", required = true)
|
||||
public String type;
|
||||
|
||||
@JsonProperty(value = "version", required = true)
|
||||
public String version;
|
||||
|
||||
@JsonProperty(value = "data", required = true)
|
||||
public String data;
|
||||
|
||||
@JsonCreator
|
||||
public Data3DS(
|
||||
@JsonProperty(value = "type", required = true) String type,
|
||||
@JsonProperty(value = "version", required = true) String version,
|
||||
@JsonProperty(value = "data", required = true) String data
|
||||
) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.rbkmoney.provider.samsungpay.service.ExpDateDeserialiser;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public class PData3DS {
|
||||
public long amount;
|
||||
public String currencyCode;
|
||||
public String created;
|
||||
public String eci;
|
||||
public String dpan;
|
||||
public LocalDate expirationDate;
|
||||
public String cryptogram;
|
||||
public String cardholder;
|
||||
|
||||
@JsonCreator
|
||||
public PData3DS(
|
||||
@JsonProperty(value = "amount", required = true) long amount,
|
||||
@JsonProperty(value = "currency_code", required = true) String currencyCode,
|
||||
@JsonProperty(value = "utc", required = true) String created,
|
||||
@JsonProperty(value = "eci_indicator") String eci,
|
||||
@JsonProperty(value = "tokenPAN", required = true) String dpan,
|
||||
@JsonDeserialize(using = ExpDateDeserialiser.class)
|
||||
@JsonProperty(value = "tokenPanExpiration", required = true) LocalDate expirationDate,
|
||||
@JsonProperty(value = "cryptogram", required = true) String cryptogram,
|
||||
@JsonProperty(value = "cardholder_name") String cardholder) {
|
||||
this.amount = amount;
|
||||
this.currencyCode = currencyCode;
|
||||
this.created = created;
|
||||
this.eci = eci;
|
||||
this.dpan = dpan;
|
||||
this.expirationDate = expirationDate;
|
||||
this.cryptogram = cryptogram;
|
||||
this.cardholder = cardholder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PData3DS{" +
|
||||
"amount=" + amount +
|
||||
", currencyCode='" + currencyCode + '\'' +
|
||||
", created='" + created + '\'' +
|
||||
", eci='" + eci + '\'' +
|
||||
", dpan='" + (dpan == null ? null : "***") + '\'' +
|
||||
", expirationDate=" + (expirationDate == null ? null : "***") +
|
||||
", cryptogram='" + (cryptogram == null ? null : "***") + '\'' +
|
||||
", cardholder='" + (cardholder == null ? amount : "***") + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.rbkmoney.provider.samsungpay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ResultStatus {
|
||||
public String code;
|
||||
public String description;
|
||||
|
||||
@JsonCreator
|
||||
public ResultStatus(
|
||||
@JsonProperty(value = "resultCode", required = true) String code,
|
||||
@JsonProperty(value = "resultMessage", required = true) String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResultStatus{" +
|
||||
"code='" + code + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.rbkmoney.provider.samsungpay.iface.decrypt;
|
||||
|
||||
import com.rbkmoney.damsel.base.InvalidRequest;
|
||||
import com.rbkmoney.damsel.payment_tool_provider.*;
|
||||
import com.rbkmoney.provider.samsungpay.domain.CardBrand;
|
||||
import com.rbkmoney.provider.samsungpay.domain.CredentialsResponse;
|
||||
import com.rbkmoney.provider.samsungpay.domain.PData3DS;
|
||||
import com.rbkmoney.provider.samsungpay.service.SPayService;
|
||||
import org.apache.thrift.TException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.07.18.
|
||||
*/
|
||||
public class ProviderHandler implements PaymentToolProviderSrv.Iface {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final SPayService service;
|
||||
|
||||
public ProviderHandler(SPayService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwrappedPaymentTool unwrap(WrappedPaymentTool paymentTool) throws InvalidRequest, TException {
|
||||
log.info("New unwrap request: {}", paymentTool);
|
||||
if (!paymentTool.getRequest().isSetSamsung()) {
|
||||
throw new InvalidRequest(Arrays.asList("Received request type is not SamsungPay"));
|
||||
}
|
||||
String refId = paymentTool.getRequest().getSamsung().getReferenceId();
|
||||
String srvId = paymentTool.getRequest().getSamsung().getServiceId();
|
||||
try {
|
||||
Map.Entry<CredentialsResponse, PData3DS> responseWithPData3DS = service.getCredentials(srvId, refId);
|
||||
|
||||
CredentialsResponse credentialsResponse = responseWithPData3DS.getKey();
|
||||
PData3DS pData = responseWithPData3DS.getValue();
|
||||
log.info("Payment data decrypted: {}", pData);
|
||||
|
||||
UnwrappedPaymentTool result = new UnwrappedPaymentTool();
|
||||
SamsungPayDetails samsungPayDetails = new SamsungPayDetails();
|
||||
samsungPayDetails.setDeviceId(credentialsResponse.deviceId);
|
||||
result.setDetails(PaymentDetails.samsung(samsungPayDetails));
|
||||
|
||||
CardPaymentData cardPaymentData = new CardPaymentData();
|
||||
TokenizedCard tokenizedCard = new TokenizedCard();
|
||||
tokenizedCard.setDpan(pData.dpan);
|
||||
ExpDate expDate = new ExpDate();
|
||||
expDate.setMonth((byte) pData.expirationDate.getMonth().getValue());
|
||||
expDate.setYear((short) pData.expirationDate.getYear());
|
||||
tokenizedCard.setExpDate(expDate);
|
||||
AuthData authData = new AuthData();
|
||||
Auth3DS auth3DS = new Auth3DS();
|
||||
auth3DS.setCryptogram(pData.cryptogram);
|
||||
auth3DS.setEci(pData.eci);
|
||||
authData.setAuth3ds(auth3DS);
|
||||
tokenizedCard.setAuthData(authData);
|
||||
cardPaymentData.setTokenizedCard(tokenizedCard);
|
||||
result.setPaymentData(cardPaymentData);
|
||||
|
||||
CardInfo cardInfo = new CardInfo();
|
||||
// cardInfo.setCardClass(); //?
|
||||
// cardInfo.setDisplayName(); //?
|
||||
cardInfo.setLast4Digits(credentialsResponse.last4digits);
|
||||
cardInfo.setPaymentSystem(CardBrand.findPaymentSystemById(credentialsResponse.cardBrand));
|
||||
cardInfo.setCardholderName(pData.cardholder);
|
||||
result.setCardInfo(cardInfo);
|
||||
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
//log.error("Failed to read json data: {}", filterPan(e.getMessage()));
|
||||
throw new InvalidRequest(Arrays.asList("Failed to read json data"));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get credentials", e);
|
||||
throw new InvalidRequest(Arrays.asList(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.rbkmoney.provider.samsungpay.iface.decrypt;
|
||||
|
||||
import com.rbkmoney.damsel.payment_tool_provider.PaymentToolProviderSrv;
|
||||
import com.rbkmoney.woody.thrift.impl.http.THServiceBuilder;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 18.04.18.
|
||||
*/
|
||||
@WebServlet("/provider/samsung")
|
||||
public class ProviderServlet extends GenericServlet {
|
||||
private final Servlet handlerServlet;
|
||||
|
||||
public ProviderServlet(PaymentToolProviderSrv.Iface handler) {
|
||||
this.handlerServlet = new THServiceBuilder()
|
||||
.build(PaymentToolProviderSrv.Iface.class, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
|
||||
handlerServlet.service(req, res);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.rbkmoney.provider.samsungpay.iface.transaction;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.provider.samsungpay.service.SPayService;
|
||||
import com.rbkmoney.woody.api.flow.error.WErrorType;
|
||||
import com.rbkmoney.woody.api.flow.error.WRuntimeException;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.04.18.
|
||||
*/
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
@Api(description = "Transaction creation API")
|
||||
public class DumbRequestTransactionController {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private SPayService service;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
|
||||
@ApiOperation(value = "Request SamsungPay transaction", notes = "")
|
||||
@PostMapping(value = "/transaction", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, headers = "Content-Type=application/json")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code= 200, message = "Samsung Pay session object"),
|
||||
@ApiResponse(code = 500, message = "Internal service error"),
|
||||
@ApiResponse(code = 503, message = "Samsung Pay service unavailable")
|
||||
})
|
||||
@CrossOrigin
|
||||
|
||||
public ResponseEntity<String> getTransaction(@RequestBody Map<String, Object> request) {
|
||||
log.info("New Transaction request: {}", request);
|
||||
|
||||
try {
|
||||
return ResponseEntity.ok(service.createTransaction(mapper.writeValueAsString(request)));
|
||||
} catch (WRuntimeException e) {
|
||||
WErrorType errorType = e.getErrorDefinition().getErrorType();
|
||||
if (errorType == WErrorType.UNDEFINED_RESULT || errorType == WErrorType.UNAVAILABLE_RESULT) {
|
||||
log.warn("Samsung pay service unavailable", e);
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Third party service unavailable");
|
||||
} else {
|
||||
log.error("Failed to request transaction", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to request transaction");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to request transaction", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to request transaction");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 05.07.18.
|
||||
*/
|
||||
public class Decryptor {
|
||||
public static String getDecryptedData(String encPayload, PKCS8EncodedKeySpec prvKeySpec) throws Exception {
|
||||
String delims = "[.]";
|
||||
String[] tokens = encPayload.split(delims);
|
||||
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
|
||||
byte[] encKey = urlDecoder.decode(tokens[1]);
|
||||
byte[] iv = urlDecoder.decode(tokens[2]);
|
||||
byte[] cipherText = urlDecoder.decode(tokens[3]);
|
||||
byte[] tag = urlDecoder.decode(tokens[4]);
|
||||
byte[] plainText = new byte[cipherText.length];
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey privKey = keyFactory.generatePrivate(prvKeySpec);
|
||||
Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, privKey);
|
||||
byte[] plainEncKey = decryptCipher.doFinal(encKey);
|
||||
final Cipher aes128Cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * Byte.SIZE, iv);
|
||||
final SecretKeySpec keySpec = new SecretKeySpec(plainEncKey, "AES");
|
||||
aes128Cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
|
||||
int offset = aes128Cipher.update(cipherText, 0, cipherText.length, plainText, 0);
|
||||
aes128Cipher.update(tag, 0, tag.length, plainText, offset);
|
||||
aes128Cipher.doFinal(plainText, offset);
|
||||
return new String(plainText);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 09.07.18.
|
||||
*/
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.ObjectCodec;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class ExpDateDeserialiser extends JsonDeserializer<LocalDate> {
|
||||
private final DateTimeFormatter fullDateFormatter = DateTimeFormatter.ofPattern("yyMMdd");
|
||||
private final DateTimeFormatter shortDateFormatter = DateTimeFormatter.ofPattern("MMyy");
|
||||
|
||||
@Override
|
||||
public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
ObjectCodec oc = jp.getCodec();
|
||||
JsonNode node = oc.readTree(jp);
|
||||
String value = node.asText();
|
||||
if (value.length() > 4) {
|
||||
return LocalDate.from(fullDateFormatter.parse(value));
|
||||
} else {
|
||||
return LocalDate.from(shortDateFormatter.parse(value));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
import okhttp3.*;
|
||||
import okhttp3.internal.http.HttpHeaders;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor which logs request and response information. Can be applied as an
|
||||
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
|
||||
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
|
||||
* this class should not be considered stable and may change slightly between releases. If you need
|
||||
* a stable logging format, use your own interceptor.
|
||||
*/
|
||||
public final class HttpLoggingInterceptor implements Interceptor {
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
private static final org.slf4j.Logger log = LoggerFactory.getLogger(HttpLoggingInterceptor.class);
|
||||
|
||||
public enum Level {
|
||||
/**
|
||||
* No logs.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Logs request and response lines.
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1 (3-byte body)
|
||||
*
|
||||
* <-- 200 OK (22ms, 6-byte body)
|
||||
* }</pre>
|
||||
*/
|
||||
BASIC,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers.
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
HEADERS,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers and bodies (if present).
|
||||
* <p>
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
*
|
||||
* Hi?
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
*
|
||||
* Hello!
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
BODY
|
||||
}
|
||||
|
||||
public interface Logger {
|
||||
void log(String message);
|
||||
|
||||
/**
|
||||
* A {@link Logger} defaults output appropriate for the current platform.
|
||||
*/
|
||||
Logger DEFAULT = message -> log.info(message);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor() {
|
||||
this(Logger.DEFAULT);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private volatile Level level = Level.BODY;
|
||||
|
||||
/**
|
||||
* Change the level at which this interceptor logs.
|
||||
*/
|
||||
public HttpLoggingInterceptor setLevel(Level level) {
|
||||
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Level level = this.level;
|
||||
|
||||
Request request = chain.request();
|
||||
if (level == Level.NONE) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
|
||||
boolean logBody = level == Level.BODY;
|
||||
boolean logHeaders = logBody || level == Level.HEADERS;
|
||||
|
||||
RequestBody requestBody = request.body();
|
||||
boolean hasRequestBody = requestBody != null;
|
||||
|
||||
Connection connection = chain.connection();
|
||||
StringBuilder message = new StringBuilder("--> \n")
|
||||
.append(request.method())
|
||||
.append(' ').append(request.url())
|
||||
.append(connection != null ? " " + connection.protocol() : "").append('\n');
|
||||
if (!logHeaders && hasRequestBody) {
|
||||
message.append(" (").append(requestBody.contentLength()).append("-byte body)").append('\n');
|
||||
}
|
||||
//logger.log(requestStartMessage);
|
||||
|
||||
if (logHeaders) {
|
||||
if (hasRequestBody) {
|
||||
// Request body headers are only present when installed as a network interceptor. Force
|
||||
// them to be included (when available) so there values are known.
|
||||
if (requestBody.contentType() != null) {
|
||||
message.append("Content-Type: ").append(requestBody.contentType()).append('\n');
|
||||
}
|
||||
if (requestBody.contentLength() != -1) {
|
||||
message.append("Content-Length: ").append(requestBody.contentLength()).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Headers headers = request.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
String name = headers.name(i);
|
||||
// Skip headers from the request body as they are explicitly logged above.
|
||||
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
|
||||
if (!"Authorization".equalsIgnoreCase(name)) {
|
||||
message.append(name).append(": ").append(headers.value(i)).append('\n');
|
||||
} else {
|
||||
message.append(name).append(": ***").append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!logBody || !hasRequestBody) {
|
||||
message.append("\n--> END ").append(request.method());
|
||||
} else if (bodyEncoded(request.headers())) {
|
||||
message.append("\n--> END ").append(request.method()).append(" (encoded body omitted)");
|
||||
} else {
|
||||
message.append('\n');
|
||||
Buffer buffer = new Buffer();
|
||||
requestBody.writeTo(buffer);
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = requestBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (isPlaintext(buffer)) {
|
||||
message.append(buffer.readString(charset));
|
||||
message.append("\n--> END ").append(request.method())
|
||||
.append(" (").append(requestBody.contentLength()).append("-byte body)");
|
||||
} else {
|
||||
message.append("\n--> END ").append(request.method()).append(" (binary ")
|
||||
.append(requestBody.contentLength()).append("-byte body omitted)");
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.log(message.toString());
|
||||
long startNs = System.nanoTime();
|
||||
Response response;
|
||||
try {
|
||||
response = chain.proceed(request);
|
||||
} catch (Exception e) {
|
||||
logger.log("<-- HTTP FAILED: " + e);
|
||||
throw e;
|
||||
}
|
||||
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
long contentLength = responseBody.contentLength();
|
||||
message = new StringBuilder();
|
||||
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
|
||||
message.append("<-- \n")
|
||||
.append(response.code())
|
||||
.append(response.message().isEmpty() ? "" : " " + response.message())
|
||||
.append(' ').append(response.request().url())
|
||||
.append(" (").append(tookMs).append("ms").append((!logHeaders ? ", " + bodySize + " body" : "") + ')').append('\n');
|
||||
|
||||
if (logHeaders) {
|
||||
Headers headers = response.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
message.append(headers.name(i)).append(": ").append(headers.value(i)).append('\n');
|
||||
}
|
||||
|
||||
if (!logBody || !HttpHeaders.hasBody(response)) {
|
||||
message.append("\n<-- END HTTP");
|
||||
} else if (bodyEncoded(response.headers())) {
|
||||
message.append("\n<-- END HTTP (encoded body omitted)");
|
||||
} else {
|
||||
message.append('\n');
|
||||
BufferedSource source = responseBody.source();
|
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.buffer();
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = responseBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (!isPlaintext(buffer)) {
|
||||
message.append("\n<-- END HTTP (binary ").append(buffer.size()).append("-byte body omitted)");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (contentLength != 0) {
|
||||
message.append(buffer.clone().readString(charset));
|
||||
}
|
||||
|
||||
logger.log(message.append("\n<-- END HTTP (").append(buffer.size()).append("-byte body)").toString());
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the body in question probably contains human readable text. Uses a small sample
|
||||
* of code points to detect unicode control characters commonly used in binary file signatures.
|
||||
*/
|
||||
static boolean isPlaintext(Buffer buffer) {
|
||||
try {
|
||||
Buffer prefix = new Buffer();
|
||||
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
|
||||
buffer.copyTo(prefix, 0, byteCount);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (prefix.exhausted()) {
|
||||
break;
|
||||
}
|
||||
int codePoint = prefix.readUtf8CodePoint();
|
||||
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
return false; // Truncated UTF-8 sequence.
|
||||
}
|
||||
}
|
||||
|
||||
private boolean bodyEncoded(Headers headers) {
|
||||
String contentEncoding = headers.get("Content-Encoding");
|
||||
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 12.04.18.
|
||||
*/
|
||||
public class SPException extends Exception {
|
||||
private String payload;
|
||||
|
||||
public SPException() {
|
||||
}
|
||||
|
||||
public SPException(String message, String payload) {
|
||||
super(message);
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public SPException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SPException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SPException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SPException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
import com.rbkmoney.woody.api.flow.error.WErrorDefinition;
|
||||
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 okhttp3.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 26.06.18.
|
||||
*/
|
||||
public class SPayClient {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final int connTimeoutMs;
|
||||
private final int readTimeoutMs;
|
||||
private final int writeTimeoutMs;
|
||||
private final THTransportErrorMapper errorMapper;
|
||||
private final UriTemplate transactionTemplate;
|
||||
private final UriTemplate credentialsTemplate;
|
||||
|
||||
public SPayClient(String transactionURLTemplate, String credentialsURLTemplate, int connTimeoutMs, int readTimeoutMs, int writeTimeoutMs) {
|
||||
this.connTimeoutMs = connTimeoutMs;
|
||||
this.readTimeoutMs = readTimeoutMs;
|
||||
this.writeTimeoutMs = writeTimeoutMs;
|
||||
this.transactionTemplate = new UriTemplate(transactionURLTemplate);
|
||||
this.credentialsTemplate = new UriTemplate(credentialsURLTemplate);
|
||||
this.errorMapper = new THTransportErrorMapper();
|
||||
}
|
||||
|
||||
public String requestTransaction(String body) throws SPException {
|
||||
log.info("Create transaction with: {}", body);
|
||||
try {
|
||||
OkHttpClient client = prepareClient();
|
||||
log.debug("Http client prepared");
|
||||
Request request = preparePostRequest(transactionTemplate.expand(Collections.emptyMap()).toURL(), body);
|
||||
Response response = client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
log.warn("Unsuccessful call result");
|
||||
}
|
||||
String result = response.body().string();
|
||||
log.info("Create transaction result: {}", result);
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
WErrorDefinition errDef = errorMapper.mapToDef(e, TraceContext.getCurrentTraceData().getActiveSpan());
|
||||
if (errDef != null) {
|
||||
throw new WRuntimeException(e, errDef);
|
||||
} else {
|
||||
throw new WRuntimeException(e, new WErrorDefinition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String requestCredentials(String serviceId, String refId) throws Exception {
|
||||
log.info("Get credentials for srv:{}, ref:{}", serviceId, refId);
|
||||
try {
|
||||
OkHttpClient client = prepareClient();
|
||||
log.debug("Http client prepared");
|
||||
Request request = prepareGetRequest(credentialsTemplate.expand(new HashMap() {{
|
||||
put("id", refId);
|
||||
put("serviceId", serviceId);
|
||||
}}).toURL());
|
||||
Response response = client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new SPException("Unsuccessful call result", response.body().string());
|
||||
}
|
||||
String result = response.body().string();
|
||||
log.info("Credentials result: {}", result);
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
WErrorDefinition errDef = errorMapper.mapToDef(e, TraceContext.getCurrentTraceData().getActiveSpan());
|
||||
if (errDef != null) {
|
||||
throw new WRuntimeException(e, errDef);
|
||||
} else {
|
||||
throw new WRuntimeException(e, new WErrorDefinition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OkHttpClient prepareClient() {
|
||||
return new OkHttpClient.Builder()
|
||||
.connectTimeout(connTimeoutMs, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(writeTimeoutMs, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS)
|
||||
.addNetworkInterceptor(c ->
|
||||
c.proceed(
|
||||
c.request().newBuilder()
|
||||
.addHeader("X-Request-Id", TraceContext.getCurrentTraceData().getActiveSpan().getSpan().getTraceId())
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.addInterceptor(new HttpLoggingInterceptor()).build();
|
||||
}
|
||||
|
||||
private Request preparePostRequest(URL url, String body) {
|
||||
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), body);
|
||||
return new Request.Builder().url(url).method("POST", requestBody).build();
|
||||
}
|
||||
|
||||
private Request prepareGetRequest(URL url) {
|
||||
return new Request.Builder().url(url).method("GET", null).build();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.rbkmoney.provider.samsungpay.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rbkmoney.provider.samsungpay.domain.CredentialsResponse;
|
||||
import com.rbkmoney.provider.samsungpay.domain.PData3DS;
|
||||
import com.rbkmoney.provider.samsungpay.domain.ResultStatus;
|
||||
import com.rbkmoney.provider.samsungpay.store.SPKeyStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 03.07.18.
|
||||
*/
|
||||
public class SPayService {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final SPayClient sPayClient;
|
||||
private final SPKeyStore keyStore;
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public SPayService(SPayClient SPayClient, SPKeyStore keyStore) {
|
||||
this.sPayClient = SPayClient;
|
||||
this.keyStore = keyStore;
|
||||
}
|
||||
|
||||
public String createTransaction(String reqBody) throws Exception {
|
||||
String respBody = sPayClient.requestTransaction(reqBody);
|
||||
ResultStatus status = mapper.readValue(respBody, ResultStatus.class);
|
||||
if (!"0".equals(status.code)) {
|
||||
log.warn("Unsuccessful SP response code:{}", status);
|
||||
}
|
||||
return respBody;
|
||||
}
|
||||
|
||||
public Map.Entry<CredentialsResponse, PData3DS> getCredentials(String serviceId, String refId) throws Exception {
|
||||
log.info("Get key for service: {}", serviceId);
|
||||
PKCS8EncodedKeySpec keySpec = keyStore.getKey(serviceId);
|
||||
if (keySpec == null) {
|
||||
log.error("Unknown service id: {}", serviceId);
|
||||
throw new SPException("Not found key for service: " + serviceId);
|
||||
}
|
||||
String respBody = sPayClient.requestCredentials(serviceId, refId);
|
||||
ResultStatus status = mapper.readValue(respBody, ResultStatus.class);
|
||||
if (!"0".equals(status.code)) {
|
||||
log.error("Unsuccessful SP response code:" + status, respBody);
|
||||
throw new SPException("Unsuccessful SP response code", respBody);
|
||||
}
|
||||
CredentialsResponse credResp = mapper.readValue(respBody, CredentialsResponse.class);
|
||||
//optionally, add response validation
|
||||
String credentials = Decryptor.getDecryptedData(credResp.data3DS.data, keySpec);
|
||||
log.info("Payment credentials decrypted");
|
||||
return new AbstractMap.SimpleEntry<>(credResp, mapper.readValue(credentials, PData3DS.class));
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.rbkmoney.provider.samsungpay.store;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 10.04.18.
|
||||
*/
|
||||
public class CertStoreException extends RuntimeException {
|
||||
public CertStoreException() {
|
||||
}
|
||||
|
||||
public CertStoreException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CertStoreException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CertStoreException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public CertStoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.rbkmoney.provider.samsungpay.store;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 09.04.18.
|
||||
*/
|
||||
public class SPKeyStore {
|
||||
private final Path serviceKeyDir;
|
||||
|
||||
public SPKeyStore(String serviceKeyDir) {
|
||||
this.serviceKeyDir = Paths.get(serviceKeyDir);
|
||||
}
|
||||
|
||||
public PKCS8EncodedKeySpec getKey(String serviceId) {
|
||||
return getKey(serviceKeyDir, serviceId, ".pem");
|
||||
}
|
||||
|
||||
private PKCS8EncodedKeySpec getKey(Path baseDir, String serviceId, String suffix) {
|
||||
Path certPath = baseDir.resolve(buildCertFileName(serviceId, suffix));
|
||||
try {
|
||||
if (!Files.isRegularFile(certPath)) {
|
||||
certPath = baseDir.resolve(buildCertFileName(null, suffix));
|
||||
if (!Files.isRegularFile(certPath)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return getPrivateKey(new String(Files.readAllBytes(certPath)));
|
||||
} catch (Exception e) {
|
||||
throw new CertStoreException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PKCS8EncodedKeySpec getPrivateKey(String pemKey) throws GeneralSecurityException {
|
||||
pemKey = pemKey.replace("-----BEGIN PRIVATE KEY-----\n", "");
|
||||
pemKey = pemKey.replace("-----END PRIVATE KEY-----", "");
|
||||
byte[] encoded = Base64.getDecoder().decode(pemKey);
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
|
||||
return keySpec;
|
||||
}
|
||||
|
||||
private String buildCertFileName(String serviceId, String suffix) {
|
||||
return (serviceId != null ? serviceId : "") + suffix;
|
||||
}
|
||||
|
||||
}
|
21
src/main/resources/application.properties
Normal file
21
src/main/resources/application.properties
Normal file
@ -0,0 +1,21 @@
|
||||
server.port=@server.port@
|
||||
server.rest_port=@server.rest_port@
|
||||
server.rest_path_prefix=api/v1
|
||||
|
||||
spring.application.name=@project.name@
|
||||
info.version=@project.version@
|
||||
info.damsel.version=@damsel.version@
|
||||
|
||||
springfox.documentation.swagger.v2.path=/${server.rest_path_prefix}/swag
|
||||
logback.appenders=FILE
|
||||
|
||||
samsung.version=v1
|
||||
samsung.endpoint=api-ops.mpay.samsung.com
|
||||
samsung.trans_url_template=https://${samsung.endpoint}/ops/${samsung.version}/transactions
|
||||
samsung.cred_url_template=https://${samsung.endpoint}/ops/${samsung.version}/transactions/paymentCredentials/{id}?serviceId={serviceId}
|
||||
|
||||
samsung.conn_timeout_ms=3000
|
||||
samsung.read_timeout_ms=3000
|
||||
samsung.write_timeout_ms=3000
|
||||
|
||||
keys_path=
|
@ -0,0 +1,14 @@
|
||||
package com.rbkmoney.provider.samsungpay;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 04.07.18.
|
||||
*/
|
||||
public class IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testCreateTransaction() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.rbkmoney.provider.samsungpay;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 12.04.18.
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
|
||||
properties = {"samsung.endpoint=api-ops.stg.mpay.samsung.com"}
|
||||
)
|
||||
|
||||
public class TransactionRunnerTest {
|
||||
|
||||
@Value("http://127.0.0.1:${server.rest_port}/${server.rest_path_prefix}/transaction")
|
||||
private String transactionUrl;
|
||||
|
||||
@Value("classpath:transaction_req.json")
|
||||
private org.springframework.core.io.Resource resource;
|
||||
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Test
|
||||
public void testRequestTransaction() throws IOException {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
|
||||
|
||||
//headers.add("X-Request-Id", System.currentTimeMillis()+"");
|
||||
|
||||
HttpEntity<String> request = new HttpEntity<>(IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8), headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(transactionUrl, request, String.class);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
} catch (HttpClientErrorException e) {
|
||||
System.out.println(e.getResponseBodyAsString());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
10
src/test/resources/logback-test.xml
Normal file
10
src/test/resources/logback-test.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
<logger name="com.rbkmoney" level="ALL"/>
|
||||
</configuration>
|
28
src/test/resources/stg-privkey.pem
Normal file
28
src/test/resources/stg-privkey.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDG++zaPXKaUNdD
|
||||
Qd54g8lhvkBx2CAmO8cgl50Mvk1eptNVjp4KzyfyxkI4aGfi6EAIp2ZvDwUnz5n2
|
||||
uvvQxSP8j+DUCP+3sZMJ+0LBdUrYINHqdIh4ONwHaIdeLXSmpRTxogsjN0a1nzh1
|
||||
p+geXmiWaAm774RBwTomVLGZXU9tQbetwAgK7wb057bpKiCoeeULGqIxskMPS7Qv
|
||||
rM4iVpsDCEFPTKsVt0bNAM4zWXHBGt2glrHsTC6JIiVjX9o+NlIjrODxkYfFV09a
|
||||
Mg5X0tZb1Bff4RFAHdVUjcoa9469MkKfrQsoATdGg8IHLFJIxj2xn75DlABZMdAA
|
||||
FpUQ54PNAgMBAAECggEAUCckpliANHcB7x62XbdARHYdgX48nQoRUSihY4O4qLrs
|
||||
gBc9xD7j6aBBBnXP+w/w00uTMINNYVb3vvJdAyCWOWM/fknNsBIAl9G1dzYnGt5F
|
||||
kHq2ii7lOrq5ZI9M4N+4iwjqEZpvijODy6kCEFGZMZTg7uoxTUdnhA8zpwJ2Pxy1
|
||||
eifKHKTQoqBLG2CCqRC5eKtzN01GMNyeuxn/a4JiTCE04T/x/KO12YjqtylKKK/W
|
||||
RQ2XCTR3EySCwe0FKzIscfWlTlof1vFj/Mi8H2jzky8/cAn87/DWQitIluq9RKzA
|
||||
XwHuSqCn2fSvGxTRwyW4AsP85qzVgl/kcQROU+aiQQKBgQD/8vBXfuwQzaBzmena
|
||||
vRwl31UmMK+FiHyPls17SOe69OAWYOtdhdK+/Ik2n6biTOVtN2bv0eytcKsUfoyF
|
||||
HY8l4y9RddccpQPwVCSyL7WoF8ILMVPZ09tZ4YPYvDef1/9H4ZhBUqdPsOEGMLaT
|
||||
tb4Z5bQDZpap3Ik5MYHssFvdfQKBgQDHBhRVoVO333NtXETDO4VJjFQdKpkLVjK/
|
||||
jbkZ8z1X1PKKlM63OnFUw+Hc35RAyhQmCItUTVPk06glKq/yLvt8KBxIrt4muvUJ
|
||||
QjoTR6EuV5fMgwQimng+CTxSpuAWd1P/mmvRsUmM3qRI40VWOze+FhERYtqtgkJ8
|
||||
1CPEhshQkQKBgHqtQq1tVFCpfmJqP0Bsq/UrGnD3nOlwBeP87/hLdWaSwGV5htaI
|
||||
sf6ApHPeCesl2EGE8H26LKrk+dsU3N1g0Z/jSGbPCI/eOAkVC5GsdHFhEcyzk/Ew
|
||||
Lk7iXIOhkze2G9GkO4nzx+XWbcS9zIT382oOQz3uCgDYh502MYP089MxAoGAGDG+
|
||||
ARbmlYC9iHriBRXUQzei9hS6nC0zaCPzb5spRuclQQGMC6w4IMTbTT2EyUeHoYQC
|
||||
ZBIuc6/jTfldgESD3/kETzWq3ex2Y1TAuJ1Jk9ekJYF73DUJDwmSYr3UmgaRmI4O
|
||||
M2So+04JjK7MPApg4WPPWVy6FsOyD3i+jGKSMXECgYBXHXavaZC/kru3O1BUr+Ff
|
||||
g4ttAWzKFJ+9ydgGTzz8HuPY86H7m2Su6U4vdShgW7AieGmK3N9upsNJ/eFuD4/g
|
||||
SAA/V11lvyHUngYlUFO7OMvU680TLc5U09/CFtymHnApaEXLs6x0LdmbrG5NATfw
|
||||
KxnXXwDtV+PPkt9bFidQBg==
|
||||
-----END PRIVATE KEY-----
|
20
src/test/resources/transaction_req.json
Normal file
20
src/test/resources/transaction_req.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"callback": "0",
|
||||
"paymentDetails": {
|
||||
"service": {
|
||||
"id": "bb12a068ebca4916a7c3c1"
|
||||
},
|
||||
"orderNumber": "2346",
|
||||
"protocol": {
|
||||
"type": "3DS",
|
||||
"version": "80"
|
||||
},
|
||||
"amount": {
|
||||
"currency": "RUB",
|
||||
"total": 98.76
|
||||
},
|
||||
"merchant": {
|
||||
"name": "rbk_merch"
|
||||
}
|
||||
}
|
||||
}
|
11
src/test/resources/transaction_resp.json
Normal file
11
src/test/resources/transaction_resp.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"resultCode": "0",
|
||||
"resultMessage": "SUCCESS",
|
||||
"id": "cb69dbe93a544622a82f1f",
|
||||
"href": "https://us-online.stg.mpay.samsung.com/onlinepay",
|
||||
"encInfo": {
|
||||
"mod": "a158f2b516930ba7e7f2e47b92753e688cf3d17a796f3d47e7a97eb4b669ca8ab9c3ff3f8d3b910a4f1b134a9575cfb6f33c4f756782acc22ea14a1829b43fd8bbcc4342ea611a623ae494e743b101d64a8f5ef7108fd881348e7f766ab75fb17430d80cdd90264587114019029e416723d454bf8d6229b05b32b66718659c0c78aa9b41156c2a5ada6087717d63075e2adf3e350750b3c285acac62d12a0113982a92252a79a3f7def005b96cfcf8828a815a244275dccd24ed50510756224ffee83b409e5e8466723a7b807a6450f37d151bcbfefa6c1e276097b28144eb345ee886b73f4d28ffd0aba10de76acda3e26fb26e7f3d48e3bc0ea43f5315f873",
|
||||
"exp": "10001",
|
||||
"keyId": "52c124f6eb7037a83bf0be70c53ce50ec4d0227bc58468b688d46cc8c7a33fa77e66448eea724f8e0a016607dc0bfaf58b448235c9d1b921b637bf93fe14dfee_310228a3ce3542558f3b"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user