Updated on 2026-08-14
This commit is contained in:
parent
98fee821fa
commit
5ee6f98c3e
8 changed files with 88 additions and 32 deletions
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
|||
import com.tangem.domain.onramp.model.OnrampAmount
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
||||
internal class OnrampQuotesErrorConverter(
|
||||
private val jsonAdapter: JsonAdapter<ExpressErrorResponse>,
|
||||
|
|
@ -35,7 +36,12 @@ internal class OnrampQuotesErrorConverter(
|
|||
return OnrampQuote.Error.AmountTooSmallError(
|
||||
paymentMethod = input.paymentMethod,
|
||||
provider = input.provider,
|
||||
amount = createFromAmountWithOffset(
|
||||
fromAmount = OnrampAmount(
|
||||
symbol = input.amount.currencySymbol,
|
||||
value = input.amount.value.orZero(),
|
||||
decimals = input.amount.decimals,
|
||||
),
|
||||
requiredAmount = createFromAmountWithOffset(
|
||||
amountWithOffset = minAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
|
|
@ -53,7 +59,12 @@ internal class OnrampQuotesErrorConverter(
|
|||
return OnrampQuote.Error.AmountTooBigError(
|
||||
paymentMethod = input.paymentMethod,
|
||||
provider = input.provider,
|
||||
amount = createFromAmountWithOffset(
|
||||
fromAmount = OnrampAmount(
|
||||
symbol = input.amount.currencySymbol,
|
||||
value = input.amount.value.orZero(),
|
||||
decimals = input.amount.decimals,
|
||||
),
|
||||
requiredAmount = createFromAmountWithOffset(
|
||||
amountWithOffset = maxAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,16 @@ sealed interface OnrampProviderWithQuote {
|
|||
val availablePaymentMethods: List<OnrampPaymentMethod>,
|
||||
) : Unavailable
|
||||
|
||||
data class AvailableFrom(override val provider: OnrampProvider, val amount: OnrampAmount) : Unavailable
|
||||
data class AvailableUpTo(override val provider: OnrampProvider, val amount: OnrampAmount) : Unavailable
|
||||
data class AvailableFrom(
|
||||
override val provider: OnrampProvider,
|
||||
val fromAmount: OnrampAmount,
|
||||
val requiredAmount: OnrampAmount,
|
||||
) : Unavailable
|
||||
|
||||
data class AvailableUpTo(
|
||||
override val provider: OnrampProvider,
|
||||
val fromAmount: OnrampAmount,
|
||||
val requiredAmount: OnrampAmount,
|
||||
) : Unavailable
|
||||
}
|
||||
}
|
||||
|
|
@ -19,13 +19,15 @@ sealed class OnrampQuote {
|
|||
data class AmountTooSmallError(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val amount: OnrampAmount,
|
||||
val fromAmount: OnrampAmount,
|
||||
val requiredAmount: OnrampAmount,
|
||||
) : Error()
|
||||
|
||||
data class AmountTooBigError(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val amount: OnrampAmount,
|
||||
val fromAmount: OnrampAmount,
|
||||
val requiredAmount: OnrampAmount,
|
||||
) : Error()
|
||||
}
|
||||
}
|
||||
|
|
@ -54,11 +54,13 @@ class GetOnrampProviderWithQuoteUseCase(private val repository: OnrampRepository
|
|||
when (amountError) {
|
||||
is OnrampQuote.Error.AmountTooBigError -> Unavailable.AvailableUpTo(
|
||||
provider = amountError.provider,
|
||||
amount = amountError.amount,
|
||||
fromAmount = amountError.fromAmount,
|
||||
requiredAmount = amountError.requiredAmount,
|
||||
)
|
||||
is OnrampQuote.Error.AmountTooSmallError -> Unavailable.AvailableFrom(
|
||||
provider = amountError.provider,
|
||||
amount = amountError.amount,
|
||||
fromAmount = amountError.fromAmount,
|
||||
requiredAmount = amountError.requiredAmount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import arrow.core.Either
|
|||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetOnrampQuotesUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
|
|
@ -24,15 +24,29 @@ class GetOnrampQuotesUseCase(
|
|||
quotes.groupBy { it.paymentMethod.type }
|
||||
.asSequence()
|
||||
.sortedBy { it.key.getPriority(isGooglePayAvailable) }
|
||||
.map { grouped ->
|
||||
grouped.value.sortedByDescending {
|
||||
(it as? OnrampQuote.Data)?.toAmount?.value ?: BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
.sortByRate()
|
||||
.toList()
|
||||
.flatten()
|
||||
.right()
|
||||
}
|
||||
.catch { emit(it.left()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting providers by rule:
|
||||
*
|
||||
* 1. Highest rate
|
||||
* 2. Smallest difference between entered amount and required min/max amount
|
||||
*/
|
||||
private fun Sequence<Map.Entry<PaymentMethodType, List<OnrampQuote>>>.sortByRate() = map { grouped ->
|
||||
grouped.value.sortedByDescending {
|
||||
when (it) {
|
||||
is OnrampQuote.Data -> it.toAmount.value
|
||||
|
||||
// negative difference to sort both when data and unavailable is present
|
||||
is OnrampQuote.Error.AmountTooSmallError -> it.fromAmount.value - it.requiredAmount.value
|
||||
is OnrampQuote.Error.AmountTooBigError -> it.requiredAmount.value - it.fromAmount.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ internal class OnrampAmountStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getAmountSecondaryUpdatedState(quote: OnrampQuote): OnrampMainComponentUM {
|
||||
fun getAmountSecondaryUpdatedState(quote: OnrampQuote, isBestRate: Boolean): OnrampMainComponentUM {
|
||||
val currentState = currentStateProvider()
|
||||
if (currentState !is OnrampMainComponentUM.Content) return currentState
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ internal class OnrampAmountStateFactory(
|
|||
amountBlockState = amountState.copy(
|
||||
secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState),
|
||||
),
|
||||
providerBlockState = quote.toProviderBlockState(),
|
||||
providerBlockState = quote.toProviderBlockState(isBestRate),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(
|
||||
enabled = quote is OnrampQuote.Data,
|
||||
onClick = {
|
||||
|
|
@ -118,11 +118,11 @@ internal class OnrampAmountStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun OnrampQuote.toProviderBlockState(): OnrampProviderBlockUM {
|
||||
private fun OnrampQuote.toProviderBlockState(isBestRate: Boolean): OnrampProviderBlockUM {
|
||||
return OnrampProviderBlockUM.Content(
|
||||
paymentMethod = paymentMethod,
|
||||
providerName = provider.info.name,
|
||||
isBestRate = true,
|
||||
isBestRate = isBestRate,
|
||||
onClick = onrampIntents::openProviders,
|
||||
)
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ internal class OnrampAmountStateFactory(
|
|||
OnrampAmountSecondaryFieldUM.Content(stringReference(amount))
|
||||
}
|
||||
is OnrampQuote.Error.AmountTooBigError -> {
|
||||
val amount = this.amount.value.format {
|
||||
val amount = requiredAmount.value.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = amountState.amountFieldModel.fiatAmount.currencySymbol,
|
||||
fiatCurrencySymbol = amountState.amountFieldModel.fiatAmount.currencySymbol,
|
||||
|
|
@ -150,7 +150,7 @@ internal class OnrampAmountStateFactory(
|
|||
)
|
||||
}
|
||||
is OnrampQuote.Error.AmountTooSmallError -> {
|
||||
val amount = this.amount.value.format {
|
||||
val amount = requiredAmount.value.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = amountState.amountFieldModel.fiatAmount.currencySymbol,
|
||||
fiatCurrencySymbol = amountState.amountFieldModel.fiatAmount.currencySymbol,
|
||||
|
|
|
|||
|
|
@ -177,7 +177,9 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun subscribeToQuotesUpdate() {
|
||||
getOnrampQuotesUseCase.invoke()
|
||||
.onEach { maybeQuotes ->
|
||||
val quote = maybeQuotes.getOrNull()?.firstOrNull() ?: return@onEach
|
||||
val quotes = maybeQuotes.getOrNull() ?: return@onEach
|
||||
val isNoQuotes = quotes.none { it is OnrampQuote.Data }
|
||||
val quote = quotes.firstOrNull() ?: return@onEach
|
||||
if (quote is OnrampQuote.Data && lastAmount.value != quote.fromAmount.value) {
|
||||
lastAmount.value = quote.fromAmount.value
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -188,7 +190,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote) }
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote, isNoQuotes) }
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -125,11 +124,11 @@ internal class SelectProviderModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun List<OnrampProviderWithQuote>.toProvidersListItems(): ImmutableList<ProviderListItemUM> {
|
||||
val sortedByRate = sortedByDescending {
|
||||
(it as? OnrampProviderWithQuote.Data)?.toAmount?.value ?: BigDecimal.ZERO
|
||||
}
|
||||
val bestProvider = sortedByRate.firstOrNull()
|
||||
return sortedByRate.mapIndexed { index, quote ->
|
||||
val sorted = sortByRate()
|
||||
|
||||
val isQuoteExist = any { it is OnrampProviderWithQuote.Data }
|
||||
val bestProvider = sorted.firstOrNull().takeIf { isQuoteExist }
|
||||
return sorted.mapIndexed { index, quote ->
|
||||
when (quote) {
|
||||
is OnrampProviderWithQuote.Data -> {
|
||||
val rate = quote.toAmount.value.format {
|
||||
|
|
@ -154,8 +153,8 @@ internal class SelectProviderModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableFrom -> {
|
||||
val amount = quote.amount.value.format {
|
||||
crypto(symbol = quote.amount.symbol, decimals = quote.amount.decimals)
|
||||
val amount = quote.requiredAmount.value.format {
|
||||
crypto(symbol = quote.requiredAmount.symbol, decimals = quote.requiredAmount.decimals)
|
||||
}
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = quote.provider.id,
|
||||
|
|
@ -165,8 +164,8 @@ internal class SelectProviderModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableUpTo -> {
|
||||
val amount = quote.amount.value.format {
|
||||
crypto(symbol = quote.amount.symbol, decimals = quote.amount.decimals)
|
||||
val amount = quote.requiredAmount.value.format {
|
||||
crypto(symbol = quote.requiredAmount.symbol, decimals = quote.requiredAmount.decimals)
|
||||
}
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = quote.provider.id,
|
||||
|
|
@ -188,4 +187,21 @@ internal class SelectProviderModel @Inject constructor(
|
|||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting providers by rule:
|
||||
*
|
||||
* 1. Highest rate
|
||||
* 2. Smallest difference between entered amount and required min/max amount
|
||||
*/
|
||||
private fun List<OnrampProviderWithQuote>.sortByRate() = sortedByDescending {
|
||||
when (it) {
|
||||
is OnrampProviderWithQuote.Data -> it.toAmount.value
|
||||
|
||||
// negative difference to sort both when data and unavailable is present
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableFrom -> it.fromAmount.value - it.requiredAmount.value
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableUpTo -> it.requiredAmount.value - it.fromAmount.value
|
||||
is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue