Updated on 2026-08-14
This commit is contained in:
parent
e5c441a9f0
commit
acb79b2490
17 changed files with 491 additions and 15 deletions
|
|
@ -154,7 +154,7 @@ object PreferencesKeys {
|
|||
val TANGEM_PAY_ACTIVE_WITHDRAW_ORDERS_KEY by lazy {
|
||||
stringPreferencesKey(name = "tangemPayActiveWithdrawOrdersKey")
|
||||
}
|
||||
val TANGEM_PAY_ELIGIBILITY_KEY by lazy { stringSetPreferencesKey(name = "tangemPayEligibilityList") }
|
||||
val TANGEM_PAY_ELIGIBILITY_KEY by lazy { stringSetPreferencesKey(name = "tangemPayEligibilityListV2") }
|
||||
|
||||
fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key")
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ dependencies {
|
|||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.virtualAccount)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.pay.TangemPayEligibilityType
|
||||
import com.tangem.domain.models.pay.isTangemPayType
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.models.wallet.isTangemPayCompatible
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -85,7 +85,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
val wallets = userWalletsListRepository.userWallets.value ?: return emptyList()
|
||||
|
||||
val candidates = wallets.filter { wallet ->
|
||||
wallet.isMultiCurrency && !wallet.isLocked && wallet.isCompatible() &&
|
||||
wallet.isMultiCurrency && !wallet.isLocked && wallet.isTangemPayCompatible &&
|
||||
!onboardingRepository.isTangemPayDeactivated(wallet.walletId)
|
||||
}
|
||||
|
||||
|
|
@ -98,11 +98,6 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
return candidates
|
||||
}
|
||||
|
||||
private fun UserWallet.isCompatible(): Boolean = when (this) {
|
||||
is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
|
||||
}
|
||||
|
||||
private suspend fun List<UserWallet>.addPaeraCustomersData(): List<UserWalletData> {
|
||||
if (isEmpty()) return emptyList()
|
||||
|
||||
|
|
@ -139,7 +134,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
onboardingRepository.checkCustomerEligibility()
|
||||
}
|
||||
return if (entryPoint == null) {
|
||||
eligibility.isNotEmpty()
|
||||
eligibility.any { it.isTangemPayType }
|
||||
} else {
|
||||
eligibility.any { it == entryPoint.toEligibilityType() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,13 @@ import com.tangem.datasource.utils.mapWithStringKeyTypes
|
|||
import com.tangem.domain.virtualaccount.flow.VirtualAccountStatusFetcher
|
||||
import com.tangem.domain.virtualaccount.flow.VirtualAccountStatusProducer
|
||||
import com.tangem.domain.virtualaccount.flow.VirtualAccountStatusSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.virtualaccount.repository.VirtualAccountActivationRepository
|
||||
import com.tangem.domain.virtualaccount.usecase.ActivateVirtualAccountUseCase
|
||||
import com.tangem.domain.virtualaccount.usecase.GetVirtualAccountEligibilityUseCase
|
||||
import com.tangem.domain.virtualaccount.usecase.GetVirtualAccountSuitableWalletsUseCase
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -94,5 +99,26 @@ internal interface VirtualAccountDataModule {
|
|||
): ActivateVirtualAccountUseCase {
|
||||
return ActivateVirtualAccountUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetVirtualAccountSuitableWalletsUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
): GetVirtualAccountSuitableWalletsUseCase {
|
||||
return GetVirtualAccountSuitableWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideGetVirtualAccountEligibilityUseCase(
|
||||
getVirtualAccountSuitableWalletsUseCase: GetVirtualAccountSuitableWalletsUseCase,
|
||||
onboardingRepository: OnboardingRepository,
|
||||
deviceSecurityInfoProvider: DeviceSecurityInfoProvider,
|
||||
): GetVirtualAccountEligibilityUseCase {
|
||||
return GetVirtualAccountEligibilityUseCase(
|
||||
getVirtualAccountSuitableWalletsUseCase = getVirtualAccountSuitableWalletsUseCase,
|
||||
onboardingRepository = onboardingRepository,
|
||||
deviceSecurityInfoProvider = deviceSecurityInfoProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,42 @@ enum class TangemPayEligibilityType {
|
|||
|
||||
BANNER,
|
||||
DETAILS,
|
||||
DEEPLINK,
|
||||
|
||||
BANNER_VIRTUAL_ACCOUNT,
|
||||
DETAILS_VIRTUAL_ACCOUNT,
|
||||
DEEPLINK_VIRTUAL_ACCOUNT,
|
||||
|
||||
UNKNOWN,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String): TangemPayEligibilityType = when (value.lowercase()) {
|
||||
"banner" -> BANNER
|
||||
"details" -> DETAILS
|
||||
fun fromString(value: String): TangemPayEligibilityType = when (value.uppercase()) {
|
||||
"BANNER" -> BANNER
|
||||
"DETAILS" -> DETAILS
|
||||
"DEEPLINK" -> DEEPLINK
|
||||
"BANNER_VIRTUAL_ACCOUNT" -> BANNER_VIRTUAL_ACCOUNT
|
||||
"DETAILS_VIRTUAL_ACCOUNT" -> DETAILS_VIRTUAL_ACCOUNT
|
||||
"DEEPLINK_VIRTUAL_ACCOUNT" -> DEEPLINK_VIRTUAL_ACCOUNT
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val TangemPayEligibilityType.isVirtualAccountType: Boolean
|
||||
get() = this in VIRTUAL_ACCOUNT_TYPES
|
||||
|
||||
val TangemPayEligibilityType.isTangemPayType: Boolean
|
||||
get() = this in TANGEM_PAY_TYPES
|
||||
|
||||
private val VIRTUAL_ACCOUNT_TYPES = setOf(
|
||||
TangemPayEligibilityType.BANNER_VIRTUAL_ACCOUNT,
|
||||
TangemPayEligibilityType.DETAILS_VIRTUAL_ACCOUNT,
|
||||
TangemPayEligibilityType.DEEPLINK_VIRTUAL_ACCOUNT,
|
||||
)
|
||||
|
||||
private val TANGEM_PAY_TYPES = setOf(
|
||||
TangemPayEligibilityType.BANNER,
|
||||
TangemPayEligibilityType.DETAILS,
|
||||
TangemPayEligibilityType.DEEPLINK,
|
||||
)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.models.wallet
|
||||
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.models.MobileWallet
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -119,3 +120,9 @@ val UserWallet.isLocked
|
|||
|
||||
inline val UserWallet.isHotWallet get() = this is UserWallet.Hot
|
||||
inline val UserWallet.isColdWallet get() = this is UserWallet.Cold
|
||||
|
||||
val UserWallet.isTangemPayCompatible: Boolean
|
||||
get() = when (this) {
|
||||
is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
|
||||
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
|
||||
}
|
||||
|
|
@ -10,4 +10,24 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - Domain */
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.virtualAccount.models)
|
||||
implementation(projects.domain.common)
|
||||
implementation(projects.domain.visa)
|
||||
|
||||
/** Project - Core */
|
||||
implementation(projects.core.security)
|
||||
|
||||
/** Coroutines */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(deps.test.junit5)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(projects.test.core)
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.domain.card)
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.domain.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.virtualaccount.model
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
sealed interface VirtualAccountEligibility {
|
||||
|
||||
data class Available(
|
||||
val wallets: List<UserWallet>,
|
||||
) : VirtualAccountEligibility
|
||||
|
||||
data object NotAvailable : VirtualAccountEligibility
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.domain.virtualaccount.model
|
||||
|
||||
enum class VirtualAccountEntryPoint {
|
||||
BANNER,
|
||||
DETAILS,
|
||||
DEEPLINK,
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.domain.virtualaccount.usecase
|
||||
|
||||
import com.tangem.domain.models.pay.TangemPayEligibilityType
|
||||
import com.tangem.domain.models.pay.isVirtualAccountType
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEligibility
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEntryPoint
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
class GetVirtualAccountEligibilityUseCase(
|
||||
private val getVirtualAccountSuitableWalletsUseCase: GetVirtualAccountSuitableWalletsUseCase,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val deviceSecurityInfoProvider: DeviceSecurityInfoProvider,
|
||||
) {
|
||||
suspend operator fun invoke(entryPoint: VirtualAccountEntryPoint?): VirtualAccountEligibility {
|
||||
if (deviceSecurityInfoProvider.isSecurityExposed()) {
|
||||
return VirtualAccountEligibility.NotAvailable
|
||||
}
|
||||
|
||||
val suitableWallets = getVirtualAccountSuitableWalletsUseCase()
|
||||
if (suitableWallets.isEmpty()) {
|
||||
return VirtualAccountEligibility.NotAvailable
|
||||
}
|
||||
|
||||
val isEligible = checkEligibility(entryPoint)
|
||||
if (isEligible) {
|
||||
return VirtualAccountEligibility.Available(suitableWallets)
|
||||
}
|
||||
|
||||
val eligibleWallets = coroutineScope {
|
||||
suitableWallets
|
||||
.map { wallet ->
|
||||
async {
|
||||
val isExistingCustomer = onboardingRepository.hasTangemPayInWallet(wallet.walletId).getOrNull()
|
||||
wallet.takeIf { isExistingCustomer == true }
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
return if (eligibleWallets.isEmpty()) {
|
||||
VirtualAccountEligibility.NotAvailable
|
||||
} else {
|
||||
VirtualAccountEligibility.Available(eligibleWallets)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkEligibility(entryPoint: VirtualAccountEntryPoint?): Boolean {
|
||||
val eligibility = onboardingRepository.getCustomerEligibility().ifEmpty {
|
||||
onboardingRepository.checkCustomerEligibility()
|
||||
}
|
||||
return if (entryPoint == null) {
|
||||
eligibility.any { it.isVirtualAccountType }
|
||||
} else {
|
||||
eligibility.contains(entryPoint.toEligibilityType())
|
||||
}
|
||||
}
|
||||
|
||||
private fun VirtualAccountEntryPoint.toEligibilityType(): TangemPayEligibilityType = when (this) {
|
||||
VirtualAccountEntryPoint.BANNER -> TangemPayEligibilityType.BANNER_VIRTUAL_ACCOUNT
|
||||
VirtualAccountEntryPoint.DETAILS -> TangemPayEligibilityType.DETAILS_VIRTUAL_ACCOUNT
|
||||
VirtualAccountEntryPoint.DEEPLINK -> TangemPayEligibilityType.DEEPLINK_VIRTUAL_ACCOUNT
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.virtualaccount.usecase
|
||||
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.models.wallet.isTangemPayCompatible
|
||||
|
||||
class GetVirtualAccountSuitableWalletsUseCase(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
) {
|
||||
operator fun invoke(): List<UserWallet> {
|
||||
return userWalletsListRepository.userWallets.value
|
||||
.orEmpty()
|
||||
.filter { it.isMultiCurrency && !it.isLocked && it.isTangemPayCompatible }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.domain.virtualaccount.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.pay.TangemPayEligibilityType
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEligibility
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEntryPoint
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class GetVirtualAccountEligibilityUseCaseTest {
|
||||
|
||||
private val getVirtualAccountSuitableWalletsUseCase: GetVirtualAccountSuitableWalletsUseCase = mockk()
|
||||
private val onboardingRepository: OnboardingRepository = mockk()
|
||||
private val deviceSecurityInfoProvider: DeviceSecurityInfoProvider = mockk()
|
||||
|
||||
private val useCase = GetVirtualAccountEligibilityUseCase(
|
||||
getVirtualAccountSuitableWalletsUseCase = getVirtualAccountSuitableWalletsUseCase,
|
||||
onboardingRepository = onboardingRepository,
|
||||
deviceSecurityInfoProvider = deviceSecurityInfoProvider,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(getVirtualAccountSuitableWalletsUseCase, onboardingRepository, deviceSecurityInfoProvider)
|
||||
every { deviceSecurityInfoProvider.isRooted } returns false
|
||||
every { deviceSecurityInfoProvider.isBootloaderUnlocked } returns false
|
||||
every { deviceSecurityInfoProvider.isXposed } returns false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN device is rooted WHEN invoke THEN returns NotAvailable`() = runTest {
|
||||
// GIVEN
|
||||
every { deviceSecurityInfoProvider.isRooted } returns true
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.NotAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no suitable wallets WHEN invoke THEN returns NotAvailable`() = runTest {
|
||||
// GIVEN
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns emptyList()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.NotAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN entry point eligibility passes WHEN invoke THEN returns Available with all suitable wallets`() = runTest {
|
||||
// GIVEN
|
||||
val wallets = listOf(mockWallet(), mockWallet())
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns wallets
|
||||
coEvery {
|
||||
onboardingRepository.getCustomerEligibility()
|
||||
} returns listOf(TangemPayEligibilityType.BANNER_VIRTUAL_ACCOUNT)
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.Available(wallets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN null entry point AND any VA eligibility present WHEN invoke THEN returns Available`() = runTest {
|
||||
// GIVEN
|
||||
val wallets = listOf(mockWallet())
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns wallets
|
||||
coEvery {
|
||||
onboardingRepository.getCustomerEligibility()
|
||||
} returns listOf(TangemPayEligibilityType.DEEPLINK_VIRTUAL_ACCOUNT)
|
||||
|
||||
// WHEN
|
||||
val result = useCase(entryPoint = null)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.Available(wallets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cached eligibility empty WHEN invoke THEN falls back to fetched eligibility`() = runTest {
|
||||
// GIVEN
|
||||
val wallets = listOf(mockWallet())
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns wallets
|
||||
coEvery { onboardingRepository.getCustomerEligibility() } returns emptyList()
|
||||
coEvery {
|
||||
onboardingRepository.checkCustomerEligibility()
|
||||
} returns listOf(TangemPayEligibilityType.BANNER_VIRTUAL_ACCOUNT)
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.Available(wallets))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN eligibility fails AND wallet is existing customer WHEN invoke THEN returns Available with wallet`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val wallet = mockWallet()
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns listOf(wallet)
|
||||
coEvery { onboardingRepository.getCustomerEligibility() } returns listOf(TangemPayEligibilityType.BANNER)
|
||||
coEvery { onboardingRepository.hasTangemPayInWallet(wallet.walletId) } returns true.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.Available(listOf(wallet)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN eligibility fails AND wallet is not a customer WHEN invoke THEN returns NotAvailable`() = runTest {
|
||||
// GIVEN
|
||||
val wallet = mockWallet()
|
||||
every { getVirtualAccountSuitableWalletsUseCase() } returns listOf(wallet)
|
||||
coEvery { onboardingRepository.getCustomerEligibility() } returns listOf(TangemPayEligibilityType.BANNER)
|
||||
coEvery {
|
||||
onboardingRepository.hasTangemPayInWallet(wallet.walletId)
|
||||
} returns VisaApiError.NotPaeraCustomer.left()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.NotAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN eligibility fails AND only some wallets are customers WHEN invoke THEN returns Available with customers`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val customerWallet = mockWallet()
|
||||
val nonCustomerWallet = mockWallet()
|
||||
every {
|
||||
getVirtualAccountSuitableWalletsUseCase()
|
||||
} returns listOf(customerWallet, nonCustomerWallet)
|
||||
coEvery { onboardingRepository.getCustomerEligibility() } returns listOf(TangemPayEligibilityType.BANNER)
|
||||
coEvery { onboardingRepository.hasTangemPayInWallet(customerWallet.walletId) } returns true.right()
|
||||
coEvery { onboardingRepository.hasTangemPayInWallet(nonCustomerWallet.walletId) } returns false.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(VirtualAccountEntryPoint.BANNER)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(VirtualAccountEligibility.Available(listOf(customerWallet)))
|
||||
}
|
||||
|
||||
private fun mockWallet(): UserWallet {
|
||||
val id = mockk<UserWalletId>()
|
||||
return mockk { every { walletId } returns id }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.domain.virtualaccount.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.domain.card.configs.GenericCardConfig
|
||||
import com.tangem.domain.card.configs.Wallet2CardConfig
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class GetVirtualAccountSuitableWalletsUseCaseTest {
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
|
||||
private val useCase = GetVirtualAccountSuitableWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
|
||||
@Test
|
||||
fun `GIVEN compatible, single-currency and outdated wallets WHEN invoke THEN returns only the compatible one`() {
|
||||
// GIVEN
|
||||
val compatible = MockUserWalletFactory.create(
|
||||
MockScanResponseFactory.create(cardConfig = Wallet2CardConfig, derivedKeys = emptyMap()),
|
||||
)
|
||||
val singleCurrency = MockUserWalletFactory.createSingleWalletWithToken()
|
||||
val outdatedFirmware = MockUserWalletFactory.create(
|
||||
MockScanResponseFactory.create(cardConfig = GenericCardConfig(maxWalletCount = 2), derivedKeys = emptyMap()),
|
||||
)
|
||||
every { userWalletsListRepository.userWallets } returns
|
||||
MutableStateFlow(listOf(compatible, singleCurrency, outdatedFirmware))
|
||||
|
||||
// WHEN
|
||||
val result = useCase()
|
||||
|
||||
// THEN
|
||||
assertThat(result).containsExactly(compatible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no wallets WHEN invoke THEN returns empty list`() {
|
||||
// GIVEN
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(null)
|
||||
|
||||
// WHEN
|
||||
val result = useCase()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ dependencies {
|
|||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.virtualAccount)
|
||||
|
||||
/* SDK */
|
||||
// TODO: For TangemError model, should be removed after card domain scanning refactoring
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import com.tangem.domain.pay.TangemPayEligibilityManager
|
|||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEligibility
|
||||
import com.tangem.domain.virtualaccount.model.VirtualAccountEntryPoint
|
||||
import com.tangem.domain.virtualaccount.usecase.GetVirtualAccountEligibilityUseCase
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
|
|
@ -67,6 +70,7 @@ internal class DetailsModel @Inject constructor(
|
|||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
|
||||
private val getVirtualAccountEligibilityUseCase: GetVirtualAccountEligibilityUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val params: DetailsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -99,6 +103,7 @@ internal class DetailsModel @Inject constructor(
|
|||
)
|
||||
|
||||
addTangemPayItemIfEligible()
|
||||
addVirtualAccountItemIfEligible()
|
||||
|
||||
state = MutableStateFlow(
|
||||
value = DetailsUM(
|
||||
|
|
@ -285,5 +290,32 @@ internal class DetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addVirtualAccountItemIfEligible() {
|
||||
modelScope.launch {
|
||||
val eligibility = getVirtualAccountEligibilityUseCase(VirtualAccountEntryPoint.DETAILS)
|
||||
if (eligibility is VirtualAccountEligibility.Available) {
|
||||
items.update { items ->
|
||||
itemsBuilder.addVirtualAccountItem(
|
||||
items = items,
|
||||
onClick = ::onVirtualAccountItemClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onVirtualAccountItemClicked() {
|
||||
modelScope.launch {
|
||||
when (val eligibility = getVirtualAccountEligibilityUseCase(VirtualAccountEntryPoint.DETAILS)) {
|
||||
is VirtualAccountEligibility.Available -> router.push(
|
||||
AppRoute.VirtualAccountOnboarding(
|
||||
AppRoute.VirtualAccountOnboarding.Mode.FromDetailsScreen(eligibility.wallets.first().walletId),
|
||||
),
|
||||
)
|
||||
VirtualAccountEligibility.NotAvailable -> items.update { itemsBuilder.removeVirtualAccountItem(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppVersion(): String = "${appInfoProvider.appVersion} (${appInfoProvider.appVersionCode})"
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
import javax.inject.Inject
|
||||
|
||||
private const val TANGEM_PAY_ITEM_ID = "get_tangem_pay"
|
||||
private const val VIRTUAL_ACCOUNT_ITEM_ID = "get_virtual_account"
|
||||
|
||||
@ModelScoped
|
||||
internal class ItemsBuilder @Inject constructor(
|
||||
|
|
@ -81,6 +82,30 @@ internal class ItemsBuilder @Inject constructor(
|
|||
}.toImmutableList()
|
||||
}
|
||||
|
||||
fun addVirtualAccountItem(items: ImmutableList<DetailsItemUM>, onClick: () -> Unit): ImmutableList<DetailsItemUM> {
|
||||
return items.map { block ->
|
||||
if (block.id == "shop" && block is DetailsItemUM.Basic) {
|
||||
val newItems = block
|
||||
.items
|
||||
.toMutableList()
|
||||
.apply { add(getVirtualAccountItem(onClick = onClick)) }
|
||||
block.copy(items = newItems.toImmutableList())
|
||||
} else {
|
||||
block
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
fun removeVirtualAccountItem(items: ImmutableList<DetailsItemUM>): ImmutableList<DetailsItemUM> {
|
||||
return items.map { block ->
|
||||
if (block is DetailsItemUM.Basic && block.items.any { it.id == VIRTUAL_ACCOUNT_ITEM_ID }) {
|
||||
block.copy(items = block.items.filter { it.id != VIRTUAL_ACCOUNT_ITEM_ID }.toImmutableList())
|
||||
} else {
|
||||
block
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean, userWalletId: UserWalletId): DetailsItemUM? {
|
||||
return if (isWalletConnectAvailable) {
|
||||
DetailsItemUM.WalletConnect(
|
||||
|
|
@ -194,4 +219,13 @@ internal class ItemsBuilder @Inject constructor(
|
|||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
|
||||
private fun getVirtualAccountItem(onClick: () -> Unit): DetailsItemUM.Basic.Item = DetailsItemUM.Basic.Item(
|
||||
id = VIRTUAL_ACCOUNT_ITEM_ID,
|
||||
block = BlockUM(
|
||||
text = resourceReference(R.string.virtual_account_title),
|
||||
iconRes = R.drawable.ic_tangem_pay_24,
|
||||
onClick = onClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue