InitCommit

This commit is contained in:
Struchkov Mark 2023-04-26 13:03:14 +03:00
commit 89d99f4bf7
Signed by: upagge
GPG Key ID: D3018BE7BA428CA6
47 changed files with 1556 additions and 0 deletions

77
.gitignore vendored Normal file
View File

@ -0,0 +1,77 @@
*.class
*.log
*.ctxt
.mtj.tmp/
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
hs_err_pid*
replay_pid*
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.project
.classpath
.idea/
cmake-build-*/
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
.gradle
**/build/
!src/**/build/
gradle-app.setting
!gradle-wrapper.jar
!gradle-wrapper.properties
.gradletasknamecache
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msix
*.msm
*.msp
*.lnk
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

10
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Zeppelin ignored files
/ZeppelinRemoteNotebooks/

14
.idea/misc.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-m1-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

92
pom.xml Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-parent-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>yookassa-model</module>
<module>yookassa-context</module>
<module>yookassa-sdk</module>
</modules>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<lombok.version>1.18.26</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-context-quarkus</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.smallrye.reactive/mutiny -->
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
<version>2.16.6.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.15.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
</dependencyManagement>
<developers>
<developer>
<id>uPagge</id>
<name>Struchkov Mark</name>
<email>mark@struchkov.dev</email>
<url>https://mark.struchkov.dev</url>
</developer>
</developers>
</project>

38
yookassa-context/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

32
yookassa-context/pom.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-parent-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yookassa-context</artifactId>
<packaging>pom</packaging>
<modules>
<module>yookassa-context-quarkus</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-context</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yookassa-context-quarkus</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-model</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
package dev.struchkov.yookassa.sdk.context;
import dev.struchkov.yookassa.sdk.model.webhook.YooKassaNotification;
import io.smallrye.mutiny.Uni;
public interface YooKassaWebhookConsumer {
Uni<Boolean> callback(YooKassaNotification notification);
}

38
yookassa-model/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

35
yookassa-model/pom.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-parent-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yookassa-model</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package dev.struchkov.yookassa.sdk.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.util.Currency;
public class CurrencyDeserializer extends StdDeserializer<Currency> {
public CurrencyDeserializer() {
super(Currency.class);
}
@Override
public Currency deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String currencyCode = p.getValueAsString();
return Currency.getInstance(currencyCode);
}
}

View File

@ -0,0 +1,21 @@
package dev.struchkov.yookassa.sdk.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Currency;
public class CurrencySerializer extends StdSerializer<Currency> {
public CurrencySerializer() {
super(Currency.class);
}
@Override
public void serialize(Currency value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.getCurrencyCode());
}
}

View File

@ -0,0 +1,7 @@
package dev.struchkov.yookassa.sdk.model;
public @interface Required {
String[] alternative() default "";
}

View File

@ -0,0 +1,12 @@
package dev.struchkov.yookassa.sdk.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class YooKassaProp {
private String webhookAccessKey;
}

View File

@ -0,0 +1,40 @@
package dev.struchkov.yookassa.sdk.model.common;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import dev.struchkov.yookassa.sdk.jackson.CurrencyDeserializer;
import dev.struchkov.yookassa.sdk.jackson.CurrencySerializer;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.Currency;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Amount {
/**
* Сумма в выбранной валюте. Выражается в виде строки и пишется через точку, например 10.00. Количество знаков после точки зависит от выбранной валюты.
*/
@Required
private String value;
/**
* Трехбуквенный код валюты в формате <a href="https://www.iso.org/iso-4217-currency-codes.html">ISO-4217</a>. Пример: RUB. Должен соответствовать валюте субаккаунта (recipient.gateway_id), если вы разделяете потоки платежей, и валюте аккаунта (shopId в <a href="https://yookassa.ru/my">личном кабинете</a>), если не разделяете.
*/
@JsonSerialize(using = CurrencySerializer.class)
@JsonDeserialize(using = CurrencyDeserializer.class)
@Required
private Currency currency;
}

