From b3640235eff73d92ade519e4500db972340a094b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Jun 2026 00:30:26 -0700 Subject: [PATCH] Updated on 2026-08-14 --- .../entity/PaymentAccountStatusValueDM.kt | 3 + .../PaymentAccountStatusValueDMConverter.kt | 31 +++-- .../DefaultPaymentAccountStatusFetcher.kt | 21 ++- .../data/pay/util/CustomerInfoConverter.kt | 1 + ...aymentAccountStatusValueDMConverterTest.kt | 22 ++- .../account/PaymentAccountStatusValue.kt | 50 ++++--- .../tangem/domain/pay/model/CustomerInfo.kt | 1 + .../destination/model/SendDestinationModel.kt | 2 +- .../setup/TangemPayCardLimitSetupModel.kt | 2 +- .../tangempay/model/TangemPayCardPageModel.kt | 5 +- .../tangempay/model/TangemPayDetailsModel.kt | 129 ++++++++---------- .../transformers/DetailsBalanceTransformer.kt | 35 ++--- .../utils/PaymentAccountStatusExt.kt | 6 + .../setup/TangemPayCardLimitSetupModelTest.kt | 6 +- .../converter/TangemPayMainBlockConverter.kt | 8 +- 15 files changed, 175 insertions(+), 147 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt index 71bce0a129..41b174cf8d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/entity/PaymentAccountStatusValueDM.kt @@ -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) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt index aab76125e5..c586313c67 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/PaymentAccountStatusValueDMConverter.kt @@ -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, - fiatBalance = value.fiatBalance.toDomain(), - cryptoBalance = value.cryptoBalance.toDomain(), - availableForWithdrawal = value.availableForWithdrawal, + 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, - fiatBalance = value.fiatBalance.toDomain(), - cryptoBalance = value.cryptoBalance.toDomain(), + customerId = value.customerId, + balance = PaymentAccountStatusValue.Balance( + fiatBalance = value.fiatBalance.toDomain(), + cryptoBalance = value.cryptoBalance.toDomain(), + availableForWithdrawal = value.availableForWithdrawal, + ), cryptoCurrency = cryptoCurrency, fiatRate = value.fiatRate, ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt index 746221090a..7c205fffd6 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -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, - fiatBalance = fiatBalance, - cryptoBalance = cryptoBalance, + 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, - fiatBalance = cardInfo.fiatBalance, - cryptoBalance = cardInfo.cryptoBalance, - availableForWithdrawal = cardInfo.availableForWithdrawal, + balance = PaymentAccountStatusValue.Balance( + fiatBalance = cardInfo.fiatBalance, + cryptoBalance = cardInfo.cryptoBalance, + availableForWithdrawal = cardInfo.availableForWithdrawal, + ), cryptoCurrency = cryptoCurrency, fiatRate = fiatRate, cards = listOf( diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/CustomerInfoConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/CustomerInfoConverter.kt index 746b701bc8..95f70c31bc 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/util/CustomerInfoConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/CustomerInfoConverter.kt @@ -65,6 +65,7 @@ internal object CustomerInfoConverter : Converter 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, 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. * diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt index 952e701938..fbc32175d5 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt @@ -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, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt index 86a15ad9e0..419e8a6b2b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/subcomponents/destination/model/SendDestinationModel.kt @@ -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 } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt index a3b81fab45..9b33e3f33a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModel.kt @@ -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() diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt index 9acc3c9982..31c439172e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt @@ -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, ) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index 5e47660a9b..67e7bc30fe 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -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,39 +104,49 @@ 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 = SlotNavigation() init { analytics.send(TangemPayAnalyticsEvents.MainScreenOpened()) handleBalanceHiding() - fetchBalance() - if (!isTangemPayDeactivated && firstCard != null) { - subscribeToCardFrozenState(firstCard.id) - fetchAddToWalletBanner() - paymentAccountStatusSupplier.invoke(userWalletId) - .map { it.value } + val statusFlow = paymentAccountStatusSupplier.invoke(userWalletId) + .onEach { status -> currentStatus.update { status } } + .map { it.value } + + if (isTangemPayDeactivated) { + statusFlow + .filterIsInstance() + .onEach { state -> + uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance)) + } + .launchIn(modelScope) + } else { + if (initialCard != null) { + subscribeToCardFrozenState(initialCard.id) + } + fetchAddToWalletBanner() + statusFlow .filterIsInstance() .filter { it.source == StatusSource.ACTUAL } .onEach { state -> - val card = state.cards.firstOrNull() ?: return@onEach - uiState.update( - TangemPayCardDataTransformer( - card = card, - onCardClick = { onCardClick() }, - ), - ) + uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance)) + state.cards.firstOrNull()?.let { card -> + uiState.update( + TangemPayCardDataTransformer( + card = card, + onCardClick = { onCardClick() }, + ), + ) + } } .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) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt index eca90fdbb4..5cbd4bdb05 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt @@ -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, + private val fiatBalance: PaymentAccountStatusValue.FiatBalance, ) : Transformer { override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM { - val balance = when (balance) { - is Either.Left -> { - TangemPayDetailsBalanceBlockState.Error( - actionButtons = persistentListOf(), - cardsBlockState = prevState.balanceBlockState.cardsBlockState, - ) - } - is Either.Right -> { - TangemPayDetailsBalanceBlockState.Content( - isBalanceFlickering = false, - fiatBalance = getFiatBalanceText(balance.value), - actionButtons = prevState.balanceBlockState.actionButtons, - cardsBlockState = prevState.balanceBlockState.cardsBlockState, - ) - } - } + val balance = TangemPayDetailsBalanceBlockState.Content( + isBalanceFlickering = false, + 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, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/PaymentAccountStatusExt.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/PaymentAccountStatusExt.kt index 29ab477218..56139acdaa 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/PaymentAccountStatusExt.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/PaymentAccountStatusExt.kt @@ -36,6 +36,12 @@ internal inline fun 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, diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt index 9808c4f907..e192e54e5f 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/limit/setup/TangemPayCardLimitSetupModelTest.kt @@ -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 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayMainBlockConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayMainBlockConverter.kt index c771ddd2c9..773354e3fe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayMainBlockConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TangemPayMainBlockConverter.kt @@ -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,