mirror of
https://github.com/valitydev/googlepay-provider.git
synced 2024-11-06 09:15:21 +00:00
GP-2: GooglePay Implementation
This commit is contained in:
parent
6801102d06
commit
7b29b97253
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
# 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*
|
||||
### 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
|
||||
### 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
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
*.ipr
|
||||
*.iml
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# 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
|
||||
|
183
pom.xml
Normal file
183
pom.xml
Normal file
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.rbkmoney.provider</groupId>
|
||||
<artifactId>googlepay</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.maintainer>Vladimir Pankrashkin <v.pankrashkin@rbkmoney.com></project.maintainer>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<dockerfile.base.service.tag>22c57470c4fc47161894f036b7cf9d70f42b75f5</dockerfile.base.service.tag>
|
||||
<damsel.version>1.236-1510cd7</damsel.version>
|
||||
<shared.resources.version>0.3.2</shared.resources.version>
|
||||
<server.port>8022</server.port>
|
||||
<exposed.ports>${server.port}</exposed.ports>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--RBK libs-->
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.woody</groupId>
|
||||
<artifactId>woody-thrift</artifactId>
|
||||
<version>1.1.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.geck</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.6.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney.logback</groupId>
|
||||
<artifactId>nop-rolling</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>damsel</artifactId>
|
||||
<version>${damsel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rbkmoney</groupId>
|
||||
<artifactId>shared-resources</artifactId>
|
||||
<version>${shared.resources.version}</version>
|
||||
</dependency>
|
||||
<!--Thridparty libs-->
|
||||
<dependency>
|
||||
<groupId>com.google.crypto.tink</groupId>
|
||||
<artifactId>apps-paymentmethodtoken</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>24.1-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson.version}</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>
|
||||
|
||||
<!--Test libs-->
|
||||
<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>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<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>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,17 @@
|
||||
package com.rbkmoney.provider.googlepay;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = "com.rbkmoney.provider.googlepay")
|
||||
@ServletComponentScan
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.rbkmoney.provider.googlepay.config;
|
||||
|
||||
import com.google.crypto.tink.apps.paymentmethodtoken.GooglePaymentsPublicKeysManager;
|
||||
import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenRecipient;
|
||||
import com.rbkmoney.damsel.payment_tool_provider.PaymentToolProviderSrv;
|
||||
import com.rbkmoney.provider.googlepay.iface.decrypt.ProviderHandler;
|
||||
import com.rbkmoney.provider.googlepay.service.DecryptionService;
|
||||
import com.rbkmoney.provider.googlepay.service.GPKeyStore;
|
||||
import com.rbkmoney.provider.googlepay.service.ValidationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public DecryptionService decryptionService(@Value("${google.test}") boolean testMode, @Value("${google.gateway_id}") String gatewayId, @Value("${google.keys_path}")Resource keys) throws GeneralSecurityException, IOException {
|
||||
|
||||
PaymentMethodTokenRecipient.Builder builder = new PaymentMethodTokenRecipient.Builder();
|
||||
builder.fetchSenderVerifyingKeysWith(
|
||||
testMode ? GooglePaymentsPublicKeysManager.INSTANCE_TEST : GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
|
||||
.recipientId("gateway:"+gatewayId);
|
||||
for (String key: new GPKeyStore(keys.getURI()).getKeys()) {
|
||||
builder.addRecipientPrivateKey(key);
|
||||
}
|
||||
|
||||
return new DecryptionService(builder.build());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PaymentToolProviderSrv.Iface decryptHandler(DecryptionService decryptionService, @Value("${google.use_validation}") boolean enableValidation) {
|
||||
return new ProviderHandler(new ValidationService(), decryptionService, enableValidation);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
|
||||
public class Auth {
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Auth3DS extends Auth {
|
||||
private String cryptogram;
|
||||
private String eci;
|
||||
|
||||
@JsonCreator
|
||||
public Auth3DS(
|
||||
@JsonProperty(value = "3dsCryptogram", required = true) String cryptogram,
|
||||
@JsonProperty(value = "3dsEciIndicator") String eci) {
|
||||
this.cryptogram = cryptogram;
|
||||
this.eci = eci;
|
||||
}
|
||||
|
||||
public Auth3DS() {
|
||||
}
|
||||
|
||||
public String getCryptogram() {
|
||||
return cryptogram;
|
||||
}
|
||||
|
||||
public void setCryptogram(String cryptogram) {
|
||||
this.cryptogram = cryptogram;
|
||||
}
|
||||
|
||||
public String getEci() {
|
||||
return eci;
|
||||
}
|
||||
|
||||
public void setEci(String eci) {
|
||||
this.eci = eci;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Auth3DS{" +
|
||||
"cryptogram='" + (cryptogram == null ? null : "***") + '\'' +
|
||||
", eci='" + (eci == null ? null : "***") + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public enum AuthType {
|
||||
@JsonProperty("3DS")
|
||||
AUTH_3DS
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public class Card extends PaymentCredential {
|
||||
private String pan;
|
||||
private int expirationMonth;
|
||||
private int expirationYear;
|
||||
|
||||
@JsonCreator
|
||||
public Card(
|
||||
@JsonProperty(value = "pan", required = true) String pan,
|
||||
@JsonProperty(value = "expirationMonth", required = true) int expirationMonth,
|
||||
@JsonProperty(value = "expirationYear", required = true) int expirationYear) {
|
||||
this.pan = pan;
|
||||
this.expirationMonth = expirationMonth;
|
||||
this.expirationYear = expirationYear;
|
||||
}
|
||||
|
||||
public Card() {
|
||||
}
|
||||
|
||||
public String getPan() {
|
||||
return pan;
|
||||
}
|
||||
|
||||
public void setPan(String pan) {
|
||||
this.pan = pan;
|
||||
}
|
||||
|
||||
public int getExpirationMonth() {
|
||||
return expirationMonth;
|
||||
}
|
||||
|
||||
public void setExpirationMonth(int expirationMonth) {
|
||||
this.expirationMonth = expirationMonth;
|
||||
}
|
||||
|
||||
public int getExpirationYear() {
|
||||
return expirationYear;
|
||||
}
|
||||
|
||||
public void setExpirationYear(int expirationYear) {
|
||||
this.expirationYear = expirationYear;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Card{" +
|
||||
"pan='" + (pan == null ? null: "***") + '\'' +
|
||||
", expirationMonth=" + "**" +
|
||||
", expirationYear=" + "****" +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CardInfo {
|
||||
private String cardDescription;
|
||||
private String cardClass;
|
||||
private String last4digits;
|
||||
private String cardNetwork;
|
||||
|
||||
@JsonCreator
|
||||
public CardInfo(
|
||||
@JsonProperty(value = "cardDescription", required = true) String cardDescription,
|
||||
@JsonProperty(value = "cardClass", required = true) String cardClass,
|
||||
@JsonProperty(value = "cardDetails", required = true) String last4digits,
|
||||
@JsonProperty(value = "cardNetwork", required = true) String cardNetwork) {
|
||||
this.cardDescription = cardDescription;
|
||||
this.cardClass = cardClass;
|
||||
this.last4digits = last4digits;
|
||||
this.cardNetwork = cardNetwork;
|
||||
}
|
||||
|
||||
public CardInfo() {
|
||||
}
|
||||
|
||||
public String getCardDescription() {
|
||||
return cardDescription;
|
||||
}
|
||||
|
||||
public void setCardDescription(String cardDescription) {
|
||||
this.cardDescription = cardDescription;
|
||||
}
|
||||
|
||||
public String getCardNetwork() {
|
||||
return cardNetwork;
|
||||
}
|
||||
|
||||
public void setCardNetwork(String cardNetwork) {
|
||||
this.cardNetwork = cardNetwork;
|
||||
}
|
||||
|
||||
public String getCardClass() {
|
||||
return cardClass;
|
||||
}
|
||||
|
||||
public void setCardClass(String cardClass) {
|
||||
this.cardClass = cardClass;
|
||||
}
|
||||
|
||||
public String getLast4digits() {
|
||||
return last4digits;
|
||||
}
|
||||
|
||||
public void setLast4digits(String last4digits) {
|
||||
this.last4digits = last4digits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CardInfo{" +
|
||||
"cardDescription='" + cardDescription + '\'' +
|
||||
", cardClass='" + cardClass + '\'' +
|
||||
", last4digits='" + last4digits + '\'' +
|
||||
", cardNetwork='" + cardNetwork + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.rbkmoney.provider.googlepay.service.DecryptedMessageDeserializer;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
@JsonDeserialize(using = DecryptedMessageDeserializer.class)
|
||||
public class DecryptedMessage {
|
||||
private Instant expiration;
|
||||
private String messageId;
|
||||
private String gatewayMerchantId;
|
||||
private PaymentMethod paymentMethod;
|
||||
private PaymentCredential paymentCredential;
|
||||
private Map<String, Object> paymentCredentialMap;
|
||||
|
||||
|
||||
public DecryptedMessage() {
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public DecryptedMessage(
|
||||
@JsonProperty(value = "messageExpiration", required = true) Instant expiration,
|
||||
@JsonProperty(value = "messageId", required = true) String messageId,
|
||||
@JsonProperty(value = "gatewayMerchantId", required = true) String gatewayMerchantId,
|
||||
@JsonProperty(value = "paymentMethod", required = true) PaymentMethod paymentMethod,
|
||||
@JsonProperty(value = "paymentMethodDetails", required = true) Map<String, Object> paymentCredentialMap) {
|
||||
this.expiration = expiration;
|
||||
this.messageId = messageId;
|
||||
this.gatewayMerchantId = gatewayMerchantId;
|
||||
this.paymentMethod = paymentMethod;
|
||||
this.paymentCredentialMap = paymentCredentialMap;
|
||||
}
|
||||
|
||||
public Instant getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(Instant expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public String getGatewayMerchantId() {
|
||||
return gatewayMerchantId;
|
||||
}
|
||||
|
||||
public void setGatewayMerchantId(String gatewayMerchantId) {
|
||||
this.gatewayMerchantId = gatewayMerchantId;
|
||||
}
|
||||
|
||||
public PaymentMethod getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public void setPaymentMethod(PaymentMethod paymentMethod) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public PaymentCredential getPaymentCredential() {
|
||||
return paymentCredential;
|
||||
}
|
||||
|
||||
public void setPaymentCredential(PaymentCredential paymentCredential) {
|
||||
this.paymentCredential = paymentCredential;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPaymentCredentialMap() {
|
||||
return paymentCredentialMap;
|
||||
}
|
||||
|
||||
public void setPaymentCredentialMap(Map<String, Object> paymentCredentialMap) {
|
||||
this.paymentCredentialMap = paymentCredentialMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DecryptedMessage{" +
|
||||
"expiration=" + expiration +
|
||||
", messageId='" + messageId + '\'' +
|
||||
", gatewayMerchantId='" + gatewayMerchantId + '\'' +
|
||||
", paymentMethod=" + paymentMethod +
|
||||
", paymentCredential=" + paymentCredential +
|
||||
", paymentCredentialMap=" + paymentCredentialMap.size() +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
|
||||
public class PaymentCredential {
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PaymentData {
|
||||
private CardInfo cardInfo;
|
||||
private PaymentToken paymentMethodToken;
|
||||
|
||||
@JsonCreator
|
||||
public PaymentData(
|
||||
@JsonProperty(value = "paymentMethodToken", required = true) PaymentToken paymentMethodToken,
|
||||
@JsonProperty(value = "cardInfo", required = true) CardInfo cardInfo) {
|
||||
this.paymentMethodToken = paymentMethodToken;
|
||||
this.cardInfo = cardInfo;
|
||||
}
|
||||
|
||||
public PaymentData() {
|
||||
}
|
||||
|
||||
public PaymentToken getPaymentMethodToken() {
|
||||
return paymentMethodToken;
|
||||
}
|
||||
|
||||
public void setPaymentMethodToken(PaymentToken paymentMethodToken) {
|
||||
this.paymentMethodToken = paymentMethodToken;
|
||||
}
|
||||
|
||||
public CardInfo getCardInfo() {
|
||||
return cardInfo;
|
||||
}
|
||||
|
||||
public void setCardInfo(CardInfo cardInfo) {
|
||||
this.cardInfo = cardInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PaymentData{" +
|
||||
"cardInfo=" + cardInfo +
|
||||
", paymentMethodToken=" + paymentMethodToken +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public enum PaymentMethod {
|
||||
@JsonProperty("TOKENIZED_CARD")
|
||||
TOKENIZED,
|
||||
@JsonProperty("CARD")
|
||||
CARD
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public class PaymentToken {
|
||||
private String token;
|
||||
private TokenizationType tokenizationType;
|
||||
|
||||
@JsonCreator
|
||||
public PaymentToken(
|
||||
@JsonProperty(value = "tokenizationType", required = true) TokenizationType tokenizationType,
|
||||
@JsonProperty(value = "token", required = true) String token) {
|
||||
this.tokenizationType = tokenizationType;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public PaymentToken() {
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public TokenizationType getTokenizationType() {
|
||||
return tokenizationType;
|
||||
}
|
||||
|
||||
public void setTokenizationType(TokenizationType tokenizationType) {
|
||||
this.tokenizationType = tokenizationType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PaymentToken{" +
|
||||
"token='" + token + '\'' +
|
||||
", tokenizationType=" + tokenizationType +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public enum TokenizationType {
|
||||
@JsonProperty("PAYMENT_GATEWAY")
|
||||
GATEWAY,
|
||||
//@JsonProperty("DIRECT") - not supported
|
||||
//DIRECT
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.rbkmoney.provider.googlepay.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.rbkmoney.provider.googlepay.service.TokenizedCardDeserializer;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonDeserialize(using = TokenizedCardDeserializer.class)
|
||||
public class TokenizedCard extends PaymentCredential {
|
||||
private String dpan;
|
||||
private int expirationMonth;
|
||||
private int expirationYear;
|
||||
private AuthType authType;
|
||||
private Auth auth;
|
||||
|
||||
@JsonCreator
|
||||
public TokenizedCard(
|
||||
@JsonProperty(value = "dpan", required = true) String dpan,
|
||||
@JsonProperty(value = "expirationMonth", required = true) int expirationMonth,
|
||||
@JsonProperty(value = "expirationYear", required = true) int expirationYear,
|
||||
@JsonProperty(value = "authMethod", required = true) AuthType authType) {
|
||||
this.dpan = dpan;
|
||||
this.expirationMonth = expirationMonth;
|
||||
this.expirationYear = expirationYear;
|
||||
this.authType = authType;
|
||||
}
|
||||
|
||||
public TokenizedCard() {
|
||||
}
|
||||
|
||||
public String getDpan() {
|
||||
return dpan;
|
||||
}
|
||||
|
||||
public void setDpan(String dpan) {
|
||||
this.dpan = dpan;
|
||||
}
|
||||
|
||||
public int getExpirationMonth() {
|
||||
return expirationMonth;
|
||||
}
|
||||
|
||||
public void setExpirationMonth(int expirationMonth) {
|
||||
this.expirationMonth = expirationMonth;
|
||||
}
|
||||
|
||||
public int getExpirationYear() {
|
||||
return expirationYear;
|
||||
}
|
||||
|
||||
public void setExpirationYear(int expirationYear) {
|
||||
this.expirationYear = expirationYear;
|
||||
}
|
||||
|
||||
public AuthType getAuthType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
public void setAuthType(AuthType authType) {
|
||||
this.authType = authType;
|
||||
}
|
||||
|
||||
public Auth getAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setAuth(Auth auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenizedCard{" +
|
||||
"dpan='" + (dpan == null ? null : "***") + '\'' +
|
||||
", expirationMonth=" + "**" +
|
||||
", expirationYear=" + "****" +
|
||||
", authType=" + authType +
|
||||
", auth=" + auth +
|
||||
"} ";
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package com.rbkmoney.provider.googlepay.iface.decrypt;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.rbkmoney.damsel.base.Content;
|
||||
import com.rbkmoney.damsel.base.InvalidRequest;
|
||||
import com.rbkmoney.damsel.domain.BankCardPaymentSystem;
|
||||
import com.rbkmoney.damsel.payment_tool_provider.*;
|
||||
import com.rbkmoney.geck.common.util.TypeUtil;
|
||||
import com.rbkmoney.provider.googlepay.domain.DecryptedMessage;
|
||||
import com.rbkmoney.provider.googlepay.domain.PaymentData;
|
||||
import com.rbkmoney.provider.googlepay.domain.PaymentToken;
|
||||
import com.rbkmoney.provider.googlepay.service.CryptoException;
|
||||
import com.rbkmoney.provider.googlepay.service.DecryptionService;
|
||||
import com.rbkmoney.provider.googlepay.service.ValidationException;
|
||||
import com.rbkmoney.provider.googlepay.service.ValidationService;
|
||||
import org.apache.thrift.TException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 03.04.18.
|
||||
*/
|
||||
public class ProviderHandler implements PaymentToolProviderSrv.Iface {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final ValidationService validator;
|
||||
private final DecryptionService decryptor;
|
||||
private final ObjectReader srcReader = new ObjectMapper().readerFor(PaymentData.class);
|
||||
private final ObjectReader resReader = new ObjectMapper().readerFor(DecryptedMessage.class);
|
||||
private final boolean enableValidation;
|
||||
|
||||
public ProviderHandler(ValidationService validator, DecryptionService decryptor, boolean enableValidation) {
|
||||
this.validator = validator;
|
||||
this.decryptor = decryptor;
|
||||
this.enableValidation = enableValidation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnwrappedPaymentTool unwrap(WrappedPaymentTool payment_tool) throws InvalidRequest, TException {
|
||||
log.info("New unwrap request: {}", payment_tool);
|
||||
Content content = payment_tool.getRequest().getGoogle().getPaymentToken();
|
||||
if (!content.getType().equalsIgnoreCase("application/json")) {
|
||||
throw new InvalidRequest(Arrays.asList("Wrong content type"));
|
||||
}
|
||||
|
||||
try {
|
||||
PaymentData paymentData = srcReader.readValue(content.getData());
|
||||
PaymentToken paymentToken = paymentData.getPaymentMethodToken();
|
||||
|
||||
String tokenData = decryptor.decryptToken(paymentToken.getToken());
|
||||
log.info("Payment data decrypted");
|
||||
DecryptedMessage decryptedMessage = resReader.readValue(tokenData);
|
||||
log.info("Decryption result: {}", decryptedMessage);
|
||||
|
||||
if (enableValidation) {
|
||||
validator.validate(payment_tool.getRequest().getGoogle().getGatewayMerchantId(), decryptedMessage);
|
||||
log.info("Request successfully validated");
|
||||
} else {
|
||||
log.info("Request validation skipped");
|
||||
}
|
||||
UnwrappedPaymentTool result = new UnwrappedPaymentTool();
|
||||
result.setCardInfo(extractCardInfo(paymentData.getCardInfo()));
|
||||
result.setPaymentData(extractPaymentData(decryptedMessage));
|
||||
result.setDetails(extractPaymentDetails(decryptedMessage));
|
||||
|
||||
UnwrappedPaymentTool logResult = new UnwrappedPaymentTool(result);
|
||||
if (logResult.getPaymentData().isSetCard()) {
|
||||
logResult.getPaymentData().setCard(new Card());
|
||||
} else {
|
||||
logResult.getPaymentData().setTokenizedCard(new TokenizedCard());
|
||||
}
|
||||
log.info("Unwrap partial result: {}", logResult);
|
||||
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 (ValidationException e) {
|
||||
log.error("Failed to validate request", e);
|
||||
throw new InvalidRequest(Arrays.asList(e.getMessage()));
|
||||
} catch (CryptoException e) {
|
||||
log.error("Decryption error", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String filterPan(String src) {
|
||||
return src.replaceAll("([^\\d])\\d{8,19}([^\\d])", "$1***$2");
|
||||
}
|
||||
|
||||
private CardInfo extractCardInfo(com.rbkmoney.provider.googlepay.domain.CardInfo srcCardInfo) {
|
||||
CardInfo cardInfo = new CardInfo();
|
||||
cardInfo.setDisplayName(srcCardInfo.getCardDescription());
|
||||
cardInfo.setLast4Digits(srcCardInfo.getLast4digits());
|
||||
cardInfo.setCardClass(TypeUtil.toEnumField(
|
||||
Optional.of(srcCardInfo.getCardClass()).map(s -> s.toLowerCase()).orElse(null),
|
||||
CardClass.class, CardClass.unknown));
|
||||
cardInfo.setPaymentSystem(TypeUtil.toEnumField(
|
||||
Optional.ofNullable(srcCardInfo.getCardNetwork()).map(s -> s.toLowerCase()).orElse(null),
|
||||
BankCardPaymentSystem.class, null));
|
||||
return cardInfo;
|
||||
}
|
||||
|
||||
private CardPaymentData extractPaymentData(DecryptedMessage decryptedMessage) {
|
||||
CardPaymentData cardPaymentData = new CardPaymentData();
|
||||
switch (decryptedMessage.getPaymentMethod()) {
|
||||
case CARD:
|
||||
com.rbkmoney.provider.googlepay.domain.Card srcCard = (com.rbkmoney.provider.googlepay.domain.Card) decryptedMessage.getPaymentCredential();
|
||||
Card card = new Card(srcCard.getPan(), new ExpDate((byte) srcCard.getExpirationMonth(), (short) srcCard.getExpirationYear()));
|
||||
cardPaymentData.setCard(card);
|
||||
break;
|
||||
case TOKENIZED:
|
||||
com.rbkmoney.provider.googlepay.domain.TokenizedCard srcToken = (com.rbkmoney.provider.googlepay.domain.TokenizedCard) decryptedMessage.getPaymentCredential();
|
||||
AuthData authData;
|
||||
switch (srcToken.getAuthType()) {
|
||||
case AUTH_3DS:
|
||||
com.rbkmoney.provider.googlepay.domain.Auth3DS srcAuth3DS = (com.rbkmoney.provider.googlepay.domain.Auth3DS) srcToken.getAuth();
|
||||
Auth3DS auth3DS = new Auth3DS(srcAuth3DS.getCryptogram());
|
||||
auth3DS.setEci(srcAuth3DS.getEci());
|
||||
authData = AuthData.auth_3ds(auth3DS);
|
||||
break;
|
||||
default:
|
||||
throw new ValidationException("Unknown type:" + srcToken.getAuthType());
|
||||
}
|
||||
TokenizedCard tokenizedCard = new TokenizedCard(
|
||||
srcToken.getDpan(),
|
||||
new ExpDate((byte) srcToken.getExpirationMonth(), (short) srcToken.getExpirationYear()),
|
||||
authData);
|
||||
cardPaymentData.setTokenizedCard(tokenizedCard);
|
||||
break;
|
||||
default:
|
||||
throw new ValidationException("Unknown type:" + decryptedMessage.getPaymentMethod());
|
||||
}
|
||||
return cardPaymentData;
|
||||
}
|
||||
|
||||
private PaymentDetails extractPaymentDetails(DecryptedMessage decryptedMessage) {
|
||||
return PaymentDetails.google(new GooglePayDetails(decryptedMessage.getMessageId(), TypeUtil.temporalToString(decryptedMessage.getExpiration())));
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.rbkmoney.provider.googlepay.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 29.05.18.
|
||||
*/
|
||||
@WebServlet("/provider/google")
|
||||
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,25 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 12.04.18.
|
||||
*/
|
||||
public class CryptoException extends Exception {
|
||||
public CryptoException() {
|
||||
}
|
||||
|
||||
public CryptoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CryptoException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CryptoException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public CryptoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.TreeNode;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.rbkmoney.provider.googlepay.domain.Card;
|
||||
import com.rbkmoney.provider.googlepay.domain.DecryptedMessage;
|
||||
import com.rbkmoney.provider.googlepay.domain.TokenizedCard;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public class DecryptedMessageDeserializer extends StdDeserializer<DecryptedMessage> {
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
|
||||
public DecryptedMessageDeserializer() {
|
||||
super(DecryptedMessage.class);
|
||||
JacksonAnnotationIntrospector introspector = new JacksonAnnotationIntrospector() {
|
||||
@Override
|
||||
public Object findDeserializer(Annotated a) {
|
||||
if (a.getRawType() == DecryptedMessage.class)
|
||||
return null;
|
||||
return super.findDeserializer(a);
|
||||
}
|
||||
};
|
||||
mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
|
||||
mapper.setAnnotationIntrospector(introspector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptedMessage deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
TreeNode root = parser.readValueAsTree();
|
||||
|
||||
DecryptedMessage message = mapper.treeToValue(root, DecryptedMessage.class);
|
||||
|
||||
switch (message.getPaymentMethod()) {
|
||||
case CARD:
|
||||
message.setPaymentCredential(mapper.convertValue(message.getPaymentCredentialMap(), Card.class));
|
||||
break;
|
||||
case TOKENIZED:
|
||||
message.setPaymentCredential(mapper.convertValue(message.getPaymentCredentialMap(), TokenizedCard.class));
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenRecipient;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
public class DecryptionService {
|
||||
private final PaymentMethodTokenRecipient decryptor;
|
||||
|
||||
public DecryptionService(PaymentMethodTokenRecipient decryptor) {
|
||||
this.decryptor = decryptor;
|
||||
}
|
||||
|
||||
public String decryptToken(String paymentToken) throws CryptoException {
|
||||
try {
|
||||
return decryptor.unseal(paymentToken);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new CryptoException("Message decryption failed", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
public class GPKeyStore {
|
||||
private final URI path;
|
||||
|
||||
public GPKeyStore(URI path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public List<String> getKeys() throws IOException {
|
||||
return Files.readAllLines(Paths.get(path));
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.TreeNode;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
import com.rbkmoney.provider.googlepay.domain.Auth3DS;
|
||||
import com.rbkmoney.provider.googlepay.domain.TokenizedCard;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public class TokenizedCardDeserializer extends JsonDeserializer<TokenizedCard> {
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public TokenizedCardDeserializer() {
|
||||
JacksonAnnotationIntrospector introspector = new JacksonAnnotationIntrospector() {
|
||||
@Override
|
||||
public Object findDeserializer(Annotated a) {
|
||||
if (a.getRawType() == TokenizedCard.class)
|
||||
return null;
|
||||
return super.findDeserializer(a);
|
||||
}
|
||||
};
|
||||
mapper = new ObjectMapper();
|
||||
mapper.setAnnotationIntrospector(introspector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenizedCard deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
TreeNode root = parser.readValueAsTree();
|
||||
TokenizedCard tokenizedCard = mapper.treeToValue(root, TokenizedCard.class);
|
||||
|
||||
switch (tokenizedCard.getAuthType()) {
|
||||
case AUTH_3DS:
|
||||
tokenizedCard.setAuth(mapper.reader().treeToValue(root, Auth3DS.class));
|
||||
break;
|
||||
}
|
||||
|
||||
return tokenizedCard;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 28.05.18.
|
||||
*/
|
||||
public class ValidationException extends RuntimeException {
|
||||
public ValidationException() {
|
||||
}
|
||||
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ValidationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ValidationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.rbkmoney.provider.googlepay.service;
|
||||
|
||||
import com.rbkmoney.provider.googlepay.domain.DecryptedMessage;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
public class ValidationService {
|
||||
public void validate(String gMerchantId, DecryptedMessage message) throws ValidationException {
|
||||
if (!gMerchantId.equals(message.getGatewayMerchantId())) {
|
||||
throw new ValidationException(String.format("Merchant ID: '%s' doesn't match decrypted one: '%s'", gMerchantId, message.getGatewayMerchantId()));
|
||||
}
|
||||
|
||||
if (Instant.now().isAfter(message.getExpiration())) {
|
||||
throw new ValidationException("Message expired: "+message.getExpiration());
|
||||
}
|
||||
}
|
||||
}
|
11
src/main/resources/application.properties
Normal file
11
src/main/resources/application.properties
Normal file
@ -0,0 +1,11 @@
|
||||
server.port=@server.port@
|
||||
|
||||
spring.application.name=@project.name@
|
||||
info.version=@project.version@
|
||||
info.damsel.version=@damsel.version@
|
||||
|
||||
google.test=true
|
||||
google.gateway_id=rbkmoney
|
||||
google.keys_path=classpath:keys.txt
|
||||
google.use_validation=true
|
||||
|
82
src/test/java/com/rbkmoney/provider/googlepay/DataTest.java
Normal file
82
src/test/java/com/rbkmoney/provider/googlepay/DataTest.java
Normal file
@ -0,0 +1,82 @@
|
||||
package com.rbkmoney.provider.googlepay;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.crypto.tink.apps.paymentmethodtoken.GooglePaymentsPublicKeysManager;
|
||||
import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenRecipient;
|
||||
import com.rbkmoney.provider.googlepay.domain.DecryptedMessage;
|
||||
import com.rbkmoney.provider.googlepay.domain.PaymentData;
|
||||
import com.rbkmoney.provider.googlepay.service.GPKeyStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 25.05.18.Ø
|
||||
*/
|
||||
public class DataTest {
|
||||
|
||||
static {
|
||||
GooglePaymentsPublicKeysManager.INSTANCE_TEST.refreshInBackground();
|
||||
}
|
||||
|
||||
@org.junit.Test
|
||||
public void test() throws GeneralSecurityException, IOException, URISyntaxException {
|
||||
String paymentMethodToken = "{\"signature\":\"MEUCIQDiT2/dI+H7LiiP0Yu/p8Z62Laumnk/js9cQrTUpYufIAIgN+e3tDoBDnqk6pt+MEwsVWD3oq/DfbbcLPJgdo3AMfM\\u003d\",\"protocolVersion\":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"k84ZAjkWfAibZmWxJfBJ8+3lhNiVvPrMYvLsZQwSt5dEmvO1S7davQ5rq04MNLKjwRezz301L6kDxv6abe2M7Z2KIM/tlmgtsAlV2xbOxS2piChvEeB0RyVbAxjLKfL5vnOlxivLgoPbCUrm7/hSwxsPhjVvyTXMCBbo744ZnIINuk8W3aROtFnGBqdzWSEjmlrhUBHoTJ1IEMuowoklUyI2Qd8pCQHA4it8mvITPE/KDQNt4J4HmNAx1GkW/Va9SEHZXP/J5xNjFeijshDZgd9oam1JB2yg3sgIh3e2JmgkVChqHoilaEdjKTLJc95FlJfbZyxytmh4E7jxvJ//vB5ySGV1pyVVRT8IX6K2oIXjsJ+JAZQmr7/MNo7W/cq6guz07nFHMqk8FCJbsWA75krj4Y9Ls8QkDyI7ccayapE2N9/QP5koLglASxWFfUEgWyWPWA\\\\u003d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"BLhY0C4bLP+Cv6AKH7Hz2YKICO2QqFZYxFDOWZ07S0LfEAwCmiusTIW/gz377AveU3Fw5JNKZPNjHkBG4OkEnKk\\\\u003d\\\",\\\"tag\\\":\\\"JvdgTyndj5fDugbG3rEoN52adgA/x24aGmRAKyoqxrk\\\\u003d\\\"}\"}";
|
||||
PaymentMethodTokenRecipient.Builder builder = new PaymentMethodTokenRecipient.Builder();
|
||||
builder.fetchSenderVerifyingKeysWith(
|
||||
GooglePaymentsPublicKeysManager.INSTANCE_TEST)
|
||||
.recipientId("gateway:rbkmoney");
|
||||
for (String key: new GPKeyStore(this.getClass().getClassLoader().getResource("keys.txt").toURI()).getKeys()) {
|
||||
builder.addRecipientPrivateKey(key);
|
||||
}
|
||||
String decryptedMessage = builder.build()
|
||||
.unseal(paymentMethodToken);
|
||||
System.out.println(decryptedMessage);
|
||||
}
|
||||
|
||||
@org.junit.Test
|
||||
public void testDeserializationEncryptedMsg() throws IOException {
|
||||
String encCard = "{\n" +
|
||||
" \"cardInfo\": {\n" +
|
||||
" \"cardNetwork\": \"VISA\",\n" +
|
||||
" \"cardDetails\": \"9391\",\n" +
|
||||
" \"cardImageUri\": \"https://lh6.ggpht.com/NvYf_33MleY1waJfW6O98wb3KU6XeinwiahmvUIyu46LcWeQdTMGm7WYe81uZYWLUbkjvz0E\",\n" +
|
||||
" \"cardDescription\": \"Visa •••• 9391\",\n" +
|
||||
" \"cardClass\": \"CREDIT\"\n" +
|
||||
" },\n" +
|
||||
" \"paymentMethodToken\": {\n" +
|
||||
" \"tokenizationType\": \"PAYMENT_GATEWAY\",\n" +
|
||||
" \"token\": \"{\\\"signature\\\":\\\"MEUCIQCBj5ClFzpJTg3UB4yKdXRP5O12vqpcTCM+x51/99ugYAIgPnCmr8k2G4oKWQqxmAawPgTd42vX1rx91eyqIHgSju4\\\\u003d\\\",\\\"protocolVersion\\\":\\\"ECv1\\\",\\\"signedMessage\\\":\\\"{\\\\\\\"encryptedMessage\\\\\\\":\\\\\\\"tSj1k3qfKoVg5rcZg8uOzMaGrk31qTIUkjYehgjV7BVAC/SGVfPE40q9PHppoZJqFsRwM1xMKKpoZatHa/fct/2YlVwxySxNR5uu3PHR4ZzuWb7vh/Nnb8BWlSiUMMELLaHFA1+FR2zbLpgEG9QdmeQRpyq07SETaH43WF0MLTDbKr9Con3SLZVCnoNS6CZZsPVTYNm0IjLWABAf66O5VlMWAPwke+kMaiJmq5ttPaxn6zGVJq4/JT7T7iKuc/T0rWDh7BzzVEzi/wyvaYDl2SkOljqFcjNYMTEr76GHlsgpAjtzCCPJkPJ9m7wkd3v+mgdQgazJ+ZwFvKTk8Ru7wZ4eywgb1G36/+IBpH/7936PvkayOTuXgtQkjts027mnH9/SyRA3unoJrNXRpUPytxwVO048edVUBKB9Q4ZZYadeGErH4xRTg64fPS9KiHSswBeY\\\\\\\",\\\\\\\"ephemeralPublicKey\\\\\\\":\\\\\\\"BLeiRaJ1+9DkK9x8rPK1377KKgQVv3kSbomJZxoTgqaOugBa8PAR3QDIH3VfqscW8zfMbrNrA736Nm4AahbogaU\\\\\\\\u003d\\\\\\\",\\\\\\\"tag\\\\\\\":\\\\\\\"oBPVR6dw8t4GM0ZC3QLd45hcx6oeag5D98BSdIsV0No\\\\\\\\u003d\\\\\\\"}\\\"}\"\n" +
|
||||
" },\n" +
|
||||
" \"email\": \"keinasylum@gmail.com\"\n" +
|
||||
"}";
|
||||
|
||||
PaymentData paymentData = new ObjectMapper().readerFor(PaymentData.class).readValue(encCard);
|
||||
System.out.println(paymentData);
|
||||
}
|
||||
|
||||
@org.junit.Test
|
||||
public void testDeserializationDecryptedMsg() throws IOException {
|
||||
String decCard = "{\"gatewayMerchantId\":\"fadsfasdf\",\"messageExpiration\":\"1527861216388\",\"messageId\":\"AH2EjteSBP3heqxYKyKkh88NzU95u3sIc6Kup-vwgbF9k3_SzwWBylswvYvHSEdjwzYorh-tQ1VGEvTGht4c2CJdb0Pm9eDzfKtiIjstdl0QcbUWcNQM83ujxFhBmKcHG809dgnsWuOB\",\"paymentMethod\":\"CARD\",\"paymentMethodDetails\":{\"expirationYear\":2023,\"expirationMonth\":12,\"pan\":\"4111111111111111\"}}";
|
||||
DecryptedMessage decCardMsg = new ObjectMapper().readerFor(DecryptedMessage.class).readValue(decCard);
|
||||
System.out.println(decCardMsg);
|
||||
String decToken = "{\n" +
|
||||
"\"gatewayMerchantId\":\"fadsfasdf\","+
|
||||
" \"paymentMethod\": \"TOKENIZED_CARD\",\n" +
|
||||
" \"paymentMethodDetails\": {\n" +
|
||||
" \"dpan\": \"4444444444444444\",\n" +
|
||||
" \"expirationMonth\": 10,\n" +
|
||||
" \"expirationYear\": 2020,\n" +
|
||||
" \"authMethod\": \"3DS\",\n" +
|
||||
" \"3dsCryptogram\": \"AAAAAA...\",\n" +
|
||||
" \"3dsEciIndicator\": \"eci indicator\"\n" +
|
||||
" },\n" +
|
||||
" \n" +
|
||||
" \"messageId\": \"some-message-id\",\n" +
|
||||
" \"messageExpiration\": \"1520836260646\"\n" +
|
||||
"}";
|
||||
DecryptedMessage decTokenMsg = new ObjectMapper().readerFor(DecryptedMessage.class).readValue(decToken);
|
||||
System.out.println(decTokenMsg);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.rbkmoney.provider.googlepay;
|
||||
|
||||
import com.rbkmoney.damsel.base.Content;
|
||||
import com.rbkmoney.damsel.base.InvalidRequest;
|
||||
import com.rbkmoney.damsel.domain.BankCardPaymentSystem;
|
||||
import com.rbkmoney.damsel.payment_tool_provider.*;
|
||||
import com.rbkmoney.woody.thrift.impl.http.THClientBuilder;
|
||||
import org.apache.thrift.TException;
|
||||
import org.junit.Before;
|
||||
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.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Created by vpankrashkin on 29.05.18.
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
|
||||
)
|
||||
public class IntegrationTest {
|
||||
@Value("http://127.0.0.1:${server.port}/provider/google")
|
||||
private String url;
|
||||
|
||||
private PaymentToolProviderSrv.Iface client;
|
||||
|
||||
private String encMsg = "{\n" +
|
||||
" \"cardInfo\": {\n" +
|
||||
" \"cardNetwork\": \"VISA\",\n" +
|
||||
" \"cardDetails\": \"9391\",\n" +
|
||||
" \"cardImageUri\": \"https://lh6.ggpht.com/NvYf_33MleY1waJfW6O98wb3KU6XeinwiahmvUIyu46LcWeQdTMGm7WYe81uZYWLUbkjvz0E\",\n" +
|
||||
" \"cardDescription\": \"Visa •••• 9391\",\n" +
|
||||
" \"cardClass\": \"CREDIT\"\n" +
|
||||
" },\n" +
|
||||
" \"paymentMethodToken\": {\n" +
|
||||
" \"tokenizationType\": \"PAYMENT_GATEWAY\",\n" +
|
||||
" \"token\": \"{\\\"signature\\\":\\\"MEUCIQCBj5ClFzpJTg3UB4yKdXRP5O12vqpcTCM+x51/99ugYAIgPnCmr8k2G4oKWQqxmAawPgTd42vX1rx91eyqIHgSju4\\\\u003d\\\",\\\"protocolVersion\\\":\\\"ECv1\\\",\\\"signedMessage\\\":\\\"{\\\\\\\"encryptedMessage\\\\\\\":\\\\\\\"tSj1k3qfKoVg5rcZg8uOzMaGrk31qTIUkjYehgjV7BVAC/SGVfPE40q9PHppoZJqFsRwM1xMKKpoZatHa/fct/2YlVwxySxNR5uu3PHR4ZzuWb7vh/Nnb8BWlSiUMMELLaHFA1+FR2zbLpgEG9QdmeQRpyq07SETaH43WF0MLTDbKr9Con3SLZVCnoNS6CZZsPVTYNm0IjLWABAf66O5VlMWAPwke+kMaiJmq5ttPaxn6zGVJq4/JT7T7iKuc/T0rWDh7BzzVEzi/wyvaYDl2SkOljqFcjNYMTEr76GHlsgpAjtzCCPJkPJ9m7wkd3v+mgdQgazJ+ZwFvKTk8Ru7wZ4eywgb1G36/+IBpH/7936PvkayOTuXgtQkjts027mnH9/SyRA3unoJrNXRpUPytxwVO048edVUBKB9Q4ZZYadeGErH4xRTg64fPS9KiHSswBeY\\\\\\\",\\\\\\\"ephemeralPublicKey\\\\\\\":\\\\\\\"BLeiRaJ1+9DkK9x8rPK1377KKgQVv3kSbomJZxoTgqaOugBa8PAR3QDIH3VfqscW8zfMbrNrA736Nm4AahbogaU\\\\\\\\u003d\\\\\\\",\\\\\\\"tag\\\\\\\":\\\\\\\"oBPVR6dw8t4GM0ZC3QLd45hcx6oeag5D98BSdIsV0No\\\\\\\\u003d\\\\\\\"}\\\"}\"\n" +
|
||||
" },\n" +
|
||||
" \"email\": \"keinasylum@gmail.com\"\n" +
|
||||
"}";
|
||||
|
||||
private String encMechId = "rbkmoney";
|
||||
|
||||
@Before
|
||||
public void setUp() throws URISyntaxException {
|
||||
client = new THClientBuilder().withAddress(new URI(url)).build(PaymentToolProviderSrv.Iface.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCard() throws TException {
|
||||
UnwrappedPaymentTool res = client.unwrap(new WrappedPaymentTool(PaymentRequest.google(new GooglePayRequest(encMechId, toContent(encMsg)))));
|
||||
assertEquals("4111111111111111", res.getPaymentData().getCard().getPan());
|
||||
assertEquals(new ExpDate((byte) 12, (short) 2023), res.getPaymentData().getCard().getExpDate());
|
||||
|
||||
assertEquals(CardClass.credit, res.getCardInfo().getCardClass());
|
||||
assertEquals(BankCardPaymentSystem.visa, res.getCardInfo().getPaymentSystem());
|
||||
assertEquals("Visa •••• 9391", res.getCardInfo().getDisplayName());
|
||||
assertEquals("9391", res.getCardInfo().getLast4Digits());
|
||||
|
||||
assertEquals("2018-06-05T12:36:44.549Z", res.getDetails().getGoogle().getMessageExpiration());
|
||||
assertEquals("AH2EjtfIuPojiNSCv4SarecQOmML6hX1x-0yOZImfiPubV_vd6vVc0vScpQV0ExK1eJ2C9_D1KSeg4T45QyurCF6wTCJMoIgJR4zYcak0cAZ6XXOUk_hs72b45NLHbHEvAziG7ZJmLhv", res.getDetails().getGoogle().getMessageId());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidRequest.class)
|
||||
public void testWrongMerchantId() throws TException {
|
||||
client.unwrap(new WrappedPaymentTool(PaymentRequest.google(new GooglePayRequest(encMechId+"1", toContent(encMsg)))));
|
||||
}
|
||||
|
||||
private Content toContent(String data) {
|
||||
return new Content("application/json", ByteBuffer.wrap(data.getBytes()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
1
src/test/resources/keys.txt
Normal file
1
src/test/resources/keys.txt
Normal file
@ -0,0 +1 @@
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAx+oQpiveM7fhYBq8KTOdfzD3VUn1RfB4t7DCViMtIuhRANCAAQqmrw+gIJIqjpRLurRl7RjQ3LTJUrT8O3ZNG6/xxRPhjue4xcvBPFtRg0BnVB8sxSNjhGD+t2s4soIIp6YRYf/
|
Loading…
Reference in New Issue
Block a user