View File

@ -0,0 +1,44 @@
package dev.struchkov.yookassa.sdk.model.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Customer {
/**
* Для юрлица название организации, для ИП и физического лица ФИО. Если у физлица отсутствует ИНН, в этом же параметре передаются паспортные данные. Не более 256 символов.
* Онлайн-кассы, которые поддерживают этот параметр: <b>Orange Data, Атол Онлайн.</b>
*/
@JsonProperty("full_name")
private String fullName;
/**
* ИНН пользователя (10 или 12 цифр). Если у физического лица отсутствует ИНН, необходимо передать паспортные данные в параметре full_name.
* Онлайн-кассы, которые поддерживают этот параметр: Orange Data, Атол Онлайн.
*/
private String inn;
/**
* Электронная почта пользователя для отправки чека. Обязательный параметр, если не передан phone.
*/
@Required(alternative = "phone")
private String email;
/**
* Телефон пользователя для отправки чека. Указывается в формате ITU-T E.164, например 79000000000. Обязательный параметр, если не передан email.
*/
@Required(alternative = "email")
private String phone;
}

View File

@ -0,0 +1,101 @@
package dev.struchkov.yookassa.sdk.model.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Item {
/**
* Название товара (не более 128 символов). Тег в 54 ФЗ 1030.
*/
@Required
private String description;
/**
* Цена товара (тег в 54 ФЗ 1079).
*/
@Required
private Amount amount;
/**
* Ставка НДС (тег в 54 ФЗ 1199).
* Для чеков по 54-ФЗ: возможные значения числа от 1 до 6. Подробнее про коды ставок НДС
* Для чеков самозанятых: фиксированное значение 1.
*/
@Required
@JsonProperty("vat_code")
private Integer vatCode;
/**
* Количество товара (тег в 54 ФЗ 1023).
* Для чеков по 54-ФЗ: максимально возможное значение зависит от модели вашей онлайн-кассы.
* Для чеков самозанятых: только целые положительные числа (без разделителя и дробной части). Пример: 1.
*/
@Required
private String quantity;
/**
* Мера количества предмета расчета (тег в 54 ФЗ 2108) единица измерения товара, например штуки, граммы. Обязателен при использовании ФФД 1.2. <a href="https://yookassa.ru/developers/payment-acceptance/scenario-extensions/receipts/54fz/parameters-values#measure">Перечень возможных значений</a>
*/
private String measure;
/**
* Дробное количество маркированного товара (тег в 54 ФЗ 1291). Нужно передавать при одновременном выполнении следующих условий:
* используется ФФД версии 1.2;
* расчет осуществляется за маркированный товар;
* поле measure имеет значение piece.
* Пример: вы продаете поштучно карандаши. Они поставляются пачками по 100 штук с одним кодом маркировки. При продаже одного карандаша нужно в numerator передать 1, а в denominator 100.
*/
@JsonProperty("mark_quantity")
private MarkQuantity markQuantity;
/**
* Признак предмета расчета (тег в 54 ФЗ 1212) это то, за что принимается оплата, например товар, услуга. <a href="https://yookassa.ru/developers/payment-acceptance/scenario-extensions/receipts/54fz/parameters-values#payment-subject">Перечень возможных значений </a>
*/
@JsonProperty("payment_subject")
private String paymentSubject;
/**
* Признак способа расчета (тег в 54 ФЗ 1214) отражает тип оплаты и факт передачи товара. Пример: покупатель полностью оплачивает товар и сразу получает его. В этом случае нужно передать значение full_payment (полный расчет). <a href="https://yookassa.ru/developers/payment-acceptance/scenario-extensions/receipts/54fz/parameters-values#payment-mode">Перечень возможных значений</a>
*/
@JsonProperty("payment_mode")
private String paymentMode;
/**
* Код страны происхождения товара по общероссийскому классификатору стран мира (OК (MК (ИСО 3166) 004-97) 025-2001). Тег в 54 ФЗ 1230. Пример: RU.
* Онлайн-кассы, которые поддерживают этот параметр: Orange Data, Кит Инвест.
*/
@JsonProperty("country_of_origin_code")
private String countryOfOriginCode;
/**
* Номер таможенной декларации (от 1 до 32 символов). Тег в 54 ФЗ 1231.
* Онлайн-кассы, которые поддерживают этот параметр: Orange Data, Кит Инвест.
*/
@JsonProperty("customs_declaration_number")
private String customsDeclarationNumber;
/**
* Сумма акциза товара с учетом копеек (тег в 54 ФЗ 1229). Десятичное число с точностью до 2 символов после точки.
* Онлайн-кассы, которые поддерживают этот параметр: Orange Data, Кит Инвест.
*/
private String excise;
/**
* Код товара (тег в 54 ФЗ 1162) уникальный номер, который присваивается экземпляру товара при маркировке.
* Формат: число в шестнадцатеричном представлении с пробелами. Максимальная длина 32 байта. Пример: 00 00 00 01 00 21 FA 41 00 23 05 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 00 AB 00.
* Обязательный параметр, если вы используете ФФД 1.05 и товар нужно маркировать.
*/
private String productCode;
}

