Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-01 04:32:24 -07:00
parent b27ff31652
commit ad0b09deae
24 changed files with 397 additions and 40 deletions

View file

@ -11,6 +11,7 @@ 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
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.models.wallet.UserWalletId
import javax.inject.Inject
import javax.inject.Singleton
@ -52,7 +53,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
adminDailyLimit = card.limit?.adminCardLimit?.amount,
frozenState = card.frozenState.toString(),
lastDigits = card.lastDigits,
isReissuing = card.isReissuing,
state = card.state.toString(),
)
},
)
@ -108,7 +109,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
),
frozenState = TangemPayCardFrozenState.fromString(card.frozenState),
lastDigits = card.lastDigits,
isReissuing = card.isReissuing,
state = TangemPayCardState.fromString(card.state),
)
},
)

View file

@ -28,6 +28,7 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.*
import com.tangem.domain.pay.usecase.ChangeCardFrozenStateUseCase
import com.tangem.domain.pay.usecase.CloseTangemPayCardUseCase
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
import com.tangem.domain.pay.usecase.ReissueTangemPayCardUseCase
@ -73,6 +74,10 @@ internal interface TangemPayDataModule {
@Singleton
fun bindReissueCardRepository(repository: DefaultReissueCardRepository): TangemPayReissueCardRepository
@Binds
@Singleton
fun bindCloseCardRepository(repository: DefaultCloseCardRepository): TangemPayCloseCardRepository
@Binds
@Singleton
fun bindTangemPayCryptoCurrencyFactory(
@ -220,5 +225,20 @@ internal interface TangemPayDataModule {
paymentAccountStatusFetcher = paymentAccountStatusFetcher,
)
}
@Provides
fun provideCloseTangemPayCardUseCase(
closeCardRepository: TangemPayCloseCardRepository,
startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase,
appCoroutineScope: AppCoroutineScope,
paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
): CloseTangemPayCardUseCase {
return CloseTangemPayCardUseCase(
closeCardRepository = closeCardRepository,
startTangemPayOrderPollingUseCase = startTangemPayOrderPollingUseCase,
appCoroutineScope = appCoroutineScope,
paymentAccountStatusFetcher = paymentAccountStatusFetcher,
)
}
}
}

View file

