Updated on 2026-08-14
This commit is contained in:
parent
8fd894f5d5
commit
45013d6188
105 changed files with 123 additions and 5898 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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/?¤cy=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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue