Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-05 10:24:59 +01:00
parent 920fc0ff57
commit 17a97c66b6
8 changed files with 59 additions and 18 deletions

View file

@ -947,6 +947,7 @@
</plurals>
<string name="onramp_timing_minutes">%s min</string>
<string name="onramp_title_available_from">Available from</string>
<string name="onramp_title_available_up_to">Available up to</string>
<string name="onramp_title_you_get">You get</string>
<string name="onramp_tos_external_providers">Service is provided by an external provider. \nTangem is not responsible.</string>
<string name="onramp_transaction_status_footer_text">You can close this screen and check the transaction status on the token details screen.</string>

View file

@ -13,5 +13,10 @@ data class OnrampPaymentMethodGroup(
sealed interface PaymentMethodStatus {
data object Available : PaymentMethodStatus
data class Unavailable(val availableFrom: BigDecimal) : PaymentMethodStatus
sealed interface Unavailable : PaymentMethodStatus {
val requiredAmount: BigDecimal
data class MinAmount(override val requiredAmount: BigDecimal) : Unavailable
data class MaxAmount(override val requiredAmount: BigDecimal) : Unavailable
}
}

View file

@ -167,10 +167,16 @@ class GetOnrampAllOffersUseCase(
return if (hasAtLeastOneValidQuote) {
PaymentMethodStatus.Available
} else {
val minSum = methodQuotes
.filterIsInstance<OnrampQuote.AmountError>()
.minOfOrNull { it.error.requiredAmount } ?: BigDecimal.ZERO
PaymentMethodStatus.Unavailable(minSum)
val amountErrorQuotes = methodQuotes.filterIsInstance<OnrampQuote.AmountError>()
val minAmountErrors = amountErrorQuotes.filter { it.error is OnrampError.AmountError.TooSmallError }
if (minAmountErrors.isNotEmpty()) {
val minRequiredAmount = minAmountErrors.minOf { it.error.requiredAmount }
PaymentMethodStatus.Unavailable.MinAmount(minRequiredAmount)
} else {
val maxAmount = amountErrorQuotes.maxOfOrNull { it.error.requiredAmount } ?: BigDecimal.ZERO
PaymentMethodStatus.Unavailable.MaxAmount(maxAmount)
}
}
}
}

View file

