Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-10 00:30:26 -07:00
parent 819fecffb6
commit b3640235ef
15 changed files with 175 additions and 147 deletions

View file

@ -40,6 +40,7 @@ sealed interface PaymentAccountStatusValueDM {
@NameLabel("active_account")
data class ActiveAccount(
@Json(name = "active_account") val marker: Boolean = true,
@Json(name = "customer_id") val customerId: String,
@Json(name = "currency_code") val currencyCode: String,
@Json(name = "deposit_address") val depositAddress: String?,
@ -59,9 +60,11 @@ sealed interface PaymentAccountStatusValueDM {
@NameLabel("deactivated_account")
data class DeactivatedAccount(
@Json(name = "deactivated_account") val marker: Boolean = true,
@Json(name = "customer_id") val customerId: String,
@Json(name = "fiat_rate") val fiatRate: BigDecimal?,
@Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM,
@Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM,
@Json(name = "available_for_withdrawal") val availableForWithdrawal: BigDecimal,
) : PaymentAccountStatusValueDM
@JsonClass(generateAdapter = true)

View file

@ -39,11 +39,11 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
is PaymentAccountStatusValue.IssuingCard -> PaymentAccountStatusValueDM.IssuingCard()
is PaymentAccountStatusValue.Loaded -> PaymentAccountStatusValueDM.ActiveAccount(
customerId = value.customerId,
currencyCode = value.currencyCode,
currencyCode = value.balance.fiatBalance.currency,
depositAddress = value.depositAddress,
fiatBalance = value.fiatBalance.toDM(),
cryptoBalance = value.cryptoBalance.toDM(),
availableForWithdrawal = value.availableForWithdrawal,
fiatBalance = value.balance.fiatBalance.toDM(),
cryptoBalance = value.balance.cryptoBalance.toDM(),
availableForWithdrawal = value.balance.availableForWithdrawal,
fiatRate = value.fiatRate,
cards = value.cards.map { card ->
PaymentAccountStatusValueDM.TangemPayCard(
@ -63,9 +63,11 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
)
is PaymentAccountStatusValue.Empty -> PaymentAccountStatusValueDM.Empty()
is PaymentAccountStatusValue.Deactivated -> PaymentAccountStatusValueDM.DeactivatedAccount(
customerId = value.customerId,
fiatRate = value.fiatRate,
fiatBalance = value.fiatBalance.toDM(),
cryptoBalance = value.cryptoBalance.toDM(),
fiatBalance = value.balance.fiatBalance.toDM(),
cryptoBalance = value.balance.cryptoBalance.toDM(),
availableForWithdrawal = value.balance.availableForWithdrawal,
)
// Transient statuses are not persisted
is PaymentAccountStatusValue.Loading,
@ -90,11 +92,12 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
is PaymentAccountStatusValueDM.ActiveAccount -> PaymentAccountStatusValue.Loaded(
source = StatusSource.CACHE,
customerId = value.customerId,
currencyCode = value.currencyCode,
depositAddress = value.depositAddress,
balance = PaymentAccountStatusValue.Balance(
fiatBalance = value.fiatBalance.toDomain(),
cryptoBalance = value.cryptoBalance.toDomain(),
availableForWithdrawal = value.availableForWithdrawal,
),
cryptoCurrency = cryptoCurrency,
fiatRate = value.fiatRate,
cards = value.cards.map { card ->
@ -123,8 +126,12 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
)
is PaymentAccountStatusValueDM.DeactivatedAccount -> PaymentAccountStatusValue.Deactivated(
source = StatusSource.CACHE,
customerId = value.customerId,
balance = PaymentAccountStatusValue.Balance(
fiatBalance = value.fiatBalance.toDomain(),
cryptoBalance = value.cryptoBalance.toDomain(),
availableForWithdrawal = value.availableForWithdrawal,
),
cryptoCurrency = cryptoCurrency,
fiatRate = value.fiatRate,
)

View file

@ -33,6 +33,7 @@ import com.tangem.domain.pay.repository.TangemPayCloseCardRepository
import com.tangem.security.DeviceSecurityInfoProvider
import com.tangem.security.isSecurityExposed
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
@ -287,11 +288,16 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
)
}
fiatBalance != null && cryptoBalance != null && (isDeactivated || isFormer) -> {
fiatBalance != null && cryptoBalance != null && !customerId.isNullOrEmpty() &&
(isDeactivated || isFormer) -> {
PaymentAccountStatusValue.Deactivated(
source = StatusSource.ACTUAL,
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
balance = PaymentAccountStatusValue.Balance(
fiatBalance = fiatBalance,
cryptoBalance = cryptoBalance,
availableForWithdrawal = availableForWithdrawal.orZero(),
),
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
fiatRate = quotesData?.fiatRate,
)
@ -321,11 +327,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
return PaymentAccountStatusValue.Loaded(
source = StatusSource.ACTUAL,
customerId = customerId,
currencyCode = cardInfo.currencyCode,
depositAddress = cardInfo.depositAddress,
balance = PaymentAccountStatusValue.Balance(
fiatBalance = cardInfo.fiatBalance,
cryptoBalance = cardInfo.cryptoBalance,
availableForWithdrawal = cardInfo.availableForWithdrawal,
),
cryptoCurrency = cryptoCurrency,
fiatRate = fiatRate,
cards = listOf(

View file

@ -65,6 +65,7 @@ internal object CustomerInfoConverter : Converter<CustomerMeResponse.Result, Cus
state = CustomerInfo.State.fromString(value.state),
fiatBalance = fiatBalance?.toDomain(),
cryptoBalance = cryptoBalance?.toDomain(),
availableForWithdrawal = value.balance?.availableForWithdrawal?.amount,
)
}

View file

@ -64,11 +64,15 @@ internal class PaymentAccountStatusValueDMConverterTest {
// GIVEN
val domain = PaymentAccountStatusValue.Deactivated(
source = StatusSource.ACTUAL,
customerId = "customer-1",
balance = PaymentAccountStatusValue.Balance(
fiatBalance = PaymentAccountStatusValue.FiatBalance(
availableBalance = BigDecimal("100"),
currency = "USD",
),
cryptoBalance = cryptoBalance(),
availableForWithdrawal = BigDecimal("7"),
),
cryptoCurrency = cryptoCurrency,
fiatRate = BigDecimal("1.05"),
)
@ -79,8 +83,10 @@ internal class PaymentAccountStatusValueDMConverterTest {
// THEN
assertThat(result).isInstanceOf(PaymentAccountStatusValueDM.DeactivatedAccount::class.java)
val dm = result as PaymentAccountStatusValueDM.DeactivatedAccount
assertThat(dm.customerId).isEqualTo("customer-1")
assertThat(dm.fiatBalance.availableBalance).isEqualTo(BigDecimal("100"))
assertThat(dm.fiatBalance.currency).isEqualTo("USD")
assertThat(dm.availableForWithdrawal).isEqualTo(BigDecimal("7"))
assertThat(dm.fiatRate).isEqualTo(BigDecimal("1.05"))
}
@ -141,12 +147,14 @@ internal class PaymentAccountStatusValueDMConverterTest {
fun `GIVEN DM DeactivatedAccount WHEN convertBack THEN returns domain Deactivated with CACHE source`() {
// GIVEN
val dm = PaymentAccountStatusValueDM.DeactivatedAccount(
customerId = "customer-2",
fiatBalance = PaymentAccountStatusValueDM.FiatBalanceDM(
availableBalance = BigDecimal("200"),
currency = "EUR",
),
cryptoBalance = cryptoBalanceDM(),
fiatRate = BigDecimal("0.92"),
availableForWithdrawal = BigDecimal("5"),
)
// WHEN
@ -156,8 +164,10 @@ internal class PaymentAccountStatusValueDMConverterTest {
assertThat(result).isInstanceOf(PaymentAccountStatusValue.Deactivated::class.java)
val deactivated = result as PaymentAccountStatusValue.Deactivated
assertThat(deactivated.source).isEqualTo(StatusSource.CACHE)
assertThat(deactivated.fiatBalance.availableBalance).isEqualTo(BigDecimal("200"))
assertThat(deactivated.fiatBalance.currency).isEqualTo("EUR")
assertThat(deactivated.customerId).isEqualTo("customer-2")
assertThat(deactivated.balance.fiatBalance.availableBalance).isEqualTo(BigDecimal("200"))
assertThat(deactivated.balance.fiatBalance.currency).isEqualTo("EUR")
assertThat(deactivated.balance.availableForWithdrawal).isEqualTo(BigDecimal("5"))
assertThat(deactivated.fiatRate).isEqualTo(BigDecimal("0.92"))
}

View file

@ -33,11 +33,11 @@ sealed class PaymentAccountStatusValue {
is Loading -> TotalFiatBalance.Loading
is Loaded -> {
val rate = this.fiatRate ?: return TotalFiatBalance.Failed
TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source)
TotalFiatBalance.Loaded(amount = balance.fiatBalance.availableBalance.multiply(rate), source = source)
}
is Deactivated -> {
val rate = this.fiatRate ?: return TotalFiatBalance.Failed
TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source)
TotalFiatBalance.Loaded(amount = balance.fiatBalance.availableBalance.multiply(rate), source = source)
}
}
@ -104,8 +104,8 @@ sealed class PaymentAccountStatusValue {
* Represents a state where the account is deactivated.
*
* @property source The source of the status information.
* @property fiatBalance The fiat balance details.
* @property cryptoBalance The crypto balance details.
* @property customerId The unique identifier of the customer.
* @property balance The balance details (fiat, crypto and amount available for withdrawal).
* @property cryptoCurrency The crypto currency held by the deactivated account.
* @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency,
* or `null` if the quote is not yet available. When `null`,
@ -114,18 +114,18 @@ sealed class PaymentAccountStatusValue {
@Serializable
data class Deactivated(
override val source: StatusSource,
val fiatBalance: FiatBalance,
val cryptoBalance: CryptoBalance,
val customerId: String,
val balance: Balance,
val cryptoCurrency: CryptoCurrency.Token,
val fiatRate: SerializedBigDecimal?,
) : PaymentAccountStatusValue() {
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
currency = cryptoCurrency,
value = buildCryptoCurrencyStatusValue(
amount = cryptoBalance.balance,
fiatAmount = fiatBalance.availableBalance,
amount = balance.cryptoBalance.balance,
fiatAmount = balance.fiatBalance.availableBalance,
fiatRate = fiatRate,
depositAddress = cryptoBalance.depositAddress,
depositAddress = balance.cryptoBalance.depositAddress,
),
)
}
@ -135,11 +135,9 @@ sealed class PaymentAccountStatusValue {
*
* @property source The source of the status information.
* @property customerId The unique identifier of the customer.
* @property currencyCode The code of the currency.
* @property depositAddress The address for deposits, if available.
* @property fiatBalance The fiat balance details.
* @property cryptoBalance The crypto balance details.
* @property availableForWithdrawal The crypto amount currently available for withdrawal/swap (excludes pending/locked funds).
* @property balance The balance details (fiat, crypto and amount available for withdrawal).
* The fiat currency code is available via [Balance.fiatBalance].
* @property cryptoCurrency The crypto currency held by the account.
* @property cards The list of user's cards.
* @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency,
@ -150,11 +148,8 @@ sealed class PaymentAccountStatusValue {
data class Loaded(
override val source: StatusSource,
val customerId: String,
val currencyCode: String,
val depositAddress: String?,
val fiatBalance: FiatBalance,
val cryptoBalance: CryptoBalance,
val availableForWithdrawal: SerializedBigDecimal,
val balance: Balance,
val cryptoCurrency: CryptoCurrency.Token,
val cards: List<TangemPayCard>,
val fiatRate: SerializedBigDecimal?,
@ -162,10 +157,10 @@ sealed class PaymentAccountStatusValue {
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
currency = cryptoCurrency,
value = buildCryptoCurrencyStatusValue(
amount = availableForWithdrawal,
fiatAmount = fiatBalance.availableBalance,
amount = balance.availableForWithdrawal,
fiatAmount = balance.fiatBalance.availableBalance,
fiatRate = fiatRate,
depositAddress = cryptoBalance.depositAddress,
depositAddress = balance.cryptoBalance.depositAddress,
),
)
}
@ -202,6 +197,21 @@ sealed class PaymentAccountStatusValue {
}
}
/**
* Aggregates all balance data of a payment account, as returned by the `customer/me` endpoint.
*
* @property fiatBalance The fiat balance details.
* @property cryptoBalance The crypto balance details.
* @property availableForWithdrawal The crypto amount currently available for withdrawal/swap
* (excludes pending/locked funds).
*/
@Serializable
data class Balance(
val fiatBalance: FiatBalance,
val cryptoBalance: CryptoBalance,
val availableForWithdrawal: SerializedBigDecimal,
)
/**
* Represents the fiat balance of the payment account.
*

View file

@ -28,6 +28,7 @@ data class CustomerInfo(
val state: State,
val fiatBalance: PaymentAccountStatusValue.FiatBalance?,
val cryptoBalance: PaymentAccountStatusValue.CryptoBalance?,
val availableForWithdrawal: BigDecimal?,
) {
enum class State {
NEW,

View file

@ -271,7 +271,7 @@ internal class SendDestinationModel @Inject constructor(
private fun AccountStatus.Payment.getDestinationWalletUM(wallet: UserWallet): DestinationWalletUM? {
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: return null
val (paymentAccountAddress, currency) = when (val status = this.value) {
is PaymentAccountStatusValue.Loaded -> status.cryptoBalance.depositAddress to status.cryptoCurrency
is PaymentAccountStatusValue.Loaded -> status.balance.cryptoBalance.depositAddress to status.cryptoCurrency
else -> return null
}

View file

@ -97,7 +97,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
?.amount
val currency = getJavaCurrencyByCode(status.currencyCode)
val currency = getJavaCurrencyByCode(status.balance.fiatBalance.currency)
uiState.update { state ->
val amount = if (index == 0) {
currentLimit?.stripTrailingZeros()?.toPlainString().orEmpty()

View file

@ -118,8 +118,9 @@ internal class TangemPayCardPageModel @Inject constructor(
val dailyLimitState = if (limit != null) {
TangemPayDailyLimitBlockState.Content(
limit = limit.amount.format {
val symbol = getJavaCurrencyByCode(status.currencyCode).symbol
fiat(status.currencyCode, symbol).optionalDecimals()
val currencyCode = status.balance.fiatBalance.currency
val symbol = getJavaCurrencyByCode(currencyCode).symbol
fiat(currencyCode, symbol).optionalDecimals()
},
onChangeClick = ::onClickLimitChange,
)

View file

@ -25,8 +25,8 @@ import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
@ -40,8 +40,6 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
import com.tangem.features.tangempay.model.transformers.*
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
import com.tangem.features.tangempay.utils.*
@ -52,7 +50,6 @@ import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -70,33 +67,36 @@ internal class TangemPayDetailsModel @Inject constructor(
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val uiMessageSender: UiMessageSender,
private val cardDetailsEventListener: CardDetailsEventListener,
private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener,
private val tangemPayWithdrawRepository: TangemPayWithdrawRepository,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
private val userWalletId = params.initialStatus.userWalletId
private val isTangemPayDeactivated = params.initialStatus.isDeactivated
private val loaded: PaymentAccountStatusValue.Loaded? =
params.initialStatus.value as? PaymentAccountStatusValue.Loaded
private val firstCard = loaded?.cards?.firstOrNull()
val cryptoCurrency: CryptoCurrency = params.initialStatus.cryptoCurrency
private val initialCardFrozenState: TangemPayCardFrozenState = when {
firstCard == null -> TangemPayCardFrozenState.Unfrozen
else -> firstCard.frozenState
}
private val initialCard = params.initialStatus.ifLoadedOrNull { it.cards.firstOrNull() }
private val currentStatus = MutableStateFlow(params.initialStatus)
private val userWalletId
get() = currentStatus.value.userWalletId
val cryptoCurrency
get() = currentStatus.value.cryptoCurrency
private val stateFactory = TangemPayDetailsStateFactory(
onBack = router::pop,
onOpenMenu = ::onOpenMenu,
intents = this,
cardFrozenState = initialCardFrozenState,
cardFrozenState = when {
initialCard == null -> TangemPayCardFrozenState.Unfrozen
else -> initialCard.frozenState
},
isRedesignEnabled = isRedesignEnabled(),
)
@ -104,33 +104,42 @@ internal class TangemPayDetailsModel @Inject constructor(
field = MutableStateFlow(
stateFactory.getInitialState(
isTangemPayDeactivated = isTangemPayDeactivated,
cardNumberEnd = firstCard?.lastDigits.orEmpty(),
isReissuing = firstCard == null || firstCard.state != TangemPayCardState.Active,
cardNumberEnd = initialCard?.lastDigits.orEmpty(),
isReissuing = initialCard == null || initialCard.state != TangemPayCardState.Active,
),
)
private val refreshStateJobHolder = JobHolder()
private val fetchBalanceJobHolder = JobHolder()
private val addToWalletBannerJobHolder = JobHolder()
private var balance: TangemPayCardBalance? = null
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
init {
analytics.send(TangemPayAnalyticsEvents.MainScreenOpened())
handleBalanceHiding()
fetchBalance()
if (!isTangemPayDeactivated && firstCard != null) {
subscribeToCardFrozenState(firstCard.id)
fetchAddToWalletBanner()
paymentAccountStatusSupplier.invoke(userWalletId)
val statusFlow = paymentAccountStatusSupplier.invoke(userWalletId)
.onEach { status -> currentStatus.update { status } }
.map { it.value }
if (isTangemPayDeactivated) {
statusFlow
.filterIsInstance<PaymentAccountStatusValue.Deactivated>()
.onEach { state ->
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
}
.launchIn(modelScope)
} else {
if (initialCard != null) {
subscribeToCardFrozenState(initialCard.id)
}
fetchAddToWalletBanner()
statusFlow
.filterIsInstance<PaymentAccountStatusValue.Loaded>()
.filter { it.source == StatusSource.ACTUAL }
.onEach { state ->
val card = state.cards.firstOrNull() ?: return@onEach
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
state.cards.firstOrNull()?.let { card ->
uiState.update(
TangemPayCardDataTransformer(
card = card,
@ -138,6 +147,7 @@ internal class TangemPayDetailsModel @Inject constructor(
),
)
}
}
.launchIn(modelScope)
}
}
@ -165,17 +175,16 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onClickAddFunds() {
analytics.send(TangemPayAnalyticsEvents.AddFundsClicked())
val currentBalance = balance
val depositAddress = currentBalance?.depositAddress
if (currentBalance == null || depositAddress == null) {
val balance = currentStatus.value.balanceOrNull()
if (balance == null) {
showBottomSheetError(TangemPayDetailsErrorType.Receive)
} else {
bottomSheetNavigation.activate(
TangemPayDetailsNavigation.AddFunds(
walletId = userWalletId,
fiatBalance = currentBalance.availableForWithdrawal,
cryptoBalance = currentBalance.availableForWithdrawal,
depositAddress = depositAddress,
fiatBalance = balance.availableForWithdrawal,
cryptoBalance = balance.availableForWithdrawal,
depositAddress = balance.cryptoBalance.depositAddress,
cryptoCurrency = cryptoCurrency,
),
)
@ -184,12 +193,6 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onClickWithdraw() {
analytics.send(TangemPayAnalyticsEvents.WithdrawClicked())
val currentBalance = balance
val depositAddress = currentBalance?.depositAddress
if (currentBalance == null || depositAddress == null) {
showBottomSheetError(TangemPayDetailsErrorType.Withdraw)
return
}
modelScope.launch {
val hasActiveWithdrawal = tangemPayWithdrawRepository.hasWithdrawOrder(userWalletId)
if (hasActiveWithdrawal) {
@ -197,18 +200,19 @@ internal class TangemPayDetailsModel @Inject constructor(
} else {
uiMessageSender.send(
message = TangemPayMessagesFactory.createWithdrawWarning(
onGotItClick = { onConfirmWithdrawal(cryptoCurrency, currentBalance, depositAddress) },
onGotItClick = { onConfirmWithdrawal(cryptoCurrency) },
),
)
}
}
}
private fun onConfirmWithdrawal(
currency: CryptoCurrency,
currentBalance: TangemPayCardBalance,
depositAddress: String,
) {
private fun onConfirmWithdrawal(currency: CryptoCurrency) {
val balance = currentStatus.value.balanceOrNull()
if (balance == null) {
showBottomSheetError(TangemPayDetailsErrorType.Withdraw)
return
}
router.push(
AppRoute.Swap(
cryptoCurrency = currency,
@ -216,27 +220,15 @@ internal class TangemPayDetailsModel @Inject constructor(
screenSource = AnalyticsParam.ScreensSources.TangemPay.value,
currencyPosition = AppRoute.Swap.CurrencyPosition.FROM,
tangemPayInput = AppRoute.Swap.TangemPayInput(
cryptoAmount = currentBalance.availableForWithdrawal,
fiatAmount = currentBalance.availableForWithdrawal,
depositAddress = depositAddress,
cryptoAmount = balance.availableForWithdrawal,
fiatAmount = balance.availableForWithdrawal,
depositAddress = balance.cryptoBalance.depositAddress,
isWithdrawal = true,
),
),
)
}
private fun fetchBalance(): Job {
return modelScope.launch {
val result = try {
cardDetailsRepository.getCardBalance(userWalletId).onRight { balance = it }
} catch (e: Exception) {
TangemLogger.e("Error", e)
return@launch
}
uiState.update(transformer = DetailsBalanceTransformer(balance = result))
}.saveIn(fetchBalanceJobHolder)
}
private fun fetchAddToWalletBanner() {
modelScope.launch {
val isDone = try {
@ -263,7 +255,7 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onContactSupportClicked() {
analytics.send(Basic.ButtonSupport(source = AnalyticsParam.ScreensSources.TangemPay))
val customerId = loaded?.customerId ?: return
val customerId = currentStatus.value.ifLoadedOrNull { it.customerId } ?: return
modelScope.launch {
sendFeedbackEmailUseCase.invoke(
type = FeedbackEmailType.Visa.FeatureIsBeta(
@ -277,10 +269,9 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onRefreshSwipe(refreshState: ShowRefreshState) {
modelScope.launch {
uiState.update(TangemPayDetailsRefreshTransformer(isRefreshing = refreshState.value))
cardDetailsEventListener.send(CardDetailsEvent.Hide)
paymentAccountStatusFetcher.invoke(userWalletId)
expressTransactionsEventListener.send(ExpressTransactionsEvent.Update)
txHistoryUpdateListener.triggerUpdate()
fetchBalance().join()
uiState.update(TangemPayDetailsRefreshTransformer(isRefreshing = false))
}.saveIn(refreshStateJobHolder)
}

View file

@ -1,45 +1,32 @@
package com.tangem.features.tangempay.model.transformers
import arrow.core.Either
import com.tangem.core.error.UniversalError
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
import java.util.Currency
internal class DetailsBalanceTransformer(
private val balance: Either<UniversalError, TangemPayCardBalance>,
private val fiatBalance: PaymentAccountStatusValue.FiatBalance,
) : Transformer<TangemPayDetailsUM> {
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
val balance = when (balance) {
is Either.Left<UniversalError> -> {
TangemPayDetailsBalanceBlockState.Error(
actionButtons = persistentListOf(),
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
)
}
is Either.Right<TangemPayCardBalance> -> {
TangemPayDetailsBalanceBlockState.Content(
val balance = TangemPayDetailsBalanceBlockState.Content(
isBalanceFlickering = false,
fiatBalance = getFiatBalanceText(balance.value),
fiatBalance = getFiatBalanceText(fiatBalance),
actionButtons = prevState.balanceBlockState.actionButtons,
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
)
}
}
return prevState.copy(balanceBlockState = balance)
}
private fun getFiatBalanceText(balance: TangemPayCardBalance): TextReference {
val currency = Currency.getInstance(balance.currencyCode)
return balance.fiatBalance.formatStyled {
private fun getFiatBalanceText(fiatBalance: PaymentAccountStatusValue.FiatBalance): TextReference {
val currency = Currency.getInstance(fiatBalance.currency)
return fiatBalance.availableBalance.formatStyled {
fiat(
fiatCurrencyCode = currency.currencyCode,
fiatCurrencySymbol = currency.symbol,

View file

@ -36,6 +36,12 @@ internal inline fun <T> AccountStatus.Payment.ifLoadedOrNull(call: (PaymentAccou
}
}
internal fun AccountStatus.Payment.balanceOrNull(): PaymentAccountStatusValue.Balance? = when (val v = value) {
is PaymentAccountStatusValue.Loaded -> v.balance
is PaymentAccountStatusValue.Deactivated -> v.balance
else -> null
}
internal fun AccountStatus.Payment.findCard(
initialCardId: String,
initialStatus: AccountStatus.Payment,

View file

@ -85,7 +85,11 @@ internal class TangemPayCardLimitSetupModelTest {
val statusWithLimit: PaymentAccountStatusValue.Loaded = mockk(relaxed = true) {
every { source } returns StatusSource.ACTUAL
every { cards } returns listOf(cardWithLimit)
every { currencyCode } returns "USD"
every { balance } returns mockk(relaxed = true) {
every { fiatBalance } returns mockk(relaxed = true) {
every { currency } returns "USD"
}
}
}
val paymentStatusWithLimit: AccountStatus.Payment = mockk(relaxed = true) {
every { value } returns statusWithLimit

View file

@ -58,8 +58,8 @@ internal class TangemPayMainBlockConverter(
subtitle = TextReference.Res(R.string.tangempay_status_deactivated),
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.fiatBalance.currency,
balance = statusValue.fiatBalance.availableBalance,
currencyCode = statusValue.balance.fiatBalance.currency,
balance = statusValue.balance.fiatBalance.availableBalance,
),
balanceSubtitle = stringReference(statusValue.cryptoCurrency.symbol),
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
@ -75,8 +75,8 @@ internal class TangemPayMainBlockConverter(
},
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.currencyCode,
balance = statusValue.fiatBalance.availableBalance,
currencyCode = statusValue.balance.fiatBalance.currency,
balance = statusValue.balance.fiatBalance.availableBalance,
),
balanceSubtitle = stringReference(statusValue.cryptoCurrency.symbol),
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,