Updated on 2026-08-14
This commit is contained in:
parent
f086d6bb74
commit
40fe8bb5cb
56 changed files with 82 additions and 1491 deletions
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.domain.tokens.wallet
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
|
|
@ -147,18 +146,10 @@ class WalletBalanceFetcher internal constructor(
|
|||
|
||||
fetchExpressAssets(userWallet = userWallet, currencies = currencies)
|
||||
|
||||
fetcher.fetch(
|
||||
userWalletId = userWalletId,
|
||||
currencies = currencies,
|
||||
paymentAccountRefactorEnabled = params.isPaymentAccountRefactorEnabled,
|
||||
)
|
||||
fetcher.fetch(userWalletId = userWalletId, currencies = currencies)
|
||||
}
|
||||
|
||||
private suspend fun BaseWalletBalanceFetcher.fetch(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
paymentAccountRefactorEnabled: Boolean,
|
||||
) {
|
||||
private suspend fun BaseWalletBalanceFetcher.fetch(userWalletId: UserWalletId, currencies: Set<CryptoCurrency>) {
|
||||
coroutineScope {
|
||||
// Fetch balance sources in parallel
|
||||
val balanceErrors = fetchingSources.filterIsInstance<WalletFetchingSource.Balance>()
|
||||
|
|
@ -182,7 +173,7 @@ class WalletBalanceFetcher internal constructor(
|
|||
|
||||
// Fetch TangemPay separately — may run long-polling, so it must not block balance error checking
|
||||
if (fetchingSources.any { it is WalletFetchingSource.TangemPay }) {
|
||||
fetchPaymentAccount(userWalletId, paymentAccountRefactorEnabled)
|
||||
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -197,19 +188,10 @@ class WalletBalanceFetcher internal constructor(
|
|||
expressServiceFetcher.fetch(userWallet = userWallet, assetIds = assetIds)
|
||||
}
|
||||
|
||||
private suspend fun fetchPaymentAccount(
|
||||
userWalletId: UserWalletId,
|
||||
paymentAccountRefactorEnabled: Boolean,
|
||||
): Either<Throwable, Unit> {
|
||||
if (!paymentAccountRefactorEnabled) return Unit.right()
|
||||
|
||||
return paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Params of [WalletBalanceFetcher]
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
*/
|
||||
data class Params(val userWalletId: UserWalletId, val isPaymentAccountRefactorEnabled: Boolean)
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -133,7 +132,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -171,7 +169,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -209,7 +206,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -260,7 +256,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -315,7 +310,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -376,7 +370,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -428,7 +421,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -479,7 +471,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -536,7 +527,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -607,7 +597,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -682,7 +671,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -743,7 +731,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -802,7 +789,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -869,7 +855,6 @@ internal class WalletBalanceFetcherTest {
|
|||
val actual = fetcher(
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
isPaymentAccountRefactorEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,8 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
enum class OrderStatus {
|
||||
UNKNOWN, // TODO remove it after TangemPay accounts refactor TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
|
||||
NEW,
|
||||
PROCESSING,
|
||||
COMPLETED,
|
||||
CANCELED,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String) = when (value.uppercase(Locale.US)) {
|
||||
"NEW" -> NEW
|
||||
"PROCESSING" -> PROCESSING
|
||||
"COMPLETED" -> COMPLETED
|
||||
"CANCELED" -> CANCELED
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.*
|
||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class TangemPayMainScreenCustomerInfoUseCase(
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val eligibilityManager: TangemPayEligibilityManager,
|
||||
private val deviceSecurity: DeviceSecurityInfoProvider,
|
||||
) {
|
||||
|
||||
val state: StateFlow<Map<UserWalletId, Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>>>
|
||||
field = MutableStateFlow(value = mapOf())
|
||||
|
||||
private val logger = TangemLogger.withTag("TangemPayMainScreenCustomerInfoUseCase")
|
||||
|
||||
suspend fun fetch(userWalletId: UserWalletId) {
|
||||
logger.i("fetch: ${userWalletId.stringValue}")
|
||||
|
||||
if (onboardingRepository.isTangemPayDeactivated(userWalletId)) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
return
|
||||
}
|
||||
|
||||
if (deviceSecurity.isSecurityExposed()) {
|
||||
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
||||
logger.i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
||||
logger.i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
|
||||
|
||||
updateState(userWalletId = userWalletId, either = TangemPayCustomerInfoError.ExposedDeviceError.left())
|
||||
return // fast exit
|
||||
}
|
||||
|
||||
onboardingRepository.hasTangemPayInWallet(userWalletId)
|
||||
.fold(
|
||||
ifLeft = { error ->
|
||||
logger.e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}")
|
||||
if (error is VisaApiError.NotPaeraCustomer) {
|
||||
showOnboardingBannerIfEligible(userWalletId)
|
||||
} else {
|
||||
updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left())
|
||||
}
|
||||
},
|
||||
ifRight = { hasTangemPay ->
|
||||
logger.i("checkCustomerWallet for $userWalletId: $hasTangemPay")
|
||||
if (hasTangemPay) {
|
||||
val oldResult = state.value[userWalletId]
|
||||
if (oldResult == null) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Loading.right())
|
||||
}
|
||||
|
||||
val result = proceedWithPaeraCustomerResult(userWalletId)
|
||||
if (result.leftOrNull() is TangemPayCustomerInfoError.DeactivatedError) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
return
|
||||
}
|
||||
updateState(userWalletId, result.map(MainCustomerInfoContentState::Content))
|
||||
} else {
|
||||
// if there's no tangem pay, check eligibility and show onboarding banner
|
||||
showOnboardingBannerIfEligible(userWalletId)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun showOnboardingBannerIfEligible(userWalletId: UserWalletId) {
|
||||
val tangemPayEntryPoint = TangemPayEntryPoint.BANNER
|
||||
if (eligibilityManager.isPaeraCustomerForAnyWallet(tangemPayEntryPoint)) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
return
|
||||
}
|
||||
val isEligible = eligibilityManager
|
||||
.getEligibleWallets(
|
||||
shouldExcludePaeraCustomers = false,
|
||||
entryPoint = tangemPayEntryPoint,
|
||||
)
|
||||
.any { it.walletId == userWalletId }
|
||||
if (isEligible) {
|
||||
if (onboardingRepository.getHideMainOnboardingBanner(userWalletId)) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
} else {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.OnboardingBanner.right())
|
||||
}
|
||||
} else {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
): Flow<Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>> {
|
||||
return state.mapNotNull { map -> map[userWalletId] }
|
||||
}
|
||||
|
||||
private fun updateState(
|
||||
userWalletId: UserWalletId,
|
||||
either: Either<TangemPayCustomerInfoError, MainCustomerInfoContentState>,
|
||||
) {
|
||||
state.update { currentMap ->
|
||||
currentMap.toMutableMap().apply { this[userWalletId] = either }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithPaeraCustomerResult(
|
||||
userWalletId: UserWalletId,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) {
|
||||
return TangemPayCustomerInfoError.RefreshNeededError.left()
|
||||
}
|
||||
val orderId = onboardingRepository.getOrderId(userWalletId)
|
||||
return if (orderId != null) {
|
||||
proceedWithOrderId(userWalletId = userWalletId, orderId = orderId)
|
||||
} else {
|
||||
proceedWithoutOrder(userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithoutOrder(
|
||||
userWalletId: UserWalletId,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
return onboardingRepository.getCustomerInfo(userWalletId)
|
||||
.mapLeft { error ->
|
||||
logger.e("mapErrorForCustomer: $error")
|
||||
error.mapErrorForCustomer()
|
||||
}
|
||||
.map { customerInfo ->
|
||||
logger.i("customerInfo")
|
||||
if (customerInfo.productInstance == null) {
|
||||
onboardingRepository.createOrder(userWalletId)
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.NEW)
|
||||
} else {
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.COMPLETED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithOrderId(
|
||||
userWalletId: UserWalletId,
|
||||
orderId: String,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
return customerOrderRepository.getOrderData(userWalletId, orderId = orderId)
|
||||
.fold(
|
||||
ifLeft = { error ->
|
||||
error.mapErrorForCustomer().left()
|
||||
},
|
||||
ifRight = { orderData ->
|
||||
if (orderData.status in setOf(OrderStatus.COMPLETED, OrderStatus.UNKNOWN)) {
|
||||
onboardingRepository.clearOrderId(userWalletId)
|
||||
}
|
||||
onboardingRepository.getCustomerInfo(userWalletId = userWalletId)
|
||||
.mapLeft { it.mapErrorForCustomer() }
|
||||
.map { customerInfo ->
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderData.status)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun VisaApiError.mapErrorForCustomer(): TangemPayCustomerInfoError {
|
||||
return when (this) {
|
||||
is VisaApiError.RefreshTokenExpired -> TangemPayCustomerInfoError.RefreshNeededError
|
||||
is VisaApiError.NotPaeraCustomer -> TangemPayCustomerInfoError.UnknownError
|
||||
is VisaApiError.Deactivated -> TangemPayCustomerInfoError.DeactivatedError
|
||||
else -> TangemPayCustomerInfoError.UnavailableError
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue