commit 89d99f4bf7b45f4dfce62bdba431e1859a3ea417 Author: Struchkov Mark Date: Wed Apr 26 13:03:14 2023 +0300 InitCommit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8edcd08 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0a8642f --- /dev/null +++ b/.idea/.gitignore @@ -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/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..badfc05 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb new file mode 100644 index 0000000..e69de29 diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b4e71b4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-parent-sdk + 1.0-SNAPSHOT + pom + + + yookassa-model + yookassa-context + yookassa-sdk + + + + 17 + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + 1.18.26 + + + + + + dev.struchkov.yookassa + yookassa-model + 1.0-SNAPSHOT + + + dev.struchkov.yookassa + yookassa-context-quarkus + 1.0-SNAPSHOT + + + + + io.smallrye.reactive + mutiny + 2.1.0 + + + + io.quarkus + quarkus-rest-client-reactive-jackson + 2.16.6.Final + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.15.0 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.0 + + + + org.projectlombok + lombok + ${lombok.version} + + + + + org.slf4j + slf4j-api + 2.0.7 + + + + + + + uPagge + Struchkov Mark + mark@struchkov.dev + https://mark.struchkov.dev + + + + \ No newline at end of file diff --git a/yookassa-context/.gitignore b/yookassa-context/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/yookassa-context/.gitignore @@ -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 \ No newline at end of file diff --git a/yookassa-context/pom.xml b/yookassa-context/pom.xml new file mode 100644 index 0000000..29a5541 --- /dev/null +++ b/yookassa-context/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-parent-sdk + 1.0-SNAPSHOT + + + yookassa-context + pom + + + yookassa-context-quarkus + + + + 17 + 17 + UTF-8 + + + + + io.smallrye.reactive + mutiny + + + + \ No newline at end of file diff --git a/yookassa-context/yookassa-context-quarkus/.gitignore b/yookassa-context/yookassa-context-quarkus/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/yookassa-context/yookassa-context-quarkus/.gitignore @@ -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 \ No newline at end of file diff --git a/yookassa-context/yookassa-context-quarkus/pom.xml b/yookassa-context/yookassa-context-quarkus/pom.xml new file mode 100644 index 0000000..8b72b80 --- /dev/null +++ b/yookassa-context/yookassa-context-quarkus/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-context + 1.0-SNAPSHOT + + + yookassa-context-quarkus + + + 17 + 17 + UTF-8 + + + + + dev.struchkov.yookassa + yookassa-model + + + + \ No newline at end of file diff --git a/yookassa-context/yookassa-context-quarkus/src/main/java/dev/struchkov/yookassa/sdk/context/YooKassaWebhookConsumer.java b/yookassa-context/yookassa-context-quarkus/src/main/java/dev/struchkov/yookassa/sdk/context/YooKassaWebhookConsumer.java new file mode 100644 index 0000000..cb1bb2c --- /dev/null +++ b/yookassa-context/yookassa-context-quarkus/src/main/java/dev/struchkov/yookassa/sdk/context/YooKassaWebhookConsumer.java @@ -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 callback(YooKassaNotification notification); + +} diff --git a/yookassa-model/.gitignore b/yookassa-model/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/yookassa-model/.gitignore @@ -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 \ No newline at end of file diff --git a/yookassa-model/pom.xml b/yookassa-model/pom.xml new file mode 100644 index 0000000..3b0a291 --- /dev/null +++ b/yookassa-model/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-parent-sdk + 1.0-SNAPSHOT + + + yookassa-model + + + 17 + 17 + UTF-8 + + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + org.projectlombok + lombok + + + + \ No newline at end of file diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencyDeserializer.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencyDeserializer.java new file mode 100644 index 0000000..52efa1b --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencyDeserializer.java @@ -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 { + + public CurrencyDeserializer() { + super(Currency.class); + } + + @Override + public Currency deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + String currencyCode = p.getValueAsString(); + return Currency.getInstance(currencyCode); + } + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencySerializer.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencySerializer.java new file mode 100644 index 0000000..6c92888 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/jackson/CurrencySerializer.java @@ -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 { + + public CurrencySerializer() { + super(Currency.class); + } + + @Override + public void serialize(Currency value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.getCurrencyCode()); + } + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/Required.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/Required.java new file mode 100644 index 0000000..1c446c6 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/Required.java @@ -0,0 +1,7 @@ +package dev.struchkov.yookassa.sdk.model; + +public @interface Required { + + String[] alternative() default ""; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/YooKassaProp.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/YooKassaProp.java new file mode 100644 index 0000000..2b2d775 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/YooKassaProp.java @@ -0,0 +1,12 @@ +package dev.struchkov.yookassa.sdk.model; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class YooKassaProp { + + private String webhookAccessKey; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Amount.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Amount.java new file mode 100644 index 0000000..f54cffd --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Amount.java @@ -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; + + /** + * Трехбуквенный код валюты в формате ISO-4217. Пример: RUB. Должен соответствовать валюте субаккаунта (recipient.gateway_id), если вы разделяете потоки платежей, и валюте аккаунта (shopId в личном кабинете), если не разделяете. + */ + @JsonSerialize(using = CurrencySerializer.class) + @JsonDeserialize(using = CurrencyDeserializer.class) + @Required + private Currency currency; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Customer.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Customer.java new file mode 100644 index 0000000..7546b03 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Customer.java @@ -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 символов. + * Онлайн-кассы, которые поддерживают этот параметр: Orange Data, Атол Онлайн. + */ + @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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Item.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Item.java new file mode 100644 index 0000000..66a31da --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Item.java @@ -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. Перечень возможных значений + */ + private String measure; + + /** + * Дробное количество маркированного товара (тег в 54 ФЗ — 1291). Нужно передавать при одновременном выполнении следующих условий: + * используется ФФД версии 1.2; + * расчет осуществляется за маркированный товар; + * поле measure имеет значение piece. + * Пример: вы продаете поштучно карандаши. Они поставляются пачками по 100 штук с одним кодом маркировки. При продаже одного карандаша нужно в numerator передать 1, а в denominator — 100. + */ + @JsonProperty("mark_quantity") + private MarkQuantity markQuantity; + + /** + * Признак предмета расчета (тег в 54 ФЗ — 1212) — это то, за что принимается оплата, например товар, услуга. Перечень возможных значений + */ + @JsonProperty("payment_subject") + private String paymentSubject; + + /** + * Признак способа расчета (тег в 54 ФЗ — 1214) — отражает тип оплаты и факт передачи товара. Пример: покупатель полностью оплачивает товар и сразу получает его. В этом случае нужно передать значение full_payment (полный расчет). Перечень возможных значений + */ + @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; +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/MarkQuantity.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/MarkQuantity.java new file mode 100644 index 0000000..7f9307e --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/MarkQuantity.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Payment.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Payment.java new file mode 100644 index 0000000..b802e72 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Payment.java @@ -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 metadata; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Receipt.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Receipt.java new file mode 100644 index 0000000..9f72f17 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Receipt.java @@ -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 items; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Recipient.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Recipient.java new file mode 100644 index 0000000..006b778 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/common/Recipient.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/exception/YookassaApiException.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/exception/YookassaApiException.java new file mode 100644 index 0000000..d5c2379 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/exception/YookassaApiException.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/request/PaymentRequest.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/request/PaymentRequest.java new file mode 100644 index 0000000..f16c660 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/request/PaymentRequest.java @@ -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 { + + /** + * Данные, необходимые для инициирования выбранного сценария подтверждения платежа пользователем. Подробнее о сценариях подтверждения + */ + 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; + + } + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/PaymentResponse.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/PaymentResponse.java new file mode 100644 index 0000000..f14ee49 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/PaymentResponse.java @@ -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; + + } + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/YooKassaApiErrorResponse.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/YooKassaApiErrorResponse.java new file mode 100644 index 0000000..241db27 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/response/YooKassaApiErrorResponse.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/AuthorizationDetails.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/AuthorizationDetails.java new file mode 100644 index 0000000..a6a8688 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/AuthorizationDetails.java @@ -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; + + /** + * Данные о прохождении пользователем аутентификации по 3‑D Secure для подтверждения платежа. + */ + @JsonProperty("three_d_secure") + private ThreeDSecure threeDSecure; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/CancellationDetails.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/CancellationDetails.java new file mode 100644 index 0000000..d194733 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/CancellationDetails.java @@ -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. Подробнее про инициаторов отмены платежа + */ + private Party party; + + /** + * Причина отмены платежа. Перечень и описание возможных значений + */ + private String reason; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Card.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Card.java new file mode 100644 index 0000000..8430402 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Card.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/NotificationEvent.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/NotificationEvent.java new file mode 100644 index 0000000..6fba00c --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/NotificationEvent.java @@ -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 + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Party.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Party.java new file mode 100644 index 0000000..fc2bdfe --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/Party.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentMethod.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentMethod.java new file mode 100644 index 0000000..134f7ae --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentMethod.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentStatus.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentStatus.java new file mode 100644 index 0000000..40b5f55 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentStatus.java @@ -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 + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentWebhook.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentWebhook.java new file mode 100644 index 0000000..b0a3b22 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/PaymentWebhook.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/ThreeDSecure.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/ThreeDSecure.java new file mode 100644 index 0000000..1d45181 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/ThreeDSecure.java @@ -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; + +} diff --git a/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/YooKassaNotification.java b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/YooKassaNotification.java new file mode 100644 index 0000000..8fab4b7 --- /dev/null +++ b/yookassa-model/src/main/java/dev/struchkov/yookassa/sdk/model/webhook/YooKassaNotification.java @@ -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; + +} diff --git a/yookassa-sdk/.gitignore b/yookassa-sdk/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/yookassa-sdk/.gitignore @@ -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 \ No newline at end of file diff --git a/yookassa-sdk/pom.xml b/yookassa-sdk/pom.xml new file mode 100644 index 0000000..05676f0 --- /dev/null +++ b/yookassa-sdk/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-parent-sdk + 1.0-SNAPSHOT + + + yookassa-sdk + pom + + + yookassa-quarkus-sdk + + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/yookassa-sdk/yookassa-quarkus-sdk/.gitignore b/yookassa-sdk/yookassa-quarkus-sdk/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/.gitignore @@ -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 \ No newline at end of file diff --git a/yookassa-sdk/yookassa-quarkus-sdk/pom.xml b/yookassa-sdk/yookassa-quarkus-sdk/pom.xml new file mode 100644 index 0000000..da8d298 --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + dev.struchkov.yookassa + yookassa-sdk + 1.0-SNAPSHOT + + + yookassa-quarkus-sdk + + + 17 + 17 + UTF-8 + + + + + dev.struchkov.yookassa + yookassa-context-quarkus + + + org.projectlombok + lombok + + + io.quarkus + quarkus-rest-client-reactive-jackson + + + + + + + + io.smallrye + jandex-maven-plugin + 3.0.5 + + + make-index + + jandex + + + + + + + + \ No newline at end of file diff --git a/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaPaymentClient.java b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaPaymentClient.java new file mode 100644 index 0000000..f41744d --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaPaymentClient.java @@ -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 sendInvoice(PaymentRequest paymentRequest); + +} diff --git a/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaWebhookController.java b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaWebhookController.java new file mode 100644 index 0000000..d7d3a23 --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/YooKassaWebhookController.java @@ -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 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(); + } + }); + } + +} diff --git a/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/IdempotenceKeyClientFilter.java b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/IdempotenceKeyClientFilter.java new file mode 100644 index 0000000..5dfb8ee --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/IdempotenceKeyClientFilter.java @@ -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()); + } + } + +} diff --git a/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/YookassaExceptionHandler.java b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/YookassaExceptionHandler.java new file mode 100644 index 0000000..a51c5dd --- /dev/null +++ b/yookassa-sdk/yookassa-quarkus-sdk/src/main/java/dev/struchkov/yookassa/sdk/util/YookassaExceptionHandler.java @@ -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 { + + @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; + } + +}