All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.tinkoff.acquiring.sdk.requests.AcquiringRequest.kt Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
/*
 * 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 com.google.gson.Gson
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import okhttp3.Response
import ru.tinkoff.acquiring.sdk.AcquiringSdk
import ru.tinkoff.acquiring.sdk.exceptions.NetworkException
import ru.tinkoff.acquiring.sdk.network.AcquiringApi
import ru.tinkoff.acquiring.sdk.network.AcquiringApi.JSON
import ru.tinkoff.acquiring.sdk.network.NetworkClient
import ru.tinkoff.acquiring.sdk.responses.AcquiringResponse
import ru.tinkoff.acquiring.sdk.utils.Request
import ru.tinkoff.acquiring.sdk.utils.RequestResult
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.security.PublicKey

/**
 * Базовый класс создания запроса Acquiring API
 *
 * @author Mariya Chernyadieva, Taras Nagorny
 */
abstract class AcquiringRequest(internal val apiMethod: String) :
    Request {

    protected val gson: Gson = NetworkClient.createGson()

    open val httpRequestMethod: String = AcquiringApi.API_REQUEST_METHOD_POST
    open val contentType: String = JSON

    internal lateinit var terminalKey: String
    internal lateinit var publicKey: PublicKey

    @Volatile
    private var disposed = false
    private val ignoredFieldsSet: HashSet = hashSetOf(DATA, RECEIPT, RECEIPTS, SHOPS)
    private val headersMap: HashMap = hashMapOf()

    internal open val tokenIgnoreFields: HashSet
        get() = ignoredFieldsSet

    protected abstract fun validate()

    override fun isDisposed(): Boolean {
        return disposed
    }

    override fun dispose() {
        disposed = true
    }

    open fun asMap(): MutableMap {
        val map = HashMap()

        map.putIfNotNull(TERMINAL_KEY, terminalKey)

        return map
    }

    @kotlin.jvm.Throws(NetworkException::class)
    protected fun performRequestRaw(request: AcquiringRequest): Response {
        request.validate()
        val client = NetworkClient()
        return client.callRaw(request)
    }

    protected fun performRequest(
        request: AcquiringRequest,
        responseClass: Class,
        onSuccess: (R) -> Unit,
        onFailure: (Exception) -> Unit
    ) {
        request.validate()
        val client = NetworkClient()
        client.call(request, responseClass, onSuccess, onFailure)
    }

    protected fun performRequestFlow(responseClass: Class) : Flow> {
        val flow = MutableStateFlow>(RequestResult.NotYet)
        try {
            performRequest(
                request = this,
                responseClass = responseClass,
                onSuccess = {
                    flow.tryEmit(RequestResult.Success(it))
                },
                onFailure = {
                    flow.tryEmit(RequestResult.Failure(it))
                }
            )
        } catch (e: Exception) {
            flow.tryEmit(RequestResult.Failure(e))
        }
        return flow
    }

    protected fun MutableMap.putIfNotNull(key: String, value: Any?) {
        if (value == null) {
            return
        }

        this[key] = value
    }

    protected fun Any?.validate(fieldName: String) {
        checkNotNull(this) { "Unable to build request: field '$fieldName' is null" }

        when (this) {
            is String -> check(this.isNotEmpty()) { "Unable to build request: field '$fieldName' is empty" }
            is Long -> check(this >= 0) { "Unable to build request: field '$fieldName' is negative" }
        }
    }

    open fun getRequestBody(): String {
        val params = asMap()
        if (params.isEmpty()) return ""

        getToken()?.let { params[TOKEN] = it }

        return when (contentType) {
            AcquiringApi.FORM_URL_ENCODED -> encodeRequestBody(params)
            else -> gson.toJson(params)
        }
    }

    fun addUserAgentHeader(userAgent: String = System.getProperty("http.agent")) {
        headersMap.put("User-Agent", userAgent)
    }

    fun addContentHeader(content: String = JSON) {
        headersMap.put("Accept", content)
    }

    protected open fun getToken(): String? =
        AcquiringSdk.tokenGenerator?.generateToken(this, paramsForToken())

    internal fun getHeaders() = headersMap

    private fun paramsForToken(): MutableMap {
        val tokenParams = asMap()
        tokenIgnoreFields.forEach {
            tokenParams.remove(it)
        }
        tokenParams.remove(TOKEN)
        return tokenParams
    }

    private fun encodeRequestBody(params: Map): String {
        val builder = StringBuilder()
        for ((key, value1) in params) {
            try {
                val value = URLEncoder.encode(value1.toString(), "UTF-8")
                builder.append(key)
                builder.append('=')
                builder.append(value)
                builder.append('&')
            } catch (e: UnsupportedEncodingException) {
                AcquiringSdk.log(e)
            }
        }

        builder.setLength(builder.length - 1)

        return builder.toString()
    }

    internal companion object {

        const val TERMINAL_KEY = "TerminalKey"
        const val PASSWORD = "Password"
        const val TOKEN = "Token"
        const val PAYMENT_ID = "PaymentId"
        const val SEND_EMAIL = "SendEmail"
        const val EMAIL = "InfoEmail"
        const val CARD_DATA = "CardData"
        const val LANGUAGE = "Language"
        const val AMOUNT = "Amount"
        const val ORDER_ID = "OrderId"
        const val DESCRIPTION = "Description"
        const val PAY_FORM = "PayForm"
        const val CUSTOMER_KEY = "CustomerKey"
        const val RECURRENT = "Recurrent"
        const val REBILL_ID = "RebillId"
        const val CARD_ID = "CardId"
        const val CVC = "CVC"
        const val PAY_TYPE = "PayType"
        const val RECEIPT = "Receipt"
        const val RECEIPTS = "Receipts"
        const val SHOPS = "Shops"
        const val DATA = "DATA"
        const val CHARGE_FLAG = "chargeFlag"
        const val DATA_KEY_EMAIL = "Email"
        const val CHECK_TYPE = "CheckType"
        const val REQUEST_KEY = "RequestKey"
        const val SOURCE = "Source"
        const val PAYMENT_SOURCE = "PaymentSource"
        const val ENCRYPTED_PAYMENT_DATA = "EncryptedPaymentData"
        const val DATA_TYPE = "DataType"
        const val REDIRECT_DUE_DATE = "RedirectDueDate"
        const val NOTIFICATION_URL = "NotificationURL"
        const val SUCCESS_URL = "SuccessURL"
        const val FAIL_URL = "FailURL"
        const val IP = "IP"
        const val CONNECTION_TYPE = "connection_type"
        const val SDK_VERSION = "sdk_version"
        const val SOFTWARE_VERSION = "software_version"
        const val DEVICE_MODEL = "device_model"
        const val THREE_DS_SERVER_TRANS_ID = "threeDSServerTransID"
        const val TRANS_STATUS = "transStatus"
        const val CRES = "cres"
        const val PAYSOURCE = "Paysource"
        const val DEVICE_OS = "DeviceOS"
    }
}

suspend inline fun  Request.performSuspendRequest(): Result {
    return kotlin.runCatching { execute() }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy