Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-28 03:22:15 -07:00
parent b43aa204d3
commit 40f7df2c5d
25 changed files with 90 additions and 48 deletions

View file

@ -1,7 +1,7 @@
package com.tangem.datasource.local.visa
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import kotlinx.coroutines.flow.Flow
internal class DefaultTangemPayCardFrozenStateStore(

View file

@ -1,6 +1,6 @@
package com.tangem.datasource.local.visa
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import kotlinx.coroutines.flow.Flow
interface TangemPayCardFrozenStateStore {

View file

@ -84,7 +84,7 @@ sealed interface PaymentAccountStatusValueDM {
@Json(name = "display_name") val displayName: String?,
@Json(name = "actual_daily_limit") val actualDailyLimit: SerializedBigDecimal?,
@Json(name = "admin_daily_limit") val adminDailyLimit: SerializedBigDecimal?,
@Json(name = "is_frozen") val isFrozen: Boolean,
@Json(name = "frozen_state") val frozenState: String,
@Json(name = "last_digits") val lastDigits: String,
@Json(name = "is_reissuing") val isReissuing: Boolean,
)

View file

@ -7,6 +7,7 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardLimit
import com.tangem.domain.models.pay.TangemPayCardLimitData
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
@ -49,7 +50,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
displayName = card.displayName?.value,
actualDailyLimit = card.limit?.actualCardLimit?.amount,
adminDailyLimit = card.limit?.adminCardLimit?.amount,
isFrozen = card.isFrozen,
frozenState = card.frozenState.toString(),
lastDigits = card.lastDigits,
isReissuing = card.isReissuing,
)
@ -105,7 +106,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
TangemPayCardLimit(limit, TangemPayCardLimitPeriod.DAY)
},
),
isFrozen = card.isFrozen,
frozenState = TangemPayCardFrozenState.fromString(card.frozenState),
lastDigits = card.lastDigits,
isReissuing = card.isReissuing,
)

View file

@ -22,7 +22,8 @@ import com.tangem.domain.pay.repository.CustomerOrderRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.security.DeviceSecurityInfoProvider
import com.tangem.security.isSecurityExposed
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -45,6 +46,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
private val eligibilityManager: TangemPayEligibilityManager,
private val reissueCardRepository: TangemPayReissueCardRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
) : PaymentAccountStatusFetcher {
private val logger = TangemLogger.withTag(TAG)
@ -306,6 +308,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
reissueOrder.orderStatus != OrderStatus.CANCELED &&
reissueOrder.orderStatus != OrderStatus.COMPLETED
val cardFrozenState = cardDetailsRepository.cardFrozenStateSync(productInstance.cardId)
val cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId)
return PaymentAccountStatusValue.Loaded(
source = StatusSource.ACTUAL,
@ -325,7 +328,11 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
actualCardLimit = productInstance.actualCardLimit,
adminCardLimit = productInstance.adminCardLimit,
),
isFrozen = productInstance.frozenState is TangemPayCardFrozenState.Frozen,
frozenState = if (cardFrozenState == TangemPayCardFrozenState.Pending) {
TangemPayCardFrozenState.Pending
} else {
productInstance.frozenState
},
lastDigits = cardInfo.lastFourDigits,
isReissuing = isReissuing,
),

View file

@ -30,7 +30,7 @@ import com.tangem.domain.pay.model.TangemPayCardDetails
import com.tangem.domain.pay.model.TangemPayOrderInfo
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

View file

@ -13,7 +13,7 @@ import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance.Status
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero

View file

@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable
* @property hasPinCode whether the card has a PIN code set.
* @property displayName optional human-readable name assigned to the card; `null` if not set.
* @property limit spending limit configuration for the card; `null` if not configured or not yet loaded.
* @property isFrozen whether the card is currently frozen (blocked for payments).
* @property frozenState whether the card is currently frozen (blocked for payments).
* @property lastDigits The last four digits of the card number.
*/
@Serializable
@ -20,7 +20,10 @@ data class TangemPayCard(
@SerialName("has_pin_code") val hasPinCode: Boolean,
@SerialName("display_name") val displayName: CardDisplayName?,
@SerialName("limit") val limit: TangemPayCardLimitData?,
@SerialName("is_frozen") val isFrozen: Boolean,
@SerialName("frozen_state") val frozenState: TangemPayCardFrozenState,
@SerialName("last_digits") val lastDigits: String,
@SerialName("is_reissuing") val isReissuing: Boolean,
)
)
val TangemPayCard.isFrozen
get() = frozenState == TangemPayCardFrozenState.Frozen

View file

