Skip to content

Commit

Permalink
Merge pull request #86 from Tinkoff/2.5.6
Browse files Browse the repository at this point in the history
v2.5.6
  • Loading branch information
IlnarH authored Apr 28, 2022
2 parents 6deb46f + 63e8a02 commit ffae269
Show file tree
Hide file tree
Showing 51 changed files with 727 additions and 99 deletions.
9 changes: 9 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 2.5.6

#### Fixed
User email is trimmed of whitespaces before further processing now
#### Changes
Added "MIR" cards support for payments via Google Pay
Added payment via Tinkoff Pay
#### Additions

## 2.5.5

#### Fixed
Expand Down
27 changes: 27 additions & 0 deletions core/src/main/java/ru/tinkoff/acquiring/sdk/AcquiringSdk.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package ru.tinkoff.acquiring.sdk
import ru.tinkoff.acquiring.sdk.loggers.JavaLogger
import ru.tinkoff.acquiring.sdk.loggers.Logger
import ru.tinkoff.acquiring.sdk.requests.*
import ru.tinkoff.acquiring.sdk.responses.TinkoffPayStatusResponse
import ru.tinkoff.acquiring.sdk.utils.keycreators.KeyCreator
import ru.tinkoff.acquiring.sdk.utils.keycreators.StringKeyCreator
import java.security.PublicKey
Expand All @@ -39,6 +40,8 @@ class AcquiringSdk(
private val publicKey: PublicKey
) {

var tinkoffPayStatusCache: TinkoffPayStatusCache? = null

constructor(terminalKey: String, publicKey: String) :
this(terminalKey, StringKeyCreator(publicKey))

Expand Down Expand Up @@ -185,6 +188,30 @@ class AcquiringSdk(
}
}

fun tinkoffPayStatus(request: (TinkoffPayStatusRequest.() -> Unit)? = null): TinkoffPayStatusRequest {
return TinkoffPayStatusRequest(this@AcquiringSdk.terminalKey).apply {
request?.invoke(this)
}
}

fun tinkoffPayLink(paymentId: Long, version: String, request: (TinkoffPayLinkRequest.() -> Unit)? = null): TinkoffPayLinkRequest {
return TinkoffPayLinkRequest(paymentId.toString(), version).apply {
request?.invoke(this)
}
}

class TinkoffPayStatusCache(
val status: TinkoffPayStatusResponse,
val time: Long) {

fun isExpired() = System.currentTimeMillis() - time > CACHE_EXPIRE_TIME_MS

companion object {

const val CACHE_EXPIRE_TIME_MS = 300_000L
}
}

