Skip to content
This repository has been archived by the owner. It is now read-only.

Commit

Permalink
Tinkoff Pay
Browse files Browse the repository at this point in the history
  • Loading branch information
a.ignatov committed Jul 28, 2023
1 parent 4609b6e commit c77d973
Show file tree
Hide file tree
Showing 36 changed files with 697 additions and 54 deletions.
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
org.gradle.unsafe.configuration-cache = true
org.gradle.unsafe.configuration-cache = true
android.defaults.buildfeatures.buildconfig = true
android.nonTransitiveRClass = false
android.nonFinalResIds = false
15 changes: 15 additions & 0 deletions sdk/src/main/java/ru/cloudpayments/sdk/api/CloudpaymentsApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class CloudpaymentsApi @Inject constructor(private val apiService: Cloudpayments
.subscribeOn(Schedulers.io())
}

fun getMerchantConfiguration(publicId: String): Single<CloudpaymentsMerchantConfigurationResponse> {
return apiService.getMerchantConfiguration(publicId)
.subscribeOn(Schedulers.io())
}

fun charge(requestBody: PaymentRequestBody): Single<CloudpaymentsTransactionResponse> {
return apiService.charge(requestBody)
.subscribeOn(Schedulers.io())
Expand Down Expand Up @@ -58,6 +63,16 @@ class CloudpaymentsApi @Inject constructor(private val apiService: Cloudpayments
}
}

fun getTinkoffPayQrLink(requestBody: TinkoffPayQrLinkBody): Single<CloudpaymentsGetTinkoffPayQrLinkResponse> {
return apiService.getTinkoffPayQrLink(requestBody)
.subscribeOn(Schedulers.io())
}

fun qrLinkStatusWait(requestBody: QrLinkStatusWaitBody): Single<QrLinkStatusWaitResponse> {
return apiService.qrLinkStatusWait(requestBody)
.subscribeOn(Schedulers.io())
}