@ -0,0 +1,31 @@
package com.tangem.domain.models.pay
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
enum class TangemPayCardFrozenState {
@SerialName("Pending")
Pending,
@SerialName("Frozen")
Frozen,
@SerialName("Unfrozen")
Unfrozen,
;
override fun toString() = when (this) {
Pending -> "Pending"
Frozen -> "Frozen"
Unfrozen -> "Unfrozen"
}
companion object {
fun fromString(value: String) = when (value) {
"Frozen" -> Frozen
"Unfrozen" -> Unfrozen
else -> Pending
}
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.domain.visa.model
import kotlinx.serialization.Serializable
@Serializable
sealed class TangemPayCardFrozenState {
data object Pending : TangemPayCardFrozenState()
data object Frozen : TangemPayCardFrozenState()
data object Unfrozen : TangemPayCardFrozenState()
}

View file

@ -4,7 +4,7 @@ import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.models.pay.TangemPayCardLimit
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import java.math.BigDecimal
import java.util.Locale

View file

@ -8,7 +8,7 @@ import com.tangem.domain.pay.model.SetPinResult
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayCardDetails
import com.tangem.domain.pay.model.TangemPayOrderInfo
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import kotlinx.coroutines.flow.Flow
interface TangemPayCardDetailsRepository {

View file

@ -3,10 +3,10 @@ package com.tangem.domain.pay.usecase
import arrow.core.Either
import arrow.core.raise.either
import com.tangem.core.error.UniversalError
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.utils.coroutines.AppCoroutineScope
import kotlinx.coroutines.async

View file

@ -4,12 +4,12 @@ import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth.assertThat
import com.tangem.common.test.TestAppCoroutineScope
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.pay.model.TangemPayOrderInfo
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyOrder

View file

@ -2,7 +2,7 @@ package com.tangem.features.tangempay.entity
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.model.CardDataType
import com.tangem.utils.StringsSigns
@ -26,7 +26,7 @@ internal class TangemPayCardDetailsBlockStateFactory(
onClick = onReveal,
onCopy = onCopy,
isHidden = true,
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
cardFrozenState = TangemPayCardFrozenState.Pending,
displayNameState = if (displayName != null) {
DisplayNameState.Display(
displayName = displayName.value,

View file

@ -7,7 +7,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.themedColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.utils.TangemPayDetailIntents
import kotlinx.collections.immutable.ImmutableList

View file

@ -5,7 +5,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.model.CardDataType
import kotlinx.collections.immutable.ImmutableList

View file

@ -14,6 +14,7 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.account.hasCardWithId
import com.tangem.domain.models.account.requireCardWithId
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
@ -29,6 +30,7 @@ import com.tangem.features.tangempay.model.transformers.DetailsRevealProgressSta
import com.tangem.features.tangempay.model.transformers.DetailsRevealedStateTransformer
import com.tangem.features.tangempay.model.transformers.TangemPayCardDetailsUpdateNameTransformer
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.utils.StringsSigns
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -96,22 +98,29 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
status.hasCardWithId(cardId)
) {
val card = status.requireCardWithId(cardId)
val displayName = card.displayName ?: return@onEach
if (card.isReissuing) {
hideCardDetails()
}
uiState.update(TangemPayCardDetailsUpdateNameTransformer(displayName))
uiState.update { it.copy(isActionsAvailable = !card.isReissuing) }
card.displayName?.let { uiState.update(TangemPayCardDetailsUpdateNameTransformer(it)) }
uiState.update { uiState ->
uiState.copy(
numberShort = "${StringsSigns.ASTERISK}${card.lastDigits}",
cardFrozenState = card.frozenState,
isActionsAvailable = !card.isReissuing,
)
}
}
}
.launchIn(modelScope)
}
private fun subscribeToCardFrozenState() {
cardDetailsRepository
.cardFrozenState(card.id)
.onEach { uiState.update { state -> state.copy(cardFrozenState = it) } }
cardDetailsRepository.cardFrozenState(card.id)
.onEach { cardFrozenState ->
if (cardFrozenState == TangemPayCardFrozenState.Pending) {
uiState.update { state -> state.copy(cardFrozenState = TangemPayCardFrozenState.Pending) }
}
}
.launchIn(modelScope)
}

View file

@ -27,6 +27,7 @@ import com.tangem.domain.models.account.hasCardWithId
import com.tangem.domain.models.account.requireCardWithId
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
import com.tangem.domain.models.pay.isFrozen
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository

View file

@ -29,7 +29,7 @@ import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.features.tangempay.TangemPayConstants
import com.tangem.features.tangempay.components.AddFundsListener
@ -86,8 +86,7 @@ internal class TangemPayDetailsModel @Inject constructor(
private val initialCardFrozenState: TangemPayCardFrozenState = when {
firstCard == null -> TangemPayCardFrozenState.Unfrozen
firstCard.isFrozen -> TangemPayCardFrozenState.Frozen
else -> TangemPayCardFrozenState.Unfrozen
else -> firstCard.frozenState
}
private val stateFactory = TangemPayDetailsStateFactory(

View file

@ -1,6 +1,6 @@
package com.tangem.features.tangempay.model.transformers
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer

View file

@ -28,7 +28,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.details.impl.R

View file

@ -50,7 +50,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TangemPayTestTags
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.DisplayNameState
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
@ -121,7 +121,7 @@ internal fun TangemPayCard(state: TangemPayCardDetailsUM, modifier: Modifier = M
private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize()) {
val imageResId = when (state.cardFrozenState) {
is TangemPayCardFrozenState.Frozen -> R.drawable.img_tangem_pay_visa_frozen
TangemPayCardFrozenState.Frozen -> R.drawable.img_tangem_pay_visa_frozen
else -> R.drawable.img_tangem_pay_visa
}
Image(
@ -180,7 +180,7 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
.padding(bottom = 8.dp),
)
when (state.cardFrozenState) {
is TangemPayCardFrozenState.Frozen -> Icon(
TangemPayCardFrozenState.Frozen -> Icon(
modifier = Modifier
.constrainAs(frozenIconRef) {
start.linkTo(cardNumberRef.end, margin = 4.dp)

View file

@ -28,7 +28,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.details.impl.R

View file

@ -10,6 +10,7 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardLimit
import com.tangem.domain.models.pay.TangemPayCardLimitData
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
@ -45,7 +46,7 @@ internal class TangemPayCardLimitSetupModelTest {
id = cardId,
hasPinCode = false,
displayName = null,
isFrozen = false,
frozenState = TangemPayCardFrozenState.Unfrozen,
lastDigits = "1234",
limit = null,
isReissuing = false,
@ -67,7 +68,7 @@ internal class TangemPayCardLimitSetupModelTest {
id = cardId,
hasPinCode = false,
displayName = null,
isFrozen = false,
frozenState = TangemPayCardFrozenState.Unfrozen,
lastDigits = "1234",
limit = TangemPayCardLimitData(
actualCardLimit = null,