View File

@ -0,0 +1,30 @@
package dev.struchkov.yookassa.sdk.model.common;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MarkQuantity {
/**
* Числитель количество продаваемых товаров из одной потребительской упаковки (тег в 54 ФЗ 1293). Не может превышать denominator.
*/
@Required
private Integer numerator;
/**
* Знаменатель общее количество товаров в потребительской упаковке (тег в 54 ФЗ 1294).
*/
@Required
private Integer denominator;
}

View File

@ -0,0 +1,40 @@
package dev.struchkov.yookassa.sdk.model.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.Map;
@Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class Payment {
private boolean capture;
/**
* Описание транзакции (не более 128 символов), которое вы увидите в личном кабинете ЮKassa, а пользователь при оплате. Например: «Оплата заказа 72 для user@yoomoney.ru».
*/
private String description;
/**
* Сумма платежа. Иногда партнеры ЮKassa берут с пользователя дополнительную комиссию, которая не входит в эту сумму.
*/
@Required
protected Amount amount;
/**
* Любые дополнительные данные, которые нужны вам для работы (например, ваш внутренний идентификатор заказа). Передаются в виде набора пар «ключ-значение» и возвращаются в ответе от ЮKassa. Ограничения: максимум 16 ключей, имя ключа не больше 32 символов, значение ключа не больше 512 символов, тип данных строка в формате UTF-8.
*/
@JsonProperty("metadata")
private Map<String, String> metadata;
}

View File

@ -0,0 +1,34 @@
package dev.struchkov.yookassa.sdk.model.common;
import dev.struchkov.yookassa.sdk.model.Required;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Singular;
import java.util.List;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Receipt {
/**
* Информация о пользователе. Необходимо указать как минимум контактные данные: электронную почту (customer.email) или номер телефона (customer.phone).
*/
@Required
private Customer customer;
/**
* Список товаров в заказе. Для чеков по 54-ФЗ можно передать не более 100 товаров, для чеков самозанятых не более шести.
*/
@Required
@Singular
private List<Item> items;
}

View File

@ -0,0 +1,26 @@
package dev.struchkov.yookassa.sdk.model.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Recipient {
@JsonProperty("account_id")
private String accountId;
@JsonProperty("gateway_id")
private String gatewayId;
}

View File

@ -0,0 +1,18 @@
package dev.struchkov.yookassa.sdk.model.exception;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class YookassaApiException extends RuntimeException {
private String type;
private String id;
private String code;
private String description;
private String parameter;
}