@ -240,7 +240,7 @@ class GetOnrampAllOffersUseCaseTest {
val cardGroup = offers.first()
Truth.assertThat(
cardGroup.methodStatus,
).isEqualTo(PaymentMethodStatus.Unavailable(BigDecimal("50.0")))
).isEqualTo(PaymentMethodStatus.Unavailable.MinAmount(BigDecimal("50.0")))
Truth.assertThat(cardGroup.offers).hasSize(2)
Truth.assertThat(cardGroup.bestRateOffer).isNotNull()
@ -446,14 +446,17 @@ class GetOnrampAllOffersUseCaseTest {
paymentMethod: OnrampPaymentMethod,
provider: OnrampProvider,
requiredAmount: BigDecimal = BigDecimal("50.0"),
isMinAmountError: Boolean = true,
): OnrampQuote.AmountError {
return mockk<OnrampQuote.AmountError> {
every { this@mockk.paymentMethod } returns paymentMethod
every { this@mockk.provider } returns provider
every { this@mockk.fromAmount } returns createMockAmount(BigDecimal("10.0"))
every { this@mockk.countryCode } returns "US"
every { this@mockk.error } returns mockk<OnrampError.AmountError> {
every { this@mockk.requiredAmount } returns requiredAmount
every { this@mockk.error } returns if (isMinAmountError) {
OnrampError.AmountError.TooSmallError(requiredAmount = requiredAmount)
} else {
OnrampError.AmountError.TooBigError(requiredAmount = requiredAmount)
}
}
}

View file

@ -175,9 +175,14 @@ internal class AllOffersStateFactory(
offer: OnrampOffer,
currencyCode: String,
): OnrampOfferUM {
val advantages = when (quote.error) {
is OnrampError.AmountError.TooSmallError -> OnrampOfferAdvantagesUM.Unavailable.MinAmount
is OnrampError.AmountError.TooBigError -> OnrampOfferAdvantagesUM.Unavailable.MaxAmount
}
return OnrampOfferUM(
category = OnrampOfferCategoryUM.Recommended,
advantages = OnrampOfferAdvantagesUM.Unavailable,
advantages = advantages,
paymentMethod = quote.paymentMethod,
providerName = quote.provider.info.name,
rate = formatRequiredAmount(quote, currencyCode),

View file

@ -108,6 +108,7 @@ private fun PaymentMethod(methodUM: AllOffersPaymentMethodUM, modifier: Modifier
UnavailablePaymentMethodInfoBlock(
paymentMethodName = methodUM.methodConfig.method.name,
errorAmount = methodUM.rate,
unavailableStatus = methodUM.paymentMethodStatus,
)
}
}
@ -116,7 +117,11 @@ private fun PaymentMethod(methodUM: AllOffersPaymentMethodUM, modifier: Modifier
}
@Composable
private fun UnavailablePaymentMethodInfoBlock(paymentMethodName: String, errorAmount: String) {
private fun UnavailablePaymentMethodInfoBlock(
paymentMethodName: String,
errorAmount: String,
unavailableStatus: PaymentMethodStatus.Unavailable,
) {
Column(modifier = Modifier.padding(bottom = 14.dp)) {
Text(
text = paymentMethodName,
@ -125,8 +130,12 @@ private fun UnavailablePaymentMethodInfoBlock(paymentMethodName: String, errorAm
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.PAYMENT_METHOD_NAME),
)
SpacerH(2.dp)
val messageResId = when (unavailableStatus) {
is PaymentMethodStatus.Unavailable.MinAmount -> R.string.onramp_provider_min_amount
is PaymentMethodStatus.Unavailable.MaxAmount -> R.string.onramp_provider_max_amount
}
Text(
text = stringResourceSafe(R.string.onramp_provider_min_amount, errorAmount),
text = stringResourceSafe(messageResId, errorAmount),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.testTag(SelectPaymentMethodBottomSheetTestTags.UP_TO_TEXT),

View file

@ -34,8 +34,16 @@ internal enum class OnrampOfferCategoryUM {
RecentlyUsed, Recommended
}
internal enum class OnrampOfferAdvantagesUM {
Default, BestRate, GreatRate, Fastest, Unavailable;
internal sealed interface OnrampOfferAdvantagesUM {
data object Default : OnrampOfferAdvantagesUM
data object BestRate : OnrampOfferAdvantagesUM
data object GreatRate : OnrampOfferAdvantagesUM
data object Fastest : OnrampOfferAdvantagesUM
sealed interface Unavailable : OnrampOfferAdvantagesUM {
data object MinAmount : Unavailable
data object MaxAmount : Unavailable
}
fun toAnalyticsEvent(
cryptoCurrencySymbol: String,
@ -55,7 +63,7 @@ internal enum class OnrampOfferAdvantagesUM {
)
Default,
BestRate,
Unavailable,
is Unavailable,
-> null
}
}

View file

@ -119,7 +119,7 @@ internal fun Offer(onrampOfferUM: OnrampOfferUM, modifier: Modifier = Modifier)
RateBlock(
rate = onrampOfferUM.rate,
diff = onrampOfferUM.diff,
isOfferUnavailable = onrampOfferUM.advantages == OnrampOfferAdvantagesUM.Unavailable,
isOfferUnavailable = onrampOfferUM.advantages is OnrampOfferAdvantagesUM.Unavailable,
)
}
SpacerWMax()
@ -127,7 +127,7 @@ internal fun Offer(onrampOfferUM: OnrampOfferUM, modifier: Modifier = Modifier)
size = TangemButtonSize.RoundedAction,
text = stringResourceSafe(R.string.common_buy),
onClick = onrampOfferUM.onBuyClicked,
enabled = onrampOfferUM.advantages != OnrampOfferAdvantagesUM.Unavailable,
enabled = onrampOfferUM.advantages !is OnrampOfferAdvantagesUM.Unavailable,
)
}
SpacerH(10.dp)
@ -184,9 +184,13 @@ private fun OfferHeader(advantage: OnrampOfferAdvantagesUM) {
color = TangemTheme.colors.icon.attention,
)
}
OnrampOfferAdvantagesUM.Unavailable -> {
is OnrampOfferAdvantagesUM.Unavailable -> {
val textResId = when (advantage) {
OnrampOfferAdvantagesUM.Unavailable.MinAmount -> R.string.onramp_title_available_from
OnrampOfferAdvantagesUM.Unavailable.MaxAmount -> R.string.onramp_title_available_up_to
}
Text(
text = stringResourceSafe(R.string.onramp_title_available_from),
text = stringResourceSafe(textResId),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
)