companion object AsdkLogger {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ object AcquiringApi {

internal const val STREAM_BUFFER_SIZE = 4096
internal const val API_REQUEST_METHOD_POST = "POST"
internal const val API_REQUEST_METHOD_GET = "GET"

internal const val JSON = "application/json"
internal const val FORM_URL_ENCODED = "application/x-www-form-urlencoded"
Expand All @@ -80,7 +81,7 @@ object AcquiringApi {

private const val API_VERSION = "v2"
private const val API_URL_RELEASE = "https://securepay.tinkoff.ru/$API_VERSION"
private const val API_URL_DEBUG = "https://rest-api-test.tcsbank.ru/$API_VERSION"
private const val API_URL_DEBUG = "https://rest-api-test.tinkoff.ru/$API_VERSION"

private val oldMethodsList = listOf("Submit3DSAuthorization")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import ru.tinkoff.acquiring.sdk.models.enums.CardStatus
import ru.tinkoff.acquiring.sdk.models.enums.ResponseStatus
import ru.tinkoff.acquiring.sdk.models.enums.Tax
import ru.tinkoff.acquiring.sdk.models.enums.Taxation
import ru.tinkoff.acquiring.sdk.network.AcquiringApi.API_REQUEST_METHOD_POST
import ru.tinkoff.acquiring.sdk.network.AcquiringApi.FORM_URL_ENCODED
import ru.tinkoff.acquiring.sdk.network.AcquiringApi.JSON
import ru.tinkoff.acquiring.sdk.network.AcquiringApi.STREAM_BUFFER_SIZE
Expand Down Expand Up @@ -73,14 +72,25 @@ internal class NetworkClient {
try {
lateinit var connection: HttpURLConnection

prepareBody(request) { body ->
prepareConnection(request) {
connection = it
connection.setRequestProperty("Content-length", body.size.toString())
requestContentStream = connection.outputStream
requestContentStream?.write(body)
when (request.httpRequestMethod) {
AcquiringApi.API_REQUEST_METHOD_GET -> {
prepareConnection(request) {
connection = it

AcquiringSdk.log("=== Sending $API_REQUEST_METHOD_POST request to ${connection.url}")
AcquiringSdk.log("=== Sending ${request.httpRequestMethod} request to ${connection.url}")
}
}
AcquiringApi.API_REQUEST_METHOD_POST -> {
prepareBody(request) { body ->
prepareConnection(request) {
connection = it
connection.setRequestProperty("Content-length", body.size.toString())
requestContentStream = connection.outputStream
requestContentStream?.write(body)

AcquiringSdk.log("=== Sending ${request.httpRequestMethod} request to ${connection.url}")
}
}
}
}

Expand Down Expand Up @@ -136,10 +146,13 @@ internal class NetworkClient {
val connection = targetUrl.openConnection() as HttpURLConnection

with(connection) {
requestMethod = API_REQUEST_METHOD_POST
requestMethod = request.httpRequestMethod
connectTimeout = TIMEOUT
readTimeout = TIMEOUT
doOutput = true
doOutput = when (request.httpRequestMethod) {
AcquiringApi.API_REQUEST_METHOD_GET -> false
else -> true
}
setRequestProperty("Content-type", if (AcquiringApi.useV1Api(request.apiMethod)) FORM_URL_ENCODED else JSON)

if (request is FinishAuthorizeRequest && request.is3DsVersionV2()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package ru.tinkoff.acquiring.sdk.requests

import ru.tinkoff.acquiring.sdk.network.AcquiringApi
import ru.tinkoff.acquiring.sdk.network.NetworkClient
import ru.tinkoff.acquiring.sdk.responses.AcquiringResponse
import ru.tinkoff.acquiring.sdk.utils.Request
Expand All @@ -29,6 +30,8 @@ import java.util.*
*/
abstract class AcquiringRequest<R : AcquiringResponse>(internal val apiMethod: String) : Request<R> {

open val httpRequestMethod: String = AcquiringApi.API_REQUEST_METHOD_POST

internal lateinit var terminalKey: String
internal lateinit var publicKey: PublicKey
@Volatile
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright © 2020 Tinkoff Bank
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ru.tinkoff.acquiring.sdk.requests

import ru.tinkoff.acquiring.sdk.network.AcquiringApi
import ru.tinkoff.acquiring.sdk.responses.TinkoffPayLinkResponse

/**
* Возвращает deeplink для TinkoffPay в зависимости от версии
*/
class TinkoffPayLinkRequest(paymentId: String, version: String) :
AcquiringRequest<TinkoffPayLinkResponse>(
"TinkoffPay/transactions/${paymentId}/versions/${version}/link") {

override val httpRequestMethod: String = AcquiringApi.API_REQUEST_METHOD_GET

override fun asMap(): MutableMap<String, Any> = mutableMapOf()

override fun validate() = Unit

/**
* Синхронный вызов метода API
*/
override fun execute(onSuccess: (TinkoffPayLinkResponse) -> Unit, onFailure: (Exception) -> Unit) {
super.performRequest(this, TinkoffPayLinkResponse::class.java, onSuccess, onFailure)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright © 2020 Tinkoff Bank
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ru.tinkoff.acquiring.sdk.requests

import ru.tinkoff.acquiring.sdk.network.AcquiringApi
import ru.tinkoff.acquiring.sdk.responses.TinkoffPayStatusResponse

/**
* Определение доступности проведения TinkoffPay для Продавца
*/
class TinkoffPayStatusRequest(terminalKey: String) :
AcquiringRequest<TinkoffPayStatusResponse>("TinkoffPay/terminals/$terminalKey/status") {

override val httpRequestMethod: String = AcquiringApi.API_REQUEST_METHOD_GET

override fun asMap(): MutableMap<String, Any> = mutableMapOf()

override fun validate() = Unit

/**
* Синхронный вызов метода API
*/
override fun execute(onSuccess: (TinkoffPayStatusResponse) -> Unit, onFailure: (Exception) -> Unit) {
super.performRequest(this, TinkoffPayStatusResponse::class.java, onSuccess, onFailure)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright © 2020 Tinkoff Bank
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ru.tinkoff.acquiring.sdk.responses

import com.google.gson.annotations.SerializedName

/**
* Ответ на запрос TinkoffPayLink
*
* @param params Json-объект содержащий дополнительный параметр
*/
class TinkoffPayLinkResponse(
@SerializedName("Params")
val params: Params? = null

) : AcquiringResponse() {

/**
* @param redirectUrl URL для перехода в приложение Мобильный Банк
*/
class Params(
@SerializedName("RedirectUrl")
val redirectUrl: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright © 2020 Tinkoff Bank
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ru.tinkoff.acquiring.sdk.responses

import com.google.gson.annotations.SerializedName

/**
* Ответ на запрос TinkoffPayStatus
*
* @param params Json-объект содержащий дополнительные параметры
*/
class TinkoffPayStatusResponse(
@SerializedName("Params")
val params: Params? = null

) : AcquiringResponse() {

fun isTinkoffPayAvailable(): Boolean = params?.allowed == true

/**
* @return "1.0" - deeplink, "2.0" - applink
*/
fun getTinkoffPayVersion(): String? = params?.version

/**
* @param allowed Наличие возможности проведения оплаты TinkoffPay по API, SDK
* @param version Версия TinkoffPay, доступная на терминале:
* - 1.0 (e-invoice);
* - 2.0 (TinkoffPay)
*/
class Params(
@SerializedName("Allowed")
val allowed: Boolean,

@SerializedName("Version")
val version: String? = null
)
}
3 changes: 2 additions & 1 deletion core/src/test/java/ApiSdkUnitTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ru.tinkoff.acquiring.sdk.utils.TestPaymentData.TEST_PAY_FORM
import java.util.*
import kotlin.math.abs

/* // todo test environment
class ApiSdkUnitTest {
private val sdk: AcquiringSdk = AcquiringSdk(
Expand Down Expand Up @@ -358,4 +359,4 @@ class ApiSdkUnitTest {
})
return card
}
}
}*/
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION_NAME=2.5.5
VERSION_NAME=2.5.6
VERSION_CODE=15
GROUP=ru.tinkoff.acquiring

Expand Down
30 changes: 30 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Acquiring SDK позволяет интегрировать [Интернет-Э
- Кастомизация экранов SDK;
- Интеграция с онлайн-кассами;
- Поддержка Google Pay и Системы быстрых платежей
- Оплата через Tinkoff Pay
- Совершение оплаты из уведомления

### Требования
Expand Down Expand Up @@ -209,6 +210,35 @@ var paymentOptions = PaymentOptions().setOptions {
Метод openStaticQrScreen принимает параметры: activity, localization - для локализации сообщения на экране, requestCode - для получения ошибки, если таковая возникнет.
Результат оплаты товара покупателем по статическому QR коду не отслеживается в SDK, соответственно в onActivityResult вызывающего экран активити может вернуться только ошибка или отмена (закрытие экрана).

### Tinkoff Pay
Включение приема платежей через Tinkoff Pay осуществляется в Личном кабинете.
#### Включение приема оплаты через Tinkoff Pay по кнопке для покупателя:
При инициализации экрана оплаты SDK проверит наличие возможности оплаты через Tinkoff Pay и в зависимости от результата отобразит
кнопку оплаты. Отключить отображение кнопки программно можно с помощью параметра `tinkoffPayEnabled` в `featuresOptions`.

```kotlin
var paymentOptions = PaymentOptions().setOptions {
orderOptions { /*options*/ }
customerOptions { /*options*/ }
featuresOptions {
tinkoffPayEnabled = false // отключение отображения кнопки оплаты через Tinkoff Pay; по умолчанию отображение включено
}
}
```

Для определения возможности оплаты через Tinkoff Pay SDK посылает запрос на "https://securepay.tinkoff.ru/v2/TinkoffPay/terminals/$terminalKey/status".
Результат выполнения запроса кэшируется в SDK на период в 5 минут для уменьшения количества исходящих запросов.

Для отображения кнопки оплаты через Tinkoff Pay внутри вашего приложения (вне экрана оплаты, предоставляемого SDK) необходимо:
1. Самостоятельно вызвать метод определения доступности оплаты через Tinkoff Pay. Для этого можно использовать метод `TinkoffAcquiring.checkTinkoffPayStatus`
2. При наличии возможности оплаты отобразить кнопку оплаты через Tinkoff Pay в вашем приложении в соответствии с Design Guidelines
3. По нажатию на кнопку создать процесс оплаты с помощью метода `TinkoffAcquiring.payWithTinkoffPay` (параметр `version` можно получить
из ответа на шаге 1), зарегистрировать в нем слушатель событий (c обработкой состояния `OpenTinkoffPayBankState` в методе `onUiNeeded` и
использующий `state.deepLink` для открытия приложения с формой оплаты) и запустить процесс оплаты (метод `start()`)
4. При необходимости, проверить статус платежа при помощи `TinkoffAcquiring.sdk.getState` (с указанием `paymentId` полученном в `state.paymentId` на
предыдущем шаге); время и частота проверки статуса платежа зависит от нужд клиентского приложения и остается на ваше усмотрение (один из вариантов -
проверять статус платежа при возвращении приложения из фона)

### Дополнительные возможности

#### Настройка стилей
Expand Down
Loading

0 comments on commit ffae269

Please sign in to comment.