View File

@ -0,0 +1,52 @@
package dev.struchkov.yookassa.sdk.model.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.common.Payment;
import dev.struchkov.yookassa.sdk.model.common.Receipt;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.UUID;
@Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PaymentRequest extends Payment {
/**
* Данные, необходимые для инициирования выбранного сценария подтверждения платежа пользователем. Подробнее о <a href="https://yookassa.ru/developers/payment-acceptance/getting-started/payment-process#user-confirmation">сценариях подтверждения</a>
*/
private Confirmation confirmation;
/**
* Данные для формирования чека.
* Необходимо передавать в этих случаях:
* вы компания или ИП, используете решение ЮKassa для оплаты по 54-ФЗ и отправляете данные для формирования чеков по одному из сценариев: Платеж и чек одновременно или Сначала чек, потом платеж ;
* вы самозанятый и используете решение ЮKassa для автоотправки чеков .
*/
private Receipt receipt;
private UUID idempotenceKey;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Confirmation {
private String type;
@JsonProperty("return_url")
private String returnUrl;
}
}

View File

@ -0,0 +1,64 @@
package dev.struchkov.yookassa.sdk.model.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.common.Amount;
import dev.struchkov.yookassa.sdk.model.common.Payment;
import dev.struchkov.yookassa.sdk.model.common.Recipient;
import dev.struchkov.yookassa.sdk.model.webhook.PaymentStatus;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.ZonedDateTime;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PaymentResponse extends Payment {
private String id;
@JsonProperty("status")
private PaymentStatus status;
private boolean paid;
private Amount amount;
private Confirmation confirmation;
/**
* Время создания заказа. Указывается по UTC и передается в формате ISO 8601. Пример: 2017-11-03T11:52:31.827Z
*/
@JsonProperty("created_at")
private ZonedDateTime createdAt;
private String description;
private Recipient recipient;
private boolean refundable;
private boolean test;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Confirmation {
@JsonProperty("type")
private String type;
@JsonProperty("confirmation_url")
private String confirmationUrl;
}
}

View File

@ -0,0 +1,16 @@
package dev.struchkov.yookassa.sdk.model.response;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class YooKassaApiErrorResponse {
private String type;
private String id;
private String code;
private String description;
private String parameter;
}

View File

@ -0,0 +1,38 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AuthorizationDetails {
/**
* Retrieval Reference Number уникальный идентификатор транзакции в системе эмитента. Используется при оплате банковской картой.
*/
@JsonProperty("rrn")
private String rrn;
/**
* Код авторизации банковской карты. Выдается эмитентом и подтверждает проведение авторизации.
*/
@JsonProperty("auth_code")
private String authCode;
/**
* Данные о прохождении пользователем аутентификации по 3D Secure для подтверждения платежа.
*/
@JsonProperty("three_d_secure")
private ThreeDSecure threeDSecure;
}

View File

@ -0,0 +1,29 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class CancellationDetails {
/**
* Участник процесса платежа, который принял решение об отмене транзакции. Может принимать значения yoo_money, payment_network и merchant. <a href="https://yookassa.ru/developers/payment-acceptance/after-the-payment/declined-payments#cancellation-details-party">Подробнее про инициаторов отмены платежа</a>
*/
private Party party;
/**
* Причина отмены платежа. <a href="https://yookassa.ru/developers/payment-acceptance/after-the-payment/declined-payments#cancellation-details-reason">Перечень и описание возможных значений</a>
*/
private String reason;
}

View File

@ -0,0 +1,36 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Card {
@JsonProperty("first6")
private String first6;
@JsonProperty("last4")
private String last4;
@JsonProperty("expiry_year")
private String expiryYear;
@JsonProperty("expiry_month")
private String expiryMonth;
@JsonProperty("card_type")
private String cardType;
@JsonProperty("issuer_country")
private String issuerCountry;
}

View File