fun getBinInfo(firstSixDigits: String): Single<CloudpaymentsBinInfo> =
if (firstSixDigits.length < 6) {
Single.error(CloudpaymentsTransactionError("You must specify the first 6 digits of the card number"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import ru.cloudpayments.sdk.api.models.CloudpaymentsBinInfoResponse
import ru.cloudpayments.sdk.api.models.CloudpaymentsGetTinkoffPayQrLinkResponse
import ru.cloudpayments.sdk.api.models.CloudpaymentsMerchantConfigurationResponse
import ru.cloudpayments.sdk.api.models.CloudpaymentsPublicKeyResponse
import ru.cloudpayments.sdk.api.models.PaymentRequestBody
import ru.cloudpayments.sdk.api.models.ThreeDsRequestBody
import ru.cloudpayments.sdk.api.models.CloudpaymentsTransactionResponse
import ru.cloudpayments.sdk.api.models.QrLinkStatusWaitBody
import ru.cloudpayments.sdk.api.models.QrLinkStatusWaitResponse
import ru.cloudpayments.sdk.api.models.TinkoffPayQrLinkBody

interface CloudpaymentsApiService {
@POST("payments/cards/charge")
Expand All @@ -26,4 +32,13 @@ interface CloudpaymentsApiService {

@GET("payments/publickey")
fun getPublicKey(): Single<CloudpaymentsPublicKeyResponse>

@GET("merchant/configuration")
fun getMerchantConfiguration(@Query("terminalPublicId") publicId: String): Single<CloudpaymentsMerchantConfigurationResponse>

@POST("payments/qr/tinkoffpay/link")
fun getTinkoffPayQrLink(@Body body: TinkoffPayQrLinkBody): Single<CloudpaymentsGetTinkoffPayQrLinkResponse>

@POST("payments/qr/status/wait")
fun qrLinkStatusWait(@Body body: QrLinkStatusWaitBody): Single<QrLinkStatusWaitResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName
import io.reactivex.Observable

data class CloudpaymentsGetTinkoffPayQrLinkResponse(
@SerializedName("Success") val success: Boolean?,
@SerializedName("Message") val message: String?,
@SerializedName("Model") val transaction: CloudpaymentsTinkoffPayQrLinkTransaction?) {
fun handleError(): Observable<CloudpaymentsGetTinkoffPayQrLinkResponse> {
return if (success == true ) {
Observable.just(this)
} else {
Observable.error(CloudpaymentsTransactionError(message ?: ""))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName

data class CloudpaymentsMerchantConfigurationResponse(
@SerializedName("Success") val success: Boolean?,
@SerializedName("Message") val message: String?,
@SerializedName("Model") val model: MerchantConfiguration?
)

data class MerchantConfiguration(
@SerializedName("ExternalPaymentMethods") val externalPaymentMethods: ArrayList<ExternalPaymentMethods>?,
@SerializedName("Features") val features: Features?
)

data class ExternalPaymentMethods(
@SerializedName("Type") val type: Int?,
@SerializedName("Enabled") val enabled: Boolean?
)

data class Features(
@SerializedName("IsSaveCard") val isSaveCard: Int?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName

data class CloudpaymentsTinkoffPayQrLinkTransaction(
@SerializedName("TransactionId") val transactionId: Int?,
@SerializedName("ProviderQrId") val providerQrId: String?,
@SerializedName("QrUrl") val qrUrl: String?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName

data class QrLinkStatusWait(
@SerializedName("TransactionId") val transactionId: Int?,
@SerializedName("Status") val status: String?,
@SerializedName("StatusCode") val statusCode: String?)


Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName

data class QrLinkStatusWaitBody(
@SerializedName("TransactionId") val transactionId: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName
import io.reactivex.Observable

data class QrLinkStatusWaitResponse(
@SerializedName("Success") val success: Boolean?,
@SerializedName("Message") val message: String?,
@SerializedName("Model") val transaction: QrLinkStatusWait?) {
fun handleError(): Observable<QrLinkStatusWaitResponse> {
return if (success == true ){
Observable.just(this)
} else {
Observable.error(CloudpaymentsTransactionError(message ?: ""))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.cloudpayments.sdk.api.models

import com.google.gson.annotations.SerializedName

data class TinkoffPayQrLinkBody(
@SerializedName("Webview") val webView: Boolean = true, // Мобильное устройство
@SerializedName("Device") val device: String = "MobileApp", // Вызов из мобильных приложений
@SerializedName("Amount") val amount: String, // Сумма
@SerializedName("Currency") val currency: String, // Валюта
@SerializedName("Description") val description: String? = null, // Описание платежа
@SerializedName("AccountId") val accountId: String? = null, // Identity плательщика в системе мерчанта
@SerializedName("Email") val email: String? = null, // E-mail плательщика
@SerializedName("JsonData") val jsonData: String? = null, // Произвольные данные мерчанта в формате JSON
@SerializedName("InvoiceId") val invoiceId: String? = null, // id заказа в системе мерчанта
@SerializedName("Scheme") val scheme: String, // charge - одностадийная оплата, auth - двухстадийная оплата (Scheme":"0")
@SerializedName("TtlMinutes") val ttlMinutes: Int = 30, // Время жизни Qr
@SerializedName("SuccessRedirectUrl") val successRedirectUrl: String = "https://cp.ru", // Url успешной оплаты (мерчанта)
@SerializedName("FailRedirectUrl") val failRedirectUrl: String = "https://cp.ru", // Url неуспешной оплаты (мерчанта)
@SerializedName("SaveCard") var saveCard: Boolean? = null)
Original file line number Diff line number Diff line change
@@ -1,15 +1,57 @@
package ru.cloudpayments.sdk.configuration

import android.os.Parcelable
import android.util.Log
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName
import kotlinx.android.parcel.Parcelize
import ru.cloudpayments.sdk.Constants
import ru.cloudpayments.sdk.api.models.PaymentDataPayer
import ru.cloudpayments.sdk.util.TAG

@Parcelize
class PaymentData(val amount: String,
var currency: String = "RUB",
val invoiceId: String? = null,
val description: String? = null,
val accountId: String? = null,
var email: String? = null,
val payer: PaymentDataPayer? = null,
val jsonData: String? = null): Parcelable
class PaymentData(
val amount: String,
var currency: String = "RUB",
val invoiceId: String? = null,
val description: String? = null,
val accountId: String? = null,
var email: String? = null,
val payer: PaymentDataPayer? = null,
val jsonData: String? = null
) : Parcelable {

fun jsonDataHasRecurrent(): Boolean {

if (!jsonData.isNullOrEmpty()) {
val gson = GsonBuilder()
.setLenient()
.create()

try {
val cpJsonData = gson.fromJson(jsonData, CpJsonData::class.java)
cpJsonData.cloudPayments?.recurrent?.interval?.let {
return true
}
} catch (e: JsonSyntaxException) {
Log.e(TAG, "JsonData syntax error")
}
}
return false
}
}

data class CpJsonData(
@SerializedName("cloudPayments") val cloudPayments: CloudPaymentsJsonData?
)

data class CloudPaymentsJsonData(
@SerializedName("recurrent") val recurrent: CloudPaymentsRecurrentJsonData?
)

data class CloudPaymentsRecurrentJsonData(
@SerializedName("interval") val interval: String?,
@SerializedName("period") val period: String?,
@SerializedName("amount") val amount: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ class CloudpaymentsNetModule(private val publicId: String, private var apiUrl: S
authenticationInterceptor: AuthenticationInterceptor): CloudpaymentsApiService {
val client = okHttpClientBuilder
.addInterceptor(authenticationInterceptor)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.followRedirects(false)
.build()

Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/java/ru/cloudpayments/sdk/models/PayParams.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.cloudpayments.sdk.models

data class PayParams(
var saveCard: Boolean? = null
)
16 changes: 11 additions & 5 deletions sdk/src/main/java/ru/cloudpayments/sdk/ui/PaymentActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ru.cloudpayments.sdk.dagger2.CloudpaymentsModule
import ru.cloudpayments.sdk.dagger2.CloudpaymentsNetModule
import ru.cloudpayments.sdk.dagger2.DaggerCloudpaymentsComponent
import ru.cloudpayments.sdk.databinding.ActivityCpsdkPaymentBinding
import ru.cloudpayments.sdk.models.PayParams
import ru.cloudpayments.sdk.ui.dialogs.base.BasePaymentBottomSheetFragment
import ru.cloudpayments.sdk.ui.dialogs.PaymentCardFragment
import ru.cloudpayments.sdk.ui.dialogs.PaymentOptionsFragment
Expand All @@ -35,6 +36,8 @@ internal class PaymentActivity: FragmentActivity(), BasePaymentBottomSheetFragme
PaymentOptionsFragment.IPaymentOptionsFragment, PaymentCardFragment.IPaymentCardFragment,
PaymentProcessFragment.IPaymentProcessFragment {

val payParams: PayParams = PayParams()

companion object {
private const val REQUEST_CODE_GOOGLE_PAY = 1

Expand Down Expand Up @@ -76,7 +79,7 @@ internal class PaymentActivity: FragmentActivity(), BasePaymentBottomSheetFragme
val token = Base64.decode(result.paymentToken.toString(), Base64.DEFAULT)

val runnable = {
val fragment = PaymentProcessFragment.newInstance(String(token))
val fragment = PaymentProcessFragment.newInstance(PaymentProcessFragment.MODE_YANDEX_PAY, String(token))
nextFragment(fragment, true, R.id.frame_content)
}
Handler().postDelayed(runnable, 1000)
Expand Down Expand Up @@ -167,13 +170,16 @@ internal class PaymentActivity: FragmentActivity(), BasePaymentBottomSheetFragme
override fun onCardClicked() {
val fragment = PaymentCardFragment.newInstance()
fragment.show(supportFragmentManager, "")
//nextFragment(fragment, true, R.id.frame_content)
}

override fun onTinkoffPayClicked() {
val fragment = PaymentProcessFragment.newInstance(PaymentProcessFragment.MODE_TINKOFF_PAY)
fragment.show(supportFragmentManager, "")
}

override fun onPayClicked(cryptogram: String) {
val fragment = PaymentProcessFragment.newInstance(cryptogram)
val fragment = PaymentProcessFragment.newInstance(PaymentProcessFragment.MODE_CARD, cryptogram)
fragment.show(supportFragmentManager, "")
//nextFragment(fragment, true, R.id.frame_content)
}

override fun onPaymentFinished(transactionId: Int) {
Expand Down Expand Up @@ -231,7 +237,7 @@ internal class PaymentActivity: FragmentActivity(), BasePaymentBottomSheetFragme

if (token != null) {
val runnable = {
val fragment = PaymentProcessFragment.newInstance(token)
val fragment = PaymentProcessFragment.newInstance(PaymentProcessFragment.MODE_GOOGLE_PAY, token)
nextFragment(fragment, true, R.id.frame_content)
}
Handler().postDelayed(runnable, 1000)
Expand Down
Loading

0 comments on commit c77d973

Please sign in to comment.