Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-09 17:11:34 +05:00
parent 8fd894f5d5
commit 45013d6188
105 changed files with 123 additions and 5898 deletions

View file

@ -1,36 +1,26 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.models.scan.ProductType
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService
import com.tangem.tap.scope
import kotlinx.coroutines.launch
/**
[REDACTED_AUTHOR]
* Temporary wrapper for the buy services. Service switches based on selected product type.
* Just now - UtorgService used only for the SaltPay cards
*/
internal class BuyExchangeService(
private val productTypeProvider: () -> ProductType?,
private val mercuryoService: MercuryoService,
private val utorgService: UtorgExchangeService,
) : ExchangeService, ExchangeUrlBuilder {
init {
scope.launch {
mercuryoService.update()
utorgService.update()
}
}
private val currentService: ExchangeService
get() = when (productTypeProvider.invoke()) {
ProductType.SaltPay -> utorgService
else -> mercuryoService
}
private val currentService: ExchangeService = mercuryoService
override suspend fun update() {
currentService.update()

View file

@ -1,86 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg
import android.net.Uri
import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi
import com.tangem.tap.network.exchangeServices.utorg.mock.MockUtorgApi
import okhttp3.Interceptor
import okhttp3.Response
import java.util.*
/**
[REDACTED_AUTHOR]
*/
data class UtorgEnvironment(
val baseUri: Uri,
val sidValue: String,
val apiVersion: String,
val utorgApi: UtorgApi,
val successUrl: String = "https://success.tangem.com",
) {
companion object {
private val PROD_BASE_URL = Uri.parse("https://app.utorg.pro")
private const val PROD_VERSION = "v1"
private val STAGE_BASE_URL = Uri.parse("https://app-stage.utorg.pro")
private const val STAGE_VERSION = "v1"
fun prod(authProvider: UtorgAuthProvider, apiVersion: String = PROD_VERSION): UtorgEnvironment =
UtorgEnvironment(
baseUri = PROD_BASE_URL,
sidValue = authProvider.sidValue,
apiVersion = apiVersion,
utorgApi = createApi(PROD_BASE_URL, authProvider, false),
)
fun stage(
authProvider: UtorgAuthProvider,
logEnabled: Boolean,
apiVersion: String = STAGE_VERSION,
): UtorgEnvironment = UtorgEnvironment(
baseUri = STAGE_BASE_URL,
sidValue = authProvider.sidValue,
apiVersion = apiVersion,
utorgApi = createApi(STAGE_BASE_URL, authProvider, logEnabled),
)
fun mock(): UtorgEnvironment = UtorgEnvironment(
baseUri = Uri.EMPTY,
sidValue = "",
apiVersion = "",
utorgApi = MockUtorgApi(),
)
private fun createApi(baseUri: Uri, authProvider: UtorgAuthProvider, logEnabled: Boolean): UtorgApi {
return createRetrofitInstance(
baseUrl = "$baseUri/",
interceptors = listOf(UtorgAuthHeaderInterceptor(authProvider)),
logEnabled = logEnabled,
).create(UtorgApi::class.java)
}
}
}
data class UtorgAuthProvider(
val sidValue: String,
val headerSidKey: String = "X-AUTH-SID",
val headerNonceKey: String = "X-AUTH-NONCE",
) {
val clientNonce: String
get() = UUID.randomUUID().toString()
}
private class UtorgAuthHeaderInterceptor(
private val authProvider: UtorgAuthProvider,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
.newBuilder()
.addHeader(authProvider.headerSidKey, authProvider.sidValue)
.addHeader(authProvider.headerNonceKey, authProvider.clientNonce)
.build()
return chain.proceed(request)
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg
import android.net.Uri
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl
import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyData
import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyType
import com.tangem.utils.converter.Converter
/**
[REDACTED_AUTHOR]
*/
class UtorgExchangeService(
private val environment: UtorgEnvironment,
private val currencyConverter: Converter<UtorgCurrencyData, Blockchain?> = ChainToBlockchainConverter(),
) : ExchangeService {
private val api = environment.utorgApi
private val utorgCurrencies = mutableListOf<UtorgCurrencyData>()
override fun featureIsSwitchedOn(): Boolean = true
override suspend fun update() {
when (val result = performRequest { api.getCurrency(environment.apiVersion) }) {
is Result.Success -> {
if (!result.data.isSuccess()) return
val utorgCryptos = result.data.toSuccess().data.filter {
it.enabled && it.type == UtorgCurrencyType.CRYPTO
}
utorgCurrencies.clear()
utorgCurrencies.addAll(utorgCryptos)
performRequest {
api.setSuccessUrl(environment.apiVersion, RequestSuccessUrl(environment.successUrl))
}
}
is Result.Failure -> {
utorgCurrencies.clear()
}
}
}
override fun isBuyAllowed(): Boolean = true
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean {
if (!isBuyAllowed()) return false
val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency ->
val utorgBlockchain = currencyConverter.convert(utorgCurrency) ?: return@firstOrNull false
val isSameBlockchain = utorgBlockchain == currency.blockchain
val isSameSymbol = utorgCurrency.symbol.lowercase() == currency.currencySymbol.lowercase()
isSameBlockchain && isSameSymbol
}
return foundUtorgCurrency != null
}
override fun availableForSell(currency: Currency): Boolean = false
override fun getUrl(
action: CurrencyExchangeManager.Action,
blockchain: Blockchain,
cryptoCurrencyName: CryptoCurrencyName,
fiatCurrencyName: String,
walletAddress: String,
): String {
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
// https://app-stage.utorg.pro/direct/testSID/mvmeaWyWiuVYdZdySgofAt6CmKhJbRhaWA/?&currency=BTC
val builder = Uri.Builder()
.scheme(environment.baseUri.scheme)
.authority(environment.baseUri.authority)
.appendPath("direct")
.appendPath(environment.sidValue)
.appendPath(walletAddress)
.appendQueryParameter("currency", cryptoCurrencyName)
// if we set the paymentCurrency, then the Utorg widget didn't work
// .appendQueryParameter("paymentCurrency", "USD")
val url = builder.build().toString()
return url
}
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
}
private class ChainToBlockchainConverter : Converter<UtorgCurrencyData, Blockchain?> {
override fun convert(value: UtorgCurrencyData): Blockchain? {
return when (value.chain?.uppercase()) {
"ARBITRUM" -> Blockchain.Arbitrum
"AVALANCHE" -> Blockchain.Avalanche
"BINANCE_SMART_CHAIN" -> Blockchain.BSC
"BITCOIN" -> Blockchain.Bitcoin
"BNB" -> Blockchain.Binance
"ETHEREUM" -> Blockchain.Ethereum
"GNOSIS" -> Blockchain.Gnosis
"POLYGON" -> Blockchain.Polygon
"RIPPLE" -> Blockchain.XRP
"RSK" -> Blockchain.RSK
"SOLANA" -> Blockchain.Solana
else -> null
// "APTOS", "ATOM", "NEAR", "ZKSYNC", "VECHAIN", "VELAS", "VELAS_EVM" -> null
}
}
}

View file

@ -1,62 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg.api
import com.squareup.moshi.Json
import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Path
/**
[REDACTED_AUTHOR]
*/
interface UtorgApi {
@POST("api/merchant/{apiVersion}/settings/currency")
suspend fun getCurrency(@Path("apiVersion") apiVersion: String): UtorgCurrencyResponse
@POST("api/merchant/{apiVersion}/settings/successUrl")
suspend fun setSuccessUrl(@Path("apiVersion") apiVersion: String, @Body request: RequestSuccessUrl)
}
data class RequestSuccessUrl(
val url: String,
)
interface UtorgResponse<Data> {
val success: Boolean
val timestamp: Long
val data: Data?
val error: UtorgErrorResponse?
fun isSuccess(): Boolean = success && data != null && error == null
@Throws(NullPointerException::class)
fun toSuccess(): UtorgSuccessResponse<Data> {
val internalTimestamp = this.timestamp
val internalData = this.data
return object : UtorgSuccessResponse<Data> {
override val timestamp: Long = internalTimestamp
override val data: Data = internalData!!
}
}
@Throws(NullPointerException::class)
fun toError(): UtorgErrorResponse = error!!
}
interface UtorgSuccessResponse<T> {
val timestamp: Long
val data: T
}
data class UtorgErrorResponse(
@Json(name = "message") val message: String?,
@Json(name = "type") val type: UtorgErrorType,
)
enum class UtorgErrorType {
UNAUTHORIZED,
UNKNOWN_ERROR,
BAD_REQUEST,
}

View file

@ -1,25 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg.api.model
import com.squareup.moshi.Json
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgResponse
class UtorgCurrencyResponse(
@Json(name = "success") override val success: Boolean,
@Json(name = "timestamp") override val timestamp: Long,
@Json(name = "data") override val data: List<UtorgCurrencyData>?,
@Json(name = "error") override val error: UtorgErrorResponse?,
) : UtorgResponse<List<UtorgCurrencyData>>
data class UtorgCurrencyData(
@Json(name = "currency") val currency: String,
@Json(name = "symbol") val symbol: String,
@Json(name = "enabled") val enabled: Boolean,
@Json(name = "type") val type: UtorgCurrencyType,
@Json(name = "caption") val caption: String?,
@Json(name = "chain") val chain: String?,
)
enum class UtorgCurrencyType {
FIAT, CRYPTO
}

View file

@ -1,58 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg.mock
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse
import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorType
import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse
import kotlinx.coroutines.delay
/**
[REDACTED_AUTHOR]
*/
class MockUtorgApi : UtorgApi {
var nextDelay = 500L
var nextResponseType = ResponseType.Success
var nextErrorType = UtorgErrorType.UNKNOWN_ERROR
override suspend fun getCurrency(apiVersion: String): UtorgCurrencyResponse {
delay(nextDelay)
return UtorgCurrencyResponse(
success = createIsSuccess(),
timestamp = createTime(),
data = createData(MockUtorgSuccessDataResponse.GetCurrency),
error = createError(),
)
}
override suspend fun setSuccessUrl(apiVersion: String, request: RequestSuccessUrl) {
delay(nextDelay)
}
private fun createIsSuccess(): Boolean = when (nextResponseType) {
ResponseType.Success -> true
ResponseType.Error -> false
}
private fun createTime(): Long = System.currentTimeMillis()
private inline fun <reified T> createData(response: MockUtorgSuccessDataResponse): T? = when (nextResponseType) {
ResponseType.Success -> MoshiJsonConverter.INSTANCE.fromJson(response.json)!!
ResponseType.Error -> null
}
private fun createError(): UtorgErrorResponse? = when (nextResponseType) {
ResponseType.Success -> null
ResponseType.Error -> UtorgErrorResponse(
message = "Optional message",
type = nextErrorType,
)
}
enum class ResponseType {
Success, Error
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.tap.network.exchangeServices.utorg.mock
import com.tangem.tap.network.exchangeServices.utorg.mock.json.getCurrencyFull
/**
[REDACTED_AUTHOR]
*/
sealed class MockUtorgSuccessDataResponse(
val json: String,
) {
object GetCurrency : MockUtorgSuccessDataResponse(getCurrencyFull)
}