@ -23,7 +23,10 @@ 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.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.pay.model.isFinalStatus
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayCloseCardRepository
import com.tangem.security.DeviceSecurityInfoProvider
import com.tangem.security.isSecurityExposed
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -36,7 +39,7 @@ import kotlin.time.Duration.Companion.minutes
private const val TAG = "PaymentAccountStatusFetcher"
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private val paymentAccountStatusesStore: PaymentAccountStatusesStore,
private val onboardingRepository: OnboardingRepository,
@ -46,6 +49,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
private val eligibilityManager: TangemPayEligibilityManager,
private val reissueCardRepository: TangemPayReissueCardRepository,
private val closeCardRepository: TangemPayCloseCardRepository,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
) : PaymentAccountStatusFetcher {
@ -299,16 +303,9 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
cardInfo: CustomerInfo.CardInfo,
customerId: String,
): PaymentAccountStatusValue {
val reissueOrder = reissueCardRepository.getReissueOrderInfo(
userWalletId = userWalletId,
cardId = productInstance.cardId,
).getOrNull()
val isReissuing = reissueOrder != null &&
reissueOrder.orderStatus != OrderStatus.CANCELED &&
reissueOrder.orderStatus != OrderStatus.COMPLETED
val cardFrozenState = cardDetailsRepository.cardFrozenStateSync(productInstance.cardId)
val cardId = productInstance.cardId
val cardState = getCardState(cardId, userWalletId)
val cardFrozenState = cardDetailsRepository.cardFrozenStateSync(cardId)
val cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId)
return PaymentAccountStatusValue.Loaded(
source = StatusSource.ACTUAL,
@ -321,7 +318,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
cryptoCurrency = cryptoCurrency,
cards = listOf(
TangemPayCard(
id = productInstance.cardId,
id = cardId,
hasPinCode = cardInfo.isPinSet,
displayName = productInstance.displayName,
limit = TangemPayCardLimitData(
@ -334,12 +331,35 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
productInstance.frozenState
},
lastDigits = cardInfo.lastFourDigits,
isReissuing = isReissuing,
state = cardState,
),
),
)
}
private suspend fun getCardState(cardId: String, userWalletId: UserWalletId): TangemPayCardState {
val closingOrderId = closeCardRepository.getCloseOrderId(userWalletId, cardId).getOrNull()
val reissueOrderId = reissueCardRepository.getReissueOrderId(userWalletId, cardId).getOrNull()
return if (closingOrderId != null) {
val order = cardDetailsRepository.getOrderInfo(userWalletId, closingOrderId).getOrNull()
if (order != null && order.orderStatus.isFinalStatus) {
closeCardRepository.setCloseOrderId(cardId, null)
TangemPayCardState.Active
} else {
TangemPayCardState.Closing
}
} else if (reissueOrderId != null) {
val order = cardDetailsRepository.getOrderInfo(userWalletId, reissueOrderId).getOrNull()
if (order != null && order.orderStatus.isFinalStatus) {
TangemPayCardState.Active
} else {
TangemPayCardState.Reissuing
}
} else {
TangemPayCardState.Active
}
}
private suspend fun VisaApiError.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue {
return when (this) {
is VisaApiError.RefreshTokenExpired -> PaymentAccountStatusValue.Error.NotSynced

View file

@ -0,0 +1,52 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.right
import com.tangem.core.error.UniversalError
import com.tangem.data.pay.util.OrderStatusConverter
import com.tangem.datasource.api.pay.TangemPayApi
import com.tangem.datasource.api.pay.models.request.CloseCardRequest
import com.tangem.datasource.local.visa.TangemPayCloseCardStore
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.TangemPayOrderInfo
import com.tangem.domain.pay.repository.TangemPayCloseCardRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.utils.coroutines.runSuspendCatching
import javax.inject.Inject
internal class DefaultCloseCardRepository @Inject constructor(
private val tangemPayApi: TangemPayApi,
private val requestHelper: TangemPayRequestPerformer,
private val tangemPayCloseCardStore: TangemPayCloseCardStore,
) : TangemPayCloseCardRepository {
override suspend fun closeCard(
userWalletId: UserWalletId,
cardId: String,
): Either<VisaApiError, TangemPayOrderInfo> = either {
val response = requestHelper.performRequest(userWalletId) { authHeader ->
tangemPayApi.closeCard(
authHeader = authHeader,
body = CloseCardRequest(cardId = cardId),
)
}.bind()
TangemPayOrderInfo(
orderId = response.result.orderId,
orderStatus = OrderStatusConverter.convert(response.result.status),
)
}
override suspend fun setCloseOrderId(cardId: String, orderId: String?): Either<UniversalError, Unit> =
runSuspendCatching {
tangemPayCloseCardStore.setCloseOrderId(cardId, orderId)
}.fold(
onSuccess = { Unit.right() },
onFailure = { Either.Left(VisaApiError.Unspecified) },
)
override suspend fun getCloseOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> =
either {
runSuspendCatching { tangemPayCloseCardStore.getOrderId(cardId) }.getOrNull()
}
}

View file

@ -11,7 +11,6 @@ import com.tangem.datasource.local.visa.TangemPayReissueCardStore
import com.tangem.domain.models.pay.TangemPayReissueCardFee
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.TangemPayOrderInfo
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
import com.tangem.domain.visa.error.VisaApiError
import com.tangem.utils.coroutines.runSuspendCatching
@ -21,7 +20,6 @@ internal class DefaultReissueCardRepository @Inject constructor(
private val tangemPayApi: TangemPayApi,
private val requestHelper: TangemPayRequestPerformer,
private val tangemPayReissueCardStore: TangemPayReissueCardStore,
private val cardDetailsRepository: TangemPayCardDetailsRepository,
) : TangemPayReissueCardRepository {
override suspend fun getReissueCardFee(userWalletId: UserWalletId): Either<VisaApiError, TangemPayReissueCardFee> =
@ -74,17 +72,11 @@ internal class DefaultReissueCardRepository @Inject constructor(
onFailure = { Either.Left(VisaApiError.Unspecified) },
)
override suspend fun getReissueOrderInfo(
override suspend fun getReissueOrderId(
userWalletId: UserWalletId,
cardId: String,
): Either<UniversalError, TangemPayOrderInfo?> = either {
val orderId = runSuspendCatching { tangemPayReissueCardStore.getOrderId(cardId) }.getOrNull()
if (orderId == null) {
return null.right()
}
cardDetailsRepository.getOrderInfo(userWalletId, orderId).bind()
): Either<UniversalError, String?> = either {
runSuspendCatching { tangemPayReissueCardStore.getOrderId(cardId) }.getOrNull()
}
private companion object {