diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt index effeecade9..53e728d17c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt @@ -48,6 +48,7 @@ class MoshiModule { ) .add( NamePolymorphicAdapterFactory.of(PaymentAccountStatusValueDM::class.java) + .withSubtype(PaymentAccountStatusValueDM.Empty::class.java, "empty") .withSubtype(PaymentAccountStatusValueDM.NotCreated::class.java, "not_created") .withSubtype(PaymentAccountStatusValueDM.UnderReview::class.java, "kyc_status") .withSubtype(PaymentAccountStatusValueDM.IssuingCard::class.java, "issuing_card") 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 7b1ca88f44..fb91718338 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 @@ -16,6 +16,11 @@ import java.math.BigDecimal @JsonClass(generateAdapter = true, generator = PolymorphicAdapterType.NAME_POLYMORPHIC_ADAPTER) sealed interface PaymentAccountStatusValueDM { + @NameLabel("empty") + data class Empty( + @Json(name = "empty") val marker: Boolean = true, + ) : PaymentAccountStatusValueDM + @NameLabel("not_created") data class NotCreated( @Json(name = "not_created") val marker: Boolean = 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 a42852436a..94006aaf9c 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 @@ -51,6 +51,7 @@ internal object PaymentAccountStatusValueDMConverter : is PaymentAccountStatusValue.Error.CardIssueFailed -> PaymentAccountStatusValueDM.CardIssueFailed( customerId = value.customerId, ) + is PaymentAccountStatusValue.Empty -> PaymentAccountStatusValueDM.Empty() // Transient statuses are not persisted is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Error.ExposedDevice, @@ -62,6 +63,7 @@ internal object PaymentAccountStatusValueDMConverter : override fun convertBack(value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue { return when (value) { + is PaymentAccountStatusValueDM.Empty -> PaymentAccountStatusValue.Empty is PaymentAccountStatusValueDM.NotCreated -> PaymentAccountStatusValue.NotCreated is PaymentAccountStatusValueDM.CardIssueFailed -> PaymentAccountStatusValue.Error.CardIssueFailed( customerId = value.customerId, 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 fa38de8fbd..30b61c32ef 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 @@ -8,10 +8,13 @@ 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.kyc.KycStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.pay.model.CustomerInfo import com.tangem.domain.pay.model.OrderData import com.tangem.domain.pay.model.OrderStatus +import com.tangem.domain.pay.model.TangemPayEntryPoint import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.visa.error.VisaApiError @@ -28,12 +31,14 @@ import kotlin.time.Duration.Companion.minutes private const val TAG = "PaymentAccountStatusFetcher" +@Suppress("LongParameterList") internal class DefaultPaymentAccountStatusFetcher @Inject constructor( private val paymentAccountStatusesStore: PaymentAccountStatusesStore, private val onboardingRepository: OnboardingRepository, private val customerOrderRepository: CustomerOrderRepository, private val deviceSecurity: DeviceSecurityInfoProvider, private val dispatchers: CoroutineDispatcherProvider, + private val eligibilityManager: TangemPayEligibilityManager, ) : PaymentAccountStatusFetcher { private val logger = TangemLogger.withTag(TAG) @@ -43,6 +48,16 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( val account = Account.Payment(userWalletId = params.userWalletId) logger.i("fetch: ${params.userWalletId.stringValue}") + if (onboardingRepository.isTangemPayDeactivated(params.userWalletId)) { + return@catchOn paymentAccountStatusesStore.store( + userWalletId = params.userWalletId, + status = AccountStatus.Payment( + account = account, + value = PaymentAccountStatusValue.Empty, + ), + ) + } + if (deviceSecurity.isSecurityExposed()) { logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}") logger.i("fetch security info: xposed: ${deviceSecurity.isXposed}") @@ -57,22 +72,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( ) } - if (onboardingRepository.isTangemPayDeactivated(params.userWalletId)) { - return@catchOn paymentAccountStatusesStore.store( - userWalletId = params.userWalletId, - status = AccountStatus.Payment( - account = account, - value = PaymentAccountStatusValue.NotCreated, - ), - ) - } - val status = onboardingRepository.hasTangemPayInWallet(userWalletId = params.userWalletId) .fold( ifLeft = { error -> logger.e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}") when (error) { - is VisaApiError.NotPaeraCustomer -> PaymentAccountStatusValue.NotCreated + is VisaApiError.NotPaeraCustomer -> constructNotCreatedOrEmptyStatus(params.userWalletId) else -> PaymentAccountStatusValue.Error.Unavailable } }, @@ -100,7 +105,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( return if (hasTangemPay) { fetchTangemPayAccountStatus(account) } else { - PaymentAccountStatusValue.NotCreated + constructNotCreatedOrEmptyStatus(account.userWalletId) } } @@ -133,7 +138,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( return onboardingRepository.getCustomerInfo(account.userWalletId).fold( ifLeft = { error -> logger.e("proceedWithoutOrder ${account.userWalletId} error: $error") - error.mapToPaymentAccountStatus() + error.mapToPaymentAccountStatus(account.userWalletId) }, ifRight = { customerInfo -> logger.i("proceedWithoutOrder data customerInfo ${account.userWalletId}") @@ -153,7 +158,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( val customerInfo = onboardingRepository.getCustomerInfo(account.userWalletId).fold( ifLeft = { error -> logger.e("proceedWithOrderId KYC check ${account.userWalletId} error: $error") - return error.mapToPaymentAccountStatus() + return error.mapToPaymentAccountStatus(account.userWalletId) }, ifRight = { it }, ) @@ -172,7 +177,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( return customerOrderRepository.getOrderData(userWalletId = account.userWalletId, orderId = orderId).fold( ifLeft = { error -> logger.e("proceedWithOrderId ${account.userWalletId} orderId: $orderId error: $error") - error.mapToPaymentAccountStatus() + error.mapToPaymentAccountStatus(account.userWalletId) }, ifRight = { orderData -> logger.i("proceedWithOrderId ${account.userWalletId}: $orderId status: ${orderData.status}") @@ -241,7 +246,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( onboardingRepository.clearOrderId(account.userWalletId) return onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) .fold( - ifLeft = { it.mapToPaymentAccountStatus() }, + ifLeft = { it.mapToPaymentAccountStatus(account.userWalletId) }, ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() }, ) } @@ -297,12 +302,22 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( } } - private fun VisaApiError.mapToPaymentAccountStatus(): PaymentAccountStatusValue { + private suspend fun VisaApiError.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue { return when (this) { is VisaApiError.RefreshTokenExpired -> PaymentAccountStatusValue.Error.NotSynced - is VisaApiError.NotPaeraCustomer -> PaymentAccountStatusValue.NotCreated - is VisaApiError.Deactivated -> PaymentAccountStatusValue.NotCreated + is VisaApiError.NotPaeraCustomer -> constructNotCreatedOrEmptyStatus(userWalletId) + is VisaApiError.Deactivated -> constructNotCreatedOrEmptyStatus(userWalletId) else -> PaymentAccountStatusValue.Error.Unavailable } } + + private suspend fun constructNotCreatedOrEmptyStatus(userWalletId: UserWalletId): PaymentAccountStatusValue { + val entryPoint = TangemPayEntryPoint.BANNER + val shouldShowBanner = !eligibilityManager.isPaeraCustomerForAnyWallet(entryPoint) && + eligibilityManager.getEligibleWallets(shouldExcludePaeraCustomers = false, entryPoint = entryPoint) + .any { it.walletId == userWalletId } && + !onboardingRepository.getHideMainOnboardingBanner(userWalletId) + + return if (shouldShowBanner) PaymentAccountStatusValue.NotCreated else PaymentAccountStatusValue.Empty + } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index 8253961561..e1f741be61 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -5,6 +5,7 @@ import arrow.core.flatMap import arrow.core.left import arrow.core.right import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.data.pay.store.PaymentAccountStatusesStore import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest import com.tangem.datasource.api.pay.models.request.OrderRequest @@ -15,6 +16,8 @@ import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.TangemPayEligibilityType +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.kyc.KycStatus import com.tangem.domain.models.wallet.UserWallet @@ -44,6 +47,7 @@ internal class DefaultOnboardingRepository @Inject constructor( private val authDataSource: TangemPayAuthDataSource, private val cardFrozenStateStore: TangemPayCardFrozenStateStore, private val userWalletsListRepository: UserWalletsListRepository, + private val paymentAccountStatusStore: PaymentAccountStatusesStore, ) : OnboardingRepository { // Save data for a session @@ -265,6 +269,13 @@ internal class DefaultOnboardingRepository @Inject constructor( override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) { tangemPayStorage.storeHideOnboardingBanner(userWalletId, hide = true) + paymentAccountStatusStore.store( + userWalletId = userWalletId, + status = AccountStatus.Payment( + account = Account.Payment(userWalletId), + value = PaymentAccountStatusValue.Empty, + ), + ) } override suspend fun disableTangemPay(userWalletId: UserWalletId): Either { diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCase.kt index 1ae6967739..ae529e245c 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCase.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.map * Accounts mode is considered enabled if any [com.tangem.domain.account.models.AccountStatusList] produced for the * user's wallets has more than one [AccountStatus.CryptoPortfolio], or has a [AccountStatus.Payment] with any + * [PaymentAccountStatusValue.Empty]. * * @property multiAccountStatusListSupplier supplier that provides a list of * [com.tangem.domain.account.models.AccountStatusList]s for all user wallets @@ -45,6 +46,17 @@ class IsAccountsModeEnabledUseCase( } private fun PaymentAccountStatusValue.isActivePayment(): Boolean { - return this !is PaymentAccountStatusValue.NotCreated + return when (this) { + is PaymentAccountStatusValue.Empty, + is PaymentAccountStatusValue.NotCreated, + -> false + is PaymentAccountStatusValue.Error, + is PaymentAccountStatusValue.IssuingCard, + is PaymentAccountStatusValue.Loaded, + is PaymentAccountStatusValue.Loading, + is PaymentAccountStatusValue.Locked, + is PaymentAccountStatusValue.UnderReview, + -> true + } } } \ No newline at end of file diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCaseTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCaseTest.kt index 49a4a0bd50..02aa942198 100644 --- a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCaseTest.kt +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/IsAccountsModeEnabledUseCaseTest.kt @@ -102,6 +102,18 @@ class IsAccountsModeEnabledUseCaseTest { Truth.assertThat(actual).isFalse() } + @Test + fun `returns false when payment account is Empty`() = runTest { + val statusList = createAccountStatusList( + statuses = listOf(mockCryptoPortfolio(), mockPayment(PaymentAccountStatusValue.Empty)), + ) + every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList)) + + val actual = useCase.invoke().first() + + Truth.assertThat(actual).isFalse() + } + @Test fun `returns true when payment account is UnderReview`() = runTest { val statusList = createAccountStatusList( @@ -234,6 +246,18 @@ class IsAccountsModeEnabledUseCaseTest { Truth.assertThat(actual).isFalse() } + @Test + fun `returns false when payment account is Empty`() = runTest { + val statusList = createAccountStatusList( + statuses = listOf(mockCryptoPortfolio(), mockPayment(PaymentAccountStatusValue.Empty)), + ) + coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList) + + val actual = useCase.invokeSync() + + Truth.assertThat(actual).isFalse() + } + @Test fun `returns true when payment account is UnderReview`() = runTest { val statusList = createAccountStatusList( diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt index fc44ccc105..542733a235 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/PaymentAccountStatusValue.kt @@ -20,6 +20,7 @@ sealed class PaymentAccountStatusValue { get() = when (this) { is Error, is IssuingCard, + is Empty, is NotCreated, is UnderReview, -> TotalFiatBalance.Loaded(amount = SerializedBigDecimal.ZERO, source = source) @@ -40,12 +41,19 @@ sealed class PaymentAccountStatusValue { is Locked -> copy(source = source) is UnderReview -> copy(source = source) is Loading, + is Empty, is NotCreated, is Error, -> this } } + /** Represents an empty payment account status when no specific state is available. */ + @Serializable + data object Empty : PaymentAccountStatusValue() { + override val source: StatusSource = StatusSource.ACTUAL + } + /** Represents the Loading state of a payment account, typically while fetching its details. */ @Serializable data object Loading : PaymentAccountStatusValue() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index f0d03bf32a..32e10b3532 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -29,13 +29,13 @@ import com.tangem.domain.promo.models.PromoId import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.tokensync.model.TokenSyncProgress import com.tangem.domain.tokensync.usecase.ObserveTokenSyncUseCase -import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification +import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.hot.sdk.model.HotWalletId import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.extensions.addIf @@ -43,11 +43,7 @@ import com.tangem.utils.extensions.isPositive import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.* import javax.inject.Inject @Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]") @@ -214,6 +210,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Locked, is PaymentAccountStatusValue.UnderReview, + is PaymentAccountStatusValue.Empty, -> null } notification?.let(::add) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt index 8ce6b1a9c0..c119fc6251 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt @@ -239,6 +239,7 @@ internal class GetWalletNotificationsFactory @Inject constructor( is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Locked, is PaymentAccountStatusValue.UnderReview, + is PaymentAccountStatusValue.Empty, -> null } notification?.let(::add) 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 1ce25e26ac..fef7fb08bb 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 @@ -53,6 +53,7 @@ internal class TangemPayMainBlockConverter( } }, ) + is PaymentAccountStatusValue.Empty -> TangemPayMainUM.Empty is PaymentAccountStatusValue.NotCreated -> TangemPayMainUM.Empty is PaymentAccountStatusValue.Loading -> TangemPayMainUM.Loading is PaymentAccountStatusValue.Locked -> TangemPayMainUM.Content(