@ -0,0 +1,28 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum NotificationEvent {
@JsonProperty("payment.waiting_for_capture")
PAYMENT_WAITING_FOR_CAPTURE,
@JsonProperty("payment.succeeded")
PAYMENT_SUCCEEDED,
@JsonProperty("payment.canceled")
PAYMENT_CANCELED,
@JsonProperty("refund.succeeded")
REFUND_SUCCEEDED,
@JsonProperty("deal.closed")
DEAL_CLOSED,
@JsonProperty("payout.canceled")
PAYOUT_CANCELED,
@JsonProperty("payout.succeeded")
PAYOUT_SUCCEEDED
}

View File

@ -0,0 +1,16 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum Party {
@JsonProperty("yoo_money")
YOO_MONEY,
@JsonProperty("payment_network")
PAYMENT_NETWORK,
@JsonProperty("merchant")
MERCHANT;
}

View File

@ -0,0 +1,35 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PaymentMethod {
@JsonProperty("type")
private String type;
@JsonProperty("id")
private String id;
@JsonProperty("saved")
private boolean saved;
@JsonProperty("title")
private String title;
@JsonProperty("card")
private Card card;
}

View File

@ -0,0 +1,19 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum PaymentStatus {
@JsonProperty("pending")
PENDING,
@JsonProperty("waiting_for_capture")
WAITING_FOR_CAPTURE,
@JsonProperty("succeeded")
SUCCEEDED,
@JsonProperty("canceled")
CANCELED
}

View File

@ -0,0 +1,69 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.struchkov.yookassa.sdk.model.common.Amount;
import dev.struchkov.yookassa.sdk.model.common.Payment;
import dev.struchkov.yookassa.sdk.model.common.Recipient;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.time.ZonedDateTime;
@Getter
@Setter
@ToString
public class PaymentWebhook extends Payment {
@JsonProperty("id")
private String id;
@JsonProperty("status")
private PaymentStatus status;
@JsonProperty("income_amount")
private Amount incomeAmount;
@JsonProperty("recipient")
private Recipient recipient;
@JsonProperty("payment_method")
private PaymentMethod paymentMethod;
/**
* Время подтверждения платежа. Указывается по UTC и передается в формате ISO 8601.
*/
@JsonProperty("captured_at")
private ZonedDateTime capturedAt;
/**
* Время создания заказа. Указывается по UTC и передается в формате ISO 8601. Пример: 2017-11-03T11:52:31.827Z
*/
@JsonProperty("created_at")
private ZonedDateTime createdAt;
@JsonProperty("test")
private boolean test;
@JsonProperty("refunded_amount")
private Amount refundedAmount;
/**
* Признак оплаты заказа.
*/
@JsonProperty("paid")
private boolean paid;
/**
* Возможность провести возврат по API.
*/
@JsonProperty("refundable")
private boolean refundable;
@JsonProperty("authorization_details")
private AuthorizationDetails authorizationDetails;
@JsonProperty("cancellation_details")
private CancellationDetails cancellationDetails;
}

View File

@ -0,0 +1,27 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ThreeDSecure {
@JsonProperty("applied")
private boolean applied;
@JsonProperty("method_completed")
private boolean methodCompleted;
@JsonProperty("challenge_completed")
private boolean challengeCompleted;
}

View File

@ -0,0 +1,27 @@
package dev.struchkov.yookassa.sdk.model.webhook;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class YooKassaNotification {
private String type;
private NotificationEvent event;
@JsonProperty("object")
private PaymentWebhook paymentObject;
}

38
yookassa-sdk/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

