Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-09 10:04:56 +02:00
parent f39054b5a9
commit 551e2a60bb
22 changed files with 985 additions and 37 deletions

View file

@ -13,6 +13,7 @@ import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.utils.calculateRateDif
import com.tangem.domain.onramp.utils.compareOffersByRateSpeedAndPriority
import com.tangem.domain.settings.repositories.SettingsRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
@ -38,7 +39,7 @@ class GetOnrampAllOffersUseCase(
if (validQuotes.isEmpty()) return emptyList()
val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
val overallBestRateQuote = validQuotes.maxByOrNull { it.toAmount.value }
val overallBestRateQuote = validQuotes.maxWithOrNull(compareOffersByRateSpeedAndPriority(isGooglePayAvailable))
val bestRate = overallBestRateQuote?.toAmount?.value
val offersByPaymentMethod = validQuotes.groupBy { it.paymentMethod }
@ -54,7 +55,8 @@ class GetOnrampAllOffersUseCase(
OnrampOffer(quote = quote, rateDif = rateDif, advantages = advantages)
}
val groupBestRateOfferData = methodQuotes.maxByOrNull { it.toAmount.value }
val groupBestRateOfferData =
methodQuotes.maxWithOrNull(compareOffersByRateSpeedAndPriority(isGooglePayAvailable))
val groupBestRateOffer = methodOffers.find {
when (val quote = it.quote) {
is OnrampQuote.Data -> quote == groupBestRateOfferData

View file

@ -12,15 +12,17 @@ import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.onramp.utils.calculateRateDif
import com.tangem.domain.onramp.utils.compareOffersByRateSpeedAndPriority
import com.tangem.domain.settings.repositories.SettingsRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import java.math.BigDecimal
class GetOnrampOffersUseCase(
private val onrampRepository: OnrampRepository,
private val onrampTransactionRepository: OnrampTransactionRepository,
private val errorResolver: OnrampErrorResolver,
private val settingsRepository: SettingsRepository,
) {
operator fun invoke(
@ -37,14 +39,15 @@ class GetOnrampOffersUseCase(
.catch { throwable -> errorResolver.resolve(throwable).left() }
}
private fun processOffers(
private suspend fun processOffers(
quotes: List<OnrampQuote>,
transactions: List<OnrampTransaction>,
): List<OnrampOffersBlock> {
val validQuotes = quotes.filterIsInstance<OnrampQuote.Data>()
if (validQuotes.isEmpty()) return emptyList()
val bestRateQuote = validQuotes.maxByOrNull { it.toAmount.value }
val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
val bestRateQuote = validQuotes.maxWithOrNull(compareOffersByRateSpeedAndPriority(isGooglePayAvailable))
val bestRate = bestRateQuote?.toAmount?.value
val offers = validQuotes.map { quote ->
@ -53,8 +56,8 @@ class GetOnrampOffersUseCase(
}
val recentOffer = findRecentOffer(offers, transactions)
val bestRateOffer = findBestRateOffer(offers)
val fastestOffer = findFastestOffer(offers)
val bestRateOffer = findBestRateOffer(offers, isGooglePayAvailable)
val fastestOffer = findFastestOffer(offers, isGooglePayAvailable)
return buildOffersBlocks(
recentOffer = recentOffer,
@ -73,36 +76,35 @@ class GetOnrampOffersUseCase(
}
}
private fun findBestRateOffer(offers: List<OnrampOffer>): OnrampOffer? {
return offers.maxByOrNull { offer ->
when (val quote = offer.quote) {
is OnrampQuote.Data -> quote.toAmount.value
else -> BigDecimal.ZERO
}
}
private fun findBestRateOffer(offers: List<OnrampOffer>, isGooglePayAvailable: Boolean): OnrampOffer? {
return offers.maxWithOrNull(offerComparator(isGooglePayAvailable))
}
private fun findFastestOffer(offers: List<OnrampOffer>): OnrampOffer? {
private fun findFastestOffer(offers: List<OnrampOffer>, isGooglePayAvailable: Boolean): OnrampOffer? {
val instantOffers = offers.filter { it.quote.paymentMethod.type.isInstant() }
return if (instantOffers.isNotEmpty()) {
instantOffers.maxByOrNull { offer ->
when (val quote = offer.quote) {
is OnrampQuote.Data -> quote.toAmount.value
else -> BigDecimal.ZERO
}
}
instantOffers.maxWithOrNull(offerComparator(isGooglePayAvailable))
} else {
val offersBySpeed = offers.groupBy { offer ->
offer.quote.paymentMethod.type.getProcessingSpeed().speed
}
val fastestSpeed = offersBySpeed.keys.minOrNull() ?: return null
val fastestOffers = offersBySpeed[fastestSpeed] ?: return null
fastestOffers.maxByOrNull { offer ->
when (val quote = offer.quote) {
is OnrampQuote.Data -> quote.toAmount.value
else -> BigDecimal.ZERO
fastestOffers.maxWithOrNull(offerComparator(isGooglePayAvailable))
}
}
private fun offerComparator(isGooglePayAvailable: Boolean): Comparator<OnrampOffer> = Comparator { offer1, offer2 ->
when (val quote1 = offer1.quote) {
is OnrampQuote.Data -> {
when (val quote2 = offer2.quote) {
is OnrampQuote.Data -> {
compareOffersByRateSpeedAndPriority(isGooglePayAvailable).compare(quote1, quote2)
}
else -> 1
}
}
else -> -1
}
}

View file

@ -1,8 +1,34 @@
package com.tangem.domain.onramp.utils
import com.tangem.domain.onramp.model.OnrampQuote
import java.math.BigDecimal
internal fun calculateRateDif(currentTokenRate: BigDecimal, bestRate: BigDecimal?): BigDecimal? {
if (bestRate == null) return null
return BigDecimal.ONE - currentTokenRate / bestRate
}
internal fun compareOffersByRateSpeedAndPriority(isGooglePayAvailable: Boolean): Comparator<OnrampQuote.Data> {
return Comparator { quote1, quote2 ->
val rateComparison = quote1
.toAmount
.value
.compareTo(quote2.toAmount.value)
if (rateComparison != 0) return@Comparator rateComparison
val speedComparison =
quote2
.paymentMethod
.type
.getProcessingSpeed()
.speed
.compareTo(quote1.paymentMethod.type.getProcessingSpeed().speed)
if (speedComparison != 0) return@Comparator speedComparison
quote1
.paymentMethod
.type
.getPriority(isGooglePayAvailable)
.compareTo(quote2.paymentMethod.type.getPriority(isGooglePayAvailable))
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import io.mockk.*
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
@ -22,6 +23,7 @@ class GetOnrampOffersUseCaseTest {
private val onrampRepository: OnrampRepository = mockk(relaxUnitFun = true)
private val onrampTransactionRepository: OnrampTransactionRepository = mockk(relaxUnitFun = true)
private val errorResolver: OnrampErrorResolver = mockk(relaxUnitFun = true)
private val settingsRepository: SettingsRepository = mockk(relaxUnitFun = true)
private val cryptoCurrencyId: CryptoCurrency.ID = mockk(relaxUnitFun = true)
private val userWalletId: UserWalletId = mockk(relaxUnitFun = true)
@ -34,6 +36,7 @@ class GetOnrampOffersUseCaseTest {
onrampRepository = onrampRepository,
onrampTransactionRepository = onrampTransactionRepository,
errorResolver = errorResolver,
settingsRepository = settingsRepository,
)
}
@ -42,6 +45,7 @@ class GetOnrampOffersUseCaseTest {
val emptyQuotes = listOf<OnrampQuote>()
val emptyTransactions = listOf<OnrampTransaction>()
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(emptyQuotes)
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
emptyTransactions,
@ -77,6 +81,7 @@ class GetOnrampOffersUseCaseTest {
createMockTransaction("provider1", "card", 1000L),
)
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
transactions,
@ -121,6 +126,7 @@ class GetOnrampOffersUseCaseTest {
val transactions = emptyList<OnrampTransaction>()
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
transactions,
@ -165,6 +171,7 @@ class GetOnrampOffersUseCaseTest {
val transactions = emptyList<OnrampTransaction>()
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
transactions,
@ -218,6 +225,7 @@ class GetOnrampOffersUseCaseTest {
val transactions = emptyList<OnrampTransaction>()
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
transactions,