25
yookassa-sdk/pom.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-parent-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yookassa-sdk</artifactId>
<packaging>pom</packaging>
<modules>
<module>yookassa-quarkus-sdk</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yookassa-quarkus-sdk</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>dev.struchkov.yookassa</groupId>
<artifactId>yookassa-context-quarkus</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- jandex необходим для генерируемого rest клиента! -->
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>3.0.5</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,37 @@
package dev.struchkov.yookassa.sdk;
import dev.struchkov.yookassa.sdk.model.request.PaymentRequest;
import dev.struchkov.yookassa.sdk.model.response.PaymentResponse;
import dev.struchkov.yookassa.sdk.util.IdempotenceKeyClientFilter;
import dev.struchkov.yookassa.sdk.util.YookassaExceptionHandler;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("v3/payments")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(baseUri = "https://api.yookassa.ru")
@ClientHeaderParam(name = "Authorization", value = "${yookassa.token}")
@RegisterProvider(YookassaExceptionHandler.class)
@RegisterProvider(IdempotenceKeyClientFilter.class)
public interface YooKassaPaymentClient {
default String getIdempotenceKeyFromPaymentRequest(String headerName, PaymentRequest paymentRequest) {
if ("Idempotence-Key".equals(headerName)) {
return paymentRequest.getIdempotenceKey().toString();
}
throw new UnsupportedOperationException("unknown header name");
}
@POST()
Uni<PaymentResponse> sendInvoice(PaymentRequest paymentRequest);
}

View File

@ -0,0 +1,50 @@
package dev.struchkov.yookassa.sdk;
import dev.struchkov.yookassa.sdk.context.YooKassaWebhookConsumer;
import dev.struchkov.yookassa.sdk.model.YooKassaProp;
import dev.struchkov.yookassa.sdk.model.webhook.YooKassaNotification;
import io.smallrye.mutiny.Uni;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static java.lang.Boolean.TRUE;
@Slf4j
@Path("yookassa")
@RequiredArgsConstructor
public class YooKassaWebhookController {
private final YooKassaProp yooKassaProp;
private final YooKassaWebhookConsumer consumer;
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<Response> callback(YooKassaNotification notification, @QueryParam("accessKey") String accessKey) {
if (yooKassaProp != null) {
final String webhookAccessKey = yooKassaProp.getWebhookAccessKey();
if (webhookAccessKey != null && (!webhookAccessKey.equals(accessKey))) {
log.warn("Попытка несанкционированного доступа к YooKassa Webhook. Переданный accessKey: {}. Запрос: {}", accessKey, notification);
return Uni.createFrom().item(Response.status(403).build());
}
}
return consumer.callback(notification)
.onItem().ifNull().fail()
.onItem().ifNotNull().transform(flag -> {
if (TRUE.equals(flag)) {
return Response.ok().build();
} else {
return Response.status(405).build();
}
});
}
}

View File

@ -0,0 +1,19 @@
package dev.struchkov.yookassa.sdk.util;
import dev.struchkov.yookassa.sdk.model.request.PaymentRequest;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import java.io.IOException;
public class IdempotenceKeyClientFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
Object entity = requestContext.getEntity();
if (entity instanceof PaymentRequest paymentRequest) {
requestContext.getHeaders().add("Idempotence-Key", paymentRequest.getIdempotenceKey().toString());
}
}
}

View File

@ -0,0 +1,26 @@
package dev.struchkov.yookassa.sdk.util;
import dev.struchkov.yookassa.sdk.model.exception.YookassaApiException;
import dev.struchkov.yookassa.sdk.model.response.YooKassaApiErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import javax.ws.rs.core.Response;
@Slf4j
public class YookassaExceptionHandler implements ResponseExceptionMapper<Exception> {
@Override
public Exception toThrowable(Response r) {
final YooKassaApiErrorResponse errorResponse = r.readEntity(YooKassaApiErrorResponse.class);
final YookassaApiException exception = new YookassaApiException();
exception.setType(errorResponse.getType());
exception.setId(errorResponse.getId());
exception.setCode(errorResponse.getCode());
exception.setDescription(errorResponse.getDescription());
exception.setParameter(errorResponse.getParameter());
return exception;
}
}