From 41fb5d5bccba8be882af0b2a71611d3d5a3fa0c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Feb 2026 21:03:08 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 3 + .../cardsettings/model/CardSettingsModel.kt | 2 +- .../com/tangem/common/routing/AppRoute.kt | 4 +- .../configs/feature_toggles_config.json | 4 + .../api/pay/models/response/OrderResponse.kt | 17 +- .../pay/DefaultTangemPayEligibilityManager.kt | 2 +- .../tangem/data/pay/di/TangemPayDataModule.kt | 27 +++ .../DefaultPaymentAccountStatusFetcher.kt | 178 ++++++++++++++++++ .../DefaultPaymentAccountStatusProducer.kt | 37 ++++ .../DefaultCustomerOrderRepository.kt | 11 +- .../repository/DefaultOnboardingRepository.kt | 20 +- .../DefaultTangemPayCardDetailsRepository.kt | 8 +- .../DefaultTangemPayTxHistoryRepository.kt | 26 +-- .../pay/store/PaymentAccountStatusesStore.kt | 24 +++ .../account/supplier/SingleAccountSupplier.kt | 4 + domain/kyc/models/.gitignore | 1 + domain/kyc/models/build.gradle.kts | 9 + .../com/tangem/domain/models/kyc/KycStatus.kt | 33 ++++ domain/tokens/build.gradle.kts | 1 + .../domain/tokens/wallet/FetchingSource.kt | 1 + .../tokens/wallet/WalletBalanceFetcher.kt | 32 +++- .../implementor/MultiWalletBalanceFetcher.kt | 1 + .../tokens/wallet/WalletBalanceFetcherTest.kt | 101 ++++++++-- .../MultiWalletBalanceFetcherTest.kt | 7 +- domain/visa/build.gradle.kts | 2 - .../tangem/domain/pay/PaymentAccountStatus.kt | 66 +++++++ .../domain/pay/TangemPayDetailsConfig.kt | 1 - .../pay/flow/PaymentAccountStatusFetcher.kt | 8 + .../pay/flow/PaymentAccountStatusProducer.kt | 11 ++ .../pay/flow/PaymentAccountStatusSupplier.kt | 10 + .../tangem/domain/pay/model/CustomerInfo.kt | 18 +- .../tangem/domain/pay/model/OrderStatus.kt | 12 +- .../pay/repository/OnboardingRepository.kt | 2 +- .../TangemPayMainScreenCustomerInfoUseCase.kt | 7 +- .../model/TangemPayTxHistoryListConfig.kt | 2 +- .../account/AccountCreateEditComponent.kt | 2 +- .../account/AccountDetailsComponent.kt | 2 +- .../createedit/AccountCreateEditModel.kt | 7 +- .../account/details/AccountDetailsModel.kt | 35 ++-- .../onramp/hottokens/model/HotCryptoModel.kt | 5 +- .../tangempay/TangemPayFeatureToggles.kt | 5 + .../DefaultTangemPayFeatureToggles.kt | 10 + .../components/TangemPayDetailsComponent.kt | 1 - .../DefaultTangemPayTxHistoryComponent.kt | 1 - .../tangempay/di/TangemPayDetailsModule.kt | 21 +++ .../model/TangemPayTxHistoryModel.kt | 3 +- .../utils/TangemPayTxHistoryListManager.kt | 14 +- .../model/TangemPayOnboardingModel.kt | 2 +- features/tokendetails/impl/build.gradle.kts | 1 + .../DefaultTokenDetailsDeepLinkHandler.kt | 7 +- .../utils/AccountItemsDelegate.kt | 2 +- .../WalletTangemPayAnalyticsEventSender.kt | 4 +- .../wallet/domain/WalletContentFetcher.kt | 12 +- .../TangemPayUpdateInfoStateTransformer.kt | 43 ++--- .../visa/TangemPayMainScreenBlock.kt | 13 ++ 55 files changed, 709 insertions(+), 173 deletions(-) create mode 100644 data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt create mode 100644 data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt create mode 100644 data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt create mode 100644 domain/kyc/models/.gitignore create mode 100644 domain/kyc/models/build.gradle.kts create mode 100644 domain/models/src/main/kotlin/com/tangem/domain/models/kyc/KycStatus.kt create mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/pay/PaymentAccountStatus.kt create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusFetcher.kt create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusProducer.kt create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusSupplier.kt create mode 100644 features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayDetailsModule.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 5bb200ad04..ef95af2a32 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -7,6 +7,7 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.networks.repository.NetworksRepository import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusSupplier +import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.promo.PromoRepository import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher @@ -378,6 +379,7 @@ internal object TokensDomainModule { multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiStakingBalanceFetcher: MultiStakingBalanceFetcher, + paymentAccountStatusFetcher: PaymentAccountStatusFetcher, stakingIdFactory: StakingIdFactory, dispatchers: CoroutineDispatcherProvider, ): WalletBalanceFetcher { @@ -388,6 +390,7 @@ internal object TokensDomainModule { multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiStakingBalanceFetcher = multiStakingBalanceFetcher, + paymentAccountStatusFetcher = paymentAccountStatusFetcher, stakingIdFactory = stakingIdFactory, dispatchers = dispatchers, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 797ef6c056..2b26905caf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -221,7 +221,7 @@ internal class CardSettingsModel @Inject constructor( val card = scanResponse.card modelScope.launch { - val hasTangemPay = onboardingRepository.checkCustomerWallet(userWalletId).getOrNull() == true + val hasTangemPay = onboardingRepository.hasTangemPayInWallet(userWalletId).getOrNull() == true store.dispatchNavigationAction { push( route = AppRoute.ResetToFactory( diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index a514a89100..a926dcb3ec 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -408,12 +408,12 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class EditAccount( - val account: Account, + val account: Account.CryptoPortfolio, ) : AppRoute(path = "/edit_account/${account.accountId.value}") @Serializable data class AccountDetails( - val account: Account, + val account: Account.CryptoPortfolio, ) : AppRoute(path = "/account_details/${account.accountId.value}") @Serializable diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 23b909ebad..97d0aecaab 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -59,5 +59,9 @@ { "name": "GASLESS_APPROVAL_ENABLED", "version": "undefined" + }, + { + "name": "TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED", + "version": "undefined" } ] diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt index 48cc8fc4fa..e8ce00ce4f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt @@ -12,7 +12,7 @@ data class OrderResponse( @Json(name = "id") val id: String, @Json(name = "customer_id") val customerId: String?, @Json(name = "type") val type: String?, - @Json(name = "status") val status: String, + @Json(name = "status") val status: Status, @Json(name = "step") val step: String?, @Json(name = "data") val data: Data, @Json(name = "step_change_code") val stepChangeCode: Int?, @@ -29,5 +29,20 @@ data class OrderResponse( @Json(name = "payment_account_id") val paymentAccountId: String?, @Json(name = "transaction_hash") val transactionHash: String?, ) + + @JsonClass(generateAdapter = false) + enum class Status { + @Json(name = "NEW") + NEW, + + @Json(name = "PROCESSING") + PROCESSING, + + @Json(name = "COMPLETED") + COMPLETED, + + @Json(name = "CANCELED") + CANCELED, + } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt index 2d20b15a27..c3eb76035e 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt @@ -104,7 +104,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( map { wallet -> async { val isCustomer = onboardingRepository - .checkCustomerWallet(wallet.walletId) + .hasTangemPayInWallet(wallet.walletId) .getOrNull() == true wallet to isCustomer } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 5868e4c0c6..d988e0088c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -2,12 +2,17 @@ package com.tangem.data.pay.di import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory import com.tangem.data.pay.DefaultTangemPayEligibilityManager +import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher +import com.tangem.data.pay.flow.DefaultPaymentAccountStatusProducer import com.tangem.data.pay.repository.* import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase import com.tangem.data.pay.usecase.DefaultGetTangemPayCustomerIdUseCase import com.tangem.data.pay.usecase.DefaultTangemPayWithdrawUseCase import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory import com.tangem.domain.pay.TangemPayEligibilityManager +import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher +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.ProduceTangemPayInitialDataUseCase import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase @@ -75,7 +80,29 @@ internal interface TangemPayDataModule { @Singleton fun bindTangemPayEligibilityManager(impl: DefaultTangemPayEligibilityManager): TangemPayEligibilityManager + @Binds + @Singleton + fun bindPaymentAccountStatusProducerFactory( + impl: DefaultPaymentAccountStatusProducer.Factory, + ): PaymentAccountStatusProducer.Factory + + @Binds + @Singleton + fun bindPaymentAccountStatusFetcher(impl: DefaultPaymentAccountStatusFetcher): PaymentAccountStatusFetcher + companion object { + + @Provides + @Singleton + fun providePaymentAccountStatusSupplier( + factory: PaymentAccountStatusProducer.Factory, + ): PaymentAccountStatusSupplier { + return object : PaymentAccountStatusSupplier( + factory = factory, + keyCreator = { "payment_account_status_${it.userWalletId.stringValue}" }, + ) {} + } + @Provides @Singleton fun provideTangemPayMainScreenCustomerInfoUseCase( 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 new file mode 100644 index 0000000000..cdfbf0d9b4 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -0,0 +1,178 @@ +package com.tangem.data.pay.flow + +import arrow.core.Either +import com.tangem.data.pay.store.PaymentAccountStatusesStore +import com.tangem.domain.core.utils.eitherOn +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.kyc.KycStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.PaymentAccountStatus +import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher +import com.tangem.domain.pay.model.CustomerInfo +import com.tangem.domain.pay.model.OrderStatus +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.coroutines.CoroutineDispatcherProvider +import timber.log.Timber +import javax.inject.Inject + +private const val TAG = "PaymentAccountStatusFetcher" + +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, +) : PaymentAccountStatusFetcher { + + override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either = + eitherOn(dispatchers.default) { + Timber.tag(TAG).i("fetch: ${params.userWalletId.stringValue}") + + if (deviceSecurity.isSecurityExposed()) { + Timber.tag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}") + Timber.tag(TAG).i("fetch security info: xposed: ${deviceSecurity.isXposed}") + Timber.tag(TAG).i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}") + + return@eitherOn paymentAccountStatusesStore.store( + userWalletId = params.userWalletId, + status = PaymentAccountStatus.Error.ExposedDevice, + ) + } + + val status = onboardingRepository.hasTangemPayInWallet(userWalletId = params.userWalletId) + .fold( + ifLeft = { error -> + Timber.tag(TAG).e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}") + when (error) { + is VisaApiError.NotPaeraCustomer -> PaymentAccountStatus.NotCreated + else -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL) + } + }, + ifRight = { hasTangemPay -> + proceedHasTangemPayResult(userWalletId = params.userWalletId, hasTangemPay = hasTangemPay) + }, + ) + Timber.tag(TAG).i("invoke status ${params.userWalletId}: $status") + paymentAccountStatusesStore.store(userWalletId = params.userWalletId, status = status) + } + + private suspend fun proceedHasTangemPayResult( + userWalletId: UserWalletId, + hasTangemPay: Boolean, + ): PaymentAccountStatus { + Timber.tag(TAG).i("proceedHasTangemPayResult for $userWalletId hasTangemPay: $hasTangemPay") + return if (hasTangemPay) { + fetchTangemPayAccountStatus(userWalletId = userWalletId) + } else { + PaymentAccountStatus.NotCreated + } + } + + private suspend fun fetchTangemPayAccountStatus(userWalletId: UserWalletId): PaymentAccountStatus { + val prevResult = paymentAccountStatusesStore.getSyncOrNull(userWalletId) + if (prevResult == null || prevResult is PaymentAccountStatus.Error) { + paymentAccountStatusesStore.store(userWalletId = userWalletId, status = PaymentAccountStatus.Loading) + } + + return proceedWithOrderId(userWalletId = userWalletId) + } + + private suspend fun proceedWithOrderId(userWalletId: UserWalletId): PaymentAccountStatus { + return if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) { + PaymentAccountStatus.Error.NotSynced + } else { + val orderId = onboardingRepository.getOrderId(userWalletId) + if (orderId != null) { + proceedWithOrderId(userWalletId = userWalletId, orderId = orderId) + } else { + proceedWithoutOrder(userWalletId = userWalletId) + } + } + } + + private suspend fun proceedWithoutOrder(userWalletId: UserWalletId): PaymentAccountStatus { + return onboardingRepository.getCustomerInfo(userWalletId).fold( + ifLeft = { error -> + Timber.tag(TAG).e("proceedWithoutOrder $userWalletId error: $error") + error.mapToPaymentAccountStatus() + }, + ifRight = { customerInfo -> + Timber.tag(TAG).i("proceedWithoutOrder data customerInfo $userWalletId") + val status = customerInfo.mapToPaymentAccountStatus() + if (status is PaymentAccountStatus.IssuingCard && customerInfo.kycStatus == KycStatus.APPROVED) { + // If order id wasn't saved -> start order creation and get customer info + onboardingRepository.createOrder(userWalletId) + } + status + }, + ) + } + + private suspend fun proceedWithOrderId(userWalletId: UserWalletId, orderId: String): PaymentAccountStatus { + return customerOrderRepository.getOrderData(userWalletId, orderId = orderId).fold( + ifLeft = { error -> + Timber.tag(TAG).e("proceedWithOrderId $userWalletId orderId: $orderId error: $error") + error.mapToPaymentAccountStatus() + }, + ifRight = { orderData -> + Timber.tag(TAG).i("proceedWithOrderId $userWalletId: $orderId status: ${orderData.status}") + when (orderData.status) { + // Kyc is passed and user waits for order creation -> no need to get customer info + OrderStatus.NEW, + OrderStatus.PROCESSING, + -> PaymentAccountStatus.IssuingCard(source = StatusSource.ACTUAL) + + OrderStatus.CANCELED -> { + // If order was cancelled -> clear previous order from local storage and start order creation + onboardingRepository.clearOrderId(userWalletId) + onboardingRepository.createOrder(userWalletId) + PaymentAccountStatus.Error.CardIssueFailed + } + OrderStatus.COMPLETED -> { + // Order was completed -> clear order id and get customer info + onboardingRepository.clearOrderId(userWalletId) + onboardingRepository.getCustomerInfo(userWalletId = userWalletId) + .fold( + ifLeft = { it.mapToPaymentAccountStatus() }, + ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() }, + ) + } + OrderStatus.UNKNOWN -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL) + } + }, + ) + } + + private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatus { + val cardInfo = this.cardInfo + val productInstance = this.productInstance + return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) { + PaymentAccountStatus.UnderReview(source = StatusSource.ACTUAL, kycStatus = kycStatus) + } else if (cardInfo != null && productInstance != null) { + PaymentAccountStatus.Loaded( + source = StatusSource.ACTUAL, + cardId = productInstance.cardId, + lastFourDigits = cardInfo.lastFourDigits, + balance = cardInfo.balance, + currencyCode = cardInfo.currencyCode, + depositAddress = cardInfo.depositAddress, + isPinSet = cardInfo.isPinSet, + ) + } else { + PaymentAccountStatus.IssuingCard(source = StatusSource.ACTUAL) + } + } + + private fun VisaApiError.mapToPaymentAccountStatus(): PaymentAccountStatus { + return when (this) { + is VisaApiError.RefreshTokenExpired -> PaymentAccountStatus.Error.NotSynced + is VisaApiError.NotPaeraCustomer -> PaymentAccountStatus.NotCreated + else -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL) + } + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt new file mode 100644 index 0000000000..cb021c1ee8 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusProducer.kt @@ -0,0 +1,37 @@ +package com.tangem.data.pay.flow + +import arrow.core.Option +import arrow.core.some +import com.tangem.data.pay.store.PaymentAccountStatusesStore +import com.tangem.domain.core.flow.FlowProducerTools +import com.tangem.domain.models.StatusSource +import com.tangem.domain.pay.PaymentAccountStatus +import com.tangem.domain.pay.flow.PaymentAccountStatusProducer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEmpty + +internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor( + @Assisted private val params: PaymentAccountStatusProducer.Params, + override val flowProducerTools: FlowProducerTools, + private val paymentAccountStatusesStore: PaymentAccountStatusesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : PaymentAccountStatusProducer { + override val fallback: Option + get() = PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL).some() + + override fun produce(): Flow { + return paymentAccountStatusesStore.get(userWalletId = params.userWalletId) + .onEmpty { emit(value = PaymentAccountStatus.NotCreated) } + .flowOn(dispatchers.default) + } + + @AssistedFactory + interface Factory : PaymentAccountStatusProducer.Factory { + override fun create(params: PaymentAccountStatusProducer.Params): DefaultPaymentAccountStatusProducer + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt index f479794766..508627ca37 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt @@ -2,6 +2,7 @@ package com.tangem.data.pay.repository import arrow.core.Either import com.tangem.datasource.api.pay.TangemPayApi +import com.tangem.datasource.api.pay.models.response.OrderResponse import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.OrderData import com.tangem.domain.pay.model.OrderStatus @@ -19,11 +20,11 @@ internal class DefaultCustomerOrderRepository @Inject constructor( tangemPayApi.getOrder(authHeader = authHeader, orderId = orderId) }.map { response -> val status = when (response.result?.status) { - null -> OrderStatus.UNKNOWN - OrderStatus.NEW.apiName -> OrderStatus.NEW - OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING - OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED - else -> OrderStatus.CANCELED + null -> OrderStatus.PROCESSING + OrderResponse.Result.Status.NEW -> OrderStatus.NEW + OrderResponse.Result.Status.PROCESSING -> OrderStatus.PROCESSING + OrderResponse.Result.Status.COMPLETED -> OrderStatus.COMPLETED + OrderResponse.Result.Status.CANCELED -> OrderStatus.CANCELED } OrderData( status = status, 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 2fdcfabcb6..042922e80a 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 @@ -10,6 +10,7 @@ import com.tangem.datasource.api.pay.models.response.CustomerMeResponse 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.kyc.KycStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.datasource.TangemPayAuthDataSource @@ -27,9 +28,6 @@ import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject private const val VALID_STATUS = "valid" -private const val APPROVED_KYC_STATUS = "approved" -private const val IN_PROGRESS_KYC_STATUS = "in_progress" -private const val DECLINED_KYC_STATUS = "declined" private const val TAG = "TangemPay: OnboardingRepository" @Suppress("LongParameterList") @@ -145,7 +143,6 @@ internal class DefaultOnboardingRepository @Inject constructor( lastFourDigits = card.cardNumberEnd, balance = fiatBalance.availableBalance, currencyCode = fiatBalance.currency, - customerWalletAddress = paymentAccount.customerWalletAddress, depositAddress = response.depositAddress, isPinSet = response.card?.isPinSet == true, ) @@ -159,19 +156,19 @@ internal class DefaultOnboardingRepository @Inject constructor( } cardFrozenStateStore.store(key = instance.cardId, value = cardFrozenState) - ProductInstance(id = instance.id, cardId = instance.cardId, cardFrozenState = cardFrozenState) + ProductInstance(id = instance.id, cardId = instance.cardId) } return CustomerInfo( customerId = response?.id, productInstance = productInstance, - kycStatus = getKycStatus(status = response?.kyc?.status), + kycStatus = KycStatus.fromString(status = response?.kyc?.status), cardInfo = cardInfo, ).also { lastFetchedCustomerInfoMap[userWalletId] = it } } - override suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either { + override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either { val hasTangemPay = tangemPayStorage.checkCustomerWalletResult(userWalletId) if (hasTangemPay != null) { return Either.Right(hasTangemPay) @@ -228,13 +225,4 @@ internal class DefaultOnboardingRepository @Inject constructor( setHideMainOnboardingBanner(userWalletId) } } - - private fun getKycStatus(status: String?): CustomerInfo.KycStatus { - return when (status?.lowercase()) { - IN_PROGRESS_KYC_STATUS -> CustomerInfo.KycStatus.PENDING - DECLINED_KYC_STATUS -> CustomerInfo.KycStatus.REJECTED - APPROVED_KYC_STATUS -> CustomerInfo.KycStatus.APPROVED - else -> CustomerInfo.KycStatus.INIT - } - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt index 3252c49c4e..61f222a421 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt @@ -16,10 +16,10 @@ import com.tangem.datasource.api.pay.models.request.CardDetailsRequest import com.tangem.datasource.api.pay.models.request.FreezeUnfreezeCardRequest import com.tangem.datasource.api.pay.models.request.SetPinRequest import com.tangem.datasource.api.pay.models.response.FreezeUnfreezeCardResponse +import com.tangem.datasource.api.pay.models.response.OrderResponse.Result.Status import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.model.SetPinResult import com.tangem.domain.pay.model.TangemPayCardBalance import com.tangem.domain.pay.model.TangemPayCardDetails @@ -279,15 +279,15 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( orderStatus.onRight { response -> val status = response.result?.status - if (status == OrderStatus.COMPLETED.apiName || status == OrderStatus.CANCELED.apiName) { + if (status == Status.COMPLETED || status == Status.CANCELED) { // Remove from jobs pollingJobs.remove(key = orderId) // Final card state val finalState = when { - status == OrderStatus.COMPLETED.apiName && isFreeze + status == Status.COMPLETED && isFreeze -> TangemPayCardFrozenState.Frozen - status == OrderStatus.COMPLETED.apiName && !isFreeze + status == Status.COMPLETED && !isFreeze -> TangemPayCardFrozenState.Unfrozen else -> return@launch } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt index 021605d89a..eab58caa05 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt @@ -77,34 +77,22 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor( limit: Int, ): List { cacheRegistry.invokeOnExpire( - key = getCacheKey(customerWalletAddress = config.customerWalletAddress, cursor = cursor), + key = getCacheKey(userWalletId = userWalletId, cursor = cursor), skipCache = config.shouldRefresh, - block = { - fetch( - userWalletId = userWalletId, - customerWalletAddress = config.customerWalletAddress, - cursor = cursor, - pageSize = limit, - ) - }, + block = { fetch(userWalletId = userWalletId, cursor = cursor, pageSize = limit) }, ) return txHistoryItemsStore.getSyncOrNull( - key = config.customerWalletAddress, + key = userWalletId.stringValue, cursor = cursor ?: INITIAL_CURSOR, ).orEmpty() } - private fun getCacheKey(customerWalletAddress: String, cursor: String?): String { - return "tangem_pay_tx_history_${customerWalletAddress}_${cursor ?: INITIAL_CURSOR}" + private fun getCacheKey(userWalletId: UserWalletId, cursor: String?): String { + return "tangem_pay_tx_history_${userWalletId.stringValue}_${cursor ?: INITIAL_CURSOR}" } - private suspend fun fetch( - userWalletId: UserWalletId, - customerWalletAddress: String, - cursor: String?, - pageSize: Int, - ) { + private suspend fun fetch(userWalletId: UserWalletId, cursor: String?, pageSize: Int) { requestPerformer.performRequest(userWalletId = userWalletId) { authHeader -> visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor) }.onLeft { @@ -112,7 +100,7 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor( }.onRight { response -> val result = response.result val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull() - txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items) + txHistoryItemsStore.store(key = userWalletId.stringValue, cursor = cursor ?: INITIAL_CURSOR, value = items) } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt new file mode 100644 index 0000000000..c6cfc742e0 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/store/PaymentAccountStatusesStore.kt @@ -0,0 +1,24 @@ +package com.tangem.data.pay.store + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.PaymentAccountStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Suppress("UnusedParameter", "EmptyFunctionBlock", "FunctionOnlyReturningConstant") +@Singleton +internal class PaymentAccountStatusesStore @Inject constructor() { + + fun get(userWalletId: UserWalletId): Flow { + return emptyFlow() + } + + fun getSyncOrNull(userWalletId: UserWalletId): PaymentAccountStatus? { + return null + } + + fun store(userWalletId: UserWalletId, status: PaymentAccountStatus) { + } +} \ No newline at end of file diff --git a/domain/account/src/main/java/com/tangem/domain/account/supplier/SingleAccountSupplier.kt b/domain/account/src/main/java/com/tangem/domain/account/supplier/SingleAccountSupplier.kt index 2a87c8b14e..a8a0e5cdbb 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/supplier/SingleAccountSupplier.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/supplier/SingleAccountSupplier.kt @@ -22,4 +22,8 @@ abstract class SingleAccountSupplier( fun filterPaymentAccount(accountId: AccountId): Flow { return invoke(params = SingleAccountProducer.Params(accountId)).filterIsInstance() } + + fun filterCryptoPortfolioAccount(accountId: AccountId): Flow { + return invoke(params = SingleAccountProducer.Params(accountId)).filterIsInstance() + } } \ No newline at end of file diff --git a/domain/kyc/models/.gitignore b/domain/kyc/models/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/kyc/models/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/kyc/models/build.gradle.kts b/domain/kyc/models/build.gradle.kts new file mode 100644 index 0000000000..6b18f3f83f --- /dev/null +++ b/domain/kyc/models/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.kotlin.serialization) + id("configuration") +} + +dependencies { + implementation(deps.kotlin.serialization) +} \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/kyc/KycStatus.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/kyc/KycStatus.kt new file mode 100644 index 0000000000..0546090329 --- /dev/null +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/kyc/KycStatus.kt @@ -0,0 +1,33 @@ +package com.tangem.domain.models.kyc + +private const val APPROVED_KYC_STATUS = "approved" +private const val IN_PROGRESS_KYC_STATUS = "in_progress" +private const val DECLINED_KYC_STATUS = "declined" + +enum class KycStatus { + /** Initial state */ + INIT, + + /** Performing the check */ + PENDING, + + /** SumSub approved */ + APPROVED, + + /** The check failed, documents rejected */ + REJECTED, + + ; + + companion object { + + fun fromString(status: String?, default: KycStatus = INIT): KycStatus { + return when (status?.lowercase()) { + IN_PROGRESS_KYC_STATUS -> PENDING + DECLINED_KYC_STATUS -> REJECTED + APPROVED_KYC_STATUS -> APPROVED + else -> default + } + } + } +} \ No newline at end of file diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index c2a83d7894..53e21909a4 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(projects.domain.walletManager) implementation(projects.domain.card) implementation(projects.domain.staking) + implementation(projects.domain.visa) implementation(projects.libs.blockchainSdk) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt index a30ede50c9..56c8bf8ef3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt @@ -9,4 +9,5 @@ enum class FetchingSource { NETWORK, QUOTE, STAKING, + TANGEM_PAY, } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt index 06b3f5f4ee..17b46927d6 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt @@ -2,11 +2,13 @@ package com.tangem.domain.tokens.wallet import arrow.core.Either import arrow.core.raise.either +import arrow.core.right import com.tangem.domain.core.flow.FlowFetcher import com.tangem.domain.core.utils.catchOn import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher +import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher @@ -45,6 +47,7 @@ class WalletBalanceFetcher internal constructor( private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher, + private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher, private val stakingIdFactory: StakingIdFactory, private val dispatchers: CoroutineDispatcherProvider, ) : FlowFetcher { @@ -57,6 +60,7 @@ class WalletBalanceFetcher internal constructor( multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiStakingBalanceFetcher: MultiStakingBalanceFetcher, + paymentAccountStatusFetcher: PaymentAccountStatusFetcher, stakingIdFactory: StakingIdFactory, dispatchers: CoroutineDispatcherProvider, ) : this( @@ -72,6 +76,7 @@ class WalletBalanceFetcher internal constructor( multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiStakingBalanceFetcher = multiStakingBalanceFetcher, + paymentAccountStatusFetcher = paymentAccountStatusFetcher, stakingIdFactory = stakingIdFactory, dispatchers = dispatchers, ) @@ -91,10 +96,18 @@ class WalletBalanceFetcher internal constructor( error("UserWallet doesn't contain crypto-currencies: $userWalletId") } - fetcher.fetch(userWalletId = userWalletId, currencies = currencies) + fetcher.fetch( + userWalletId = userWalletId, + currencies = currencies, + paymentAccountRefactorEnabled = params.isPaymentAccountRefactorEnabled, + ) } - private suspend fun BaseWalletBalanceFetcher.fetch(userWalletId: UserWalletId, currencies: Set) { + private suspend fun BaseWalletBalanceFetcher.fetch( + userWalletId: UserWalletId, + currencies: Set, + paymentAccountRefactorEnabled: Boolean, + ) { coroutineScope { val results = fetchingSources.map { source -> async { @@ -102,6 +115,10 @@ class WalletBalanceFetcher internal constructor( FetchingSource.NETWORK -> fetchNetworks(userWalletId = userWalletId, currencies = currencies) FetchingSource.QUOTE -> fetchQuotes(currencies = currencies) FetchingSource.STAKING -> fetchStaking(userWalletId = userWalletId, currencies = currencies) + FetchingSource.TANGEM_PAY -> fetchPaymentAccount( + userWalletId = userWalletId, + paymentAccountRefactorEnabled = paymentAccountRefactorEnabled, + ) } source to maybeResult @@ -173,10 +190,19 @@ class WalletBalanceFetcher internal constructor( } } + private suspend fun fetchPaymentAccount( + userWalletId: UserWalletId, + paymentAccountRefactorEnabled: Boolean, + ): Either { + 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) + data class Params(val userWalletId: UserWalletId, val isPaymentAccountRefactorEnabled: Boolean) } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt index 8bd9d8a36c..eaa142f3fb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt @@ -27,6 +27,7 @@ internal class MultiWalletBalanceFetcher( FetchingSource.NETWORK, FetchingSource.QUOTE, FetchingSource.STAKING, + FetchingSource.TANGEM_PAY, ) override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set { diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcherTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcherTest.kt index 2af6690bc8..937aa0d32d 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcherTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcherTest.kt @@ -9,6 +9,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.staking.StakingID import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher +import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.model.StakingIntegrationID @@ -42,6 +43,7 @@ internal class WalletBalanceFetcherTest { private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher = mockk() private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher = mockk() private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher = mockk() + private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher = mockk() private val stakingIdFactory: StakingIdFactory = mockk() private val fetcher = WalletBalanceFetcher( @@ -52,6 +54,7 @@ internal class WalletBalanceFetcherTest { multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiStakingBalanceFetcher = multiStakingBalanceFetcher, + paymentAccountStatusFetcher = paymentAccountStatusFetcher, stakingIdFactory = stakingIdFactory, dispatchers = TestingCoroutineDispatcherProvider(), ) @@ -76,7 +79,12 @@ internal class WalletBalanceFetcherTest { every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } throws exception // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = exception.left() @@ -107,7 +115,12 @@ internal class WalletBalanceFetcherTest { every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException("Unknown type of wallet: $userWalletId").left() @@ -139,7 +152,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } throws exception // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = exception.left() @@ -171,7 +189,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns emptySet() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException("UserWallet doesn't contain crypto-currencies: $userWalletId").left() @@ -213,7 +236,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns exception.left() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException( @@ -259,7 +287,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns exception.left() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException( @@ -311,7 +344,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiStakingBalanceFetcher(params = stakingBalanceFetcherParams) } returns exception.left() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException( @@ -354,7 +392,12 @@ internal class WalletBalanceFetcherTest { } returns Either.Left(StakingIdFactory.Error.UnsupportedCurrency) // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert assertEitherRight(actual) @@ -396,7 +439,12 @@ internal class WalletBalanceFetcherTest { coEvery { stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = any()) } returns stakingId // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert assertEitherRight(actual) @@ -444,7 +492,12 @@ internal class WalletBalanceFetcherTest { } returns stellarStakingId // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert assertEitherRight(actual) @@ -506,7 +559,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiStakingBalanceFetcher(params = stakingBalanceFetcherParams) } returns exception.left() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = IllegalStateException( @@ -572,7 +630,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiStakingBalanceFetcher(params = stakingBalanceFetcherParams) } returns Unit.right() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = Unit.right() @@ -624,7 +687,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns Unit.right() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = Unit.right() @@ -674,7 +742,12 @@ internal class WalletBalanceFetcherTest { coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns Unit.right() // Act - val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) + val actual = fetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = false + ) + ) // Assert val expected = Unit.right() diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt index 55bc9e7b8b..100c5964c1 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt @@ -47,7 +47,12 @@ class MultiWalletBalanceFetcherTest { val actual = fetcher.fetchingSources // Assert - val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE, FetchingSource.STAKING) + val expected = setOf( + FetchingSource.NETWORK, + FetchingSource.QUOTE, + FetchingSource.STAKING, + FetchingSource.TANGEM_PAY, + ) Truth.assertThat(actual).isEqualTo(expected) } diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index bc0e47c44b..3c13ebe644 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -24,8 +24,6 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) - implementation(projects.features.swap.domain) - /** Security */ implementation(deps.spongecastle.core) diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/PaymentAccountStatus.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/PaymentAccountStatus.kt new file mode 100644 index 0000000000..bdc604087f --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/PaymentAccountStatus.kt @@ -0,0 +1,66 @@ +package com.tangem.domain.pay + +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.kyc.KycStatus +import com.tangem.domain.models.serialization.SerializedBigDecimal +import kotlinx.serialization.Serializable + +@Serializable +sealed class PaymentAccountStatus { + + abstract val source: StatusSource + + @Serializable + data object Loading : PaymentAccountStatus() { + override val source: StatusSource = StatusSource.ACTUAL + } + + @Serializable + data object NotCreated : PaymentAccountStatus() { + override val source: StatusSource = StatusSource.ACTUAL + } + + @Serializable + data class UnderReview( + override val source: StatusSource, + val kycStatus: KycStatus, + ) : PaymentAccountStatus() + + @Serializable + data class IssuingCard(override val source: StatusSource) : PaymentAccountStatus() + + @Serializable + data class Locked(override val source: StatusSource) : PaymentAccountStatus() + + @Serializable + data class Loaded( + override val source: StatusSource, + val cardId: String, + val lastFourDigits: String, + val balance: SerializedBigDecimal, + val currencyCode: String, + val depositAddress: String?, + val isPinSet: Boolean, + ) : PaymentAccountStatus() + + @Serializable + sealed class Error : PaymentAccountStatus() { + @Serializable + data object ExposedDevice : Error() { + override val source: StatusSource = StatusSource.ACTUAL + } + + @Serializable + data class Unavailable(override val source: StatusSource) : Error() + + @Serializable + data object NotSynced : Error() { + override val source: StatusSource = StatusSource.ACTUAL + } + + @Serializable + data object CardIssueFailed : Error() { + override val source: StatusSource = StatusSource.ACTUAL + } + } +} \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayDetailsConfig.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayDetailsConfig.kt index 14edae3667..e2424a061a 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayDetailsConfig.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayDetailsConfig.kt @@ -9,7 +9,6 @@ data class TangemPayDetailsConfig( val cardId: String, val isPinSet: Boolean, val cardFrozenState: TangemPayCardFrozenState, - val customerWalletAddress: String, val cardNumberEnd: String, val chainId: Int, ) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusFetcher.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusFetcher.kt new file mode 100644 index 0000000000..740d9d0824 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusFetcher.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.pay.flow + +import com.tangem.domain.core.flow.FlowFetcher +import com.tangem.domain.models.wallet.UserWalletId + +interface PaymentAccountStatusFetcher : FlowFetcher { + data class Params(val userWalletId: UserWalletId) +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusProducer.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusProducer.kt new file mode 100644 index 0000000000..c49a25f45d --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusProducer.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.pay.flow + +import com.tangem.domain.core.flow.FlowProducer +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.PaymentAccountStatus + +interface PaymentAccountStatusProducer : FlowProducer { + data class Params(val userWalletId: UserWalletId) + + interface Factory : FlowProducer.Factory +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusSupplier.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusSupplier.kt new file mode 100644 index 0000000000..94580d26e1 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/flow/PaymentAccountStatusSupplier.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.pay.flow + +import com.tangem.domain.core.flow.FlowCachingSupplier +import com.tangem.domain.pay.PaymentAccountStatus + +@Suppress("UnnecessaryAbstractClass") +abstract class PaymentAccountStatusSupplier( + override val factory: PaymentAccountStatusProducer.Factory, + override val keyCreator: (PaymentAccountStatusProducer.Params) -> String, +) : FlowCachingSupplier() \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt index 501df2011b..2fd02e7a45 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt @@ -1,6 +1,6 @@ package com.tangem.domain.pay.model -import com.tangem.domain.visa.model.TangemPayCardFrozenState +import com.tangem.domain.models.kyc.KycStatus import java.math.BigDecimal sealed class MainCustomerInfoContentState { @@ -22,31 +22,15 @@ data class CustomerInfo( val cardInfo: CardInfo?, ) { - enum class KycStatus { - /** Initial state */ - INIT, - - /** Performing the check */ - PENDING, - - /** SumSub approved */ - APPROVED, - - /** The check failed, documents rejected */ - REJECTED, - } - data class ProductInstance( val id: String, val cardId: String, - val cardFrozenState: TangemPayCardFrozenState, ) data class CardInfo( val lastFourDigits: String, val balance: BigDecimal, val currencyCode: String, - val customerWalletAddress: String, val depositAddress: String?, val isPinSet: Boolean, ) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt index 6ae706a0e1..327d8fd61f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt @@ -1,9 +1,9 @@ package com.tangem.domain.pay.model -enum class OrderStatus(val apiName: String) { - UNKNOWN(""), - NEW("NEW"), - PROCESSING("PROCESSING"), - COMPLETED("COMPLETED"), - CANCELED("CANCELED"), +enum class OrderStatus { + UNKNOWN, // TODO remove it after TangemPay accounts refactor TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED + NEW, + PROCESSING, + COMPLETED, + CANCELED, } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt index 568cf85006..52197ec817 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt @@ -23,7 +23,7 @@ interface OnboardingRepository { suspend fun getOrderId(userWalletId: UserWalletId): String? - suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either + suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either suspend fun checkCustomerEligibility(): Boolean suspend fun getCustomerEligibility(): Boolean diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 647e6ad412..4116e2e4f4 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.pay.usecase import arrow.core.Either import arrow.core.left import arrow.core.right +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.model.* @@ -38,7 +39,7 @@ class TangemPayMainScreenCustomerInfoUseCase( return // fast exit } - onboardingRepository.checkCustomerWallet(userWalletId) + onboardingRepository.hasTangemPayInWallet(userWalletId) .fold( ifLeft = { error -> Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") @@ -125,7 +126,7 @@ class TangemPayMainScreenCustomerInfoUseCase( } .map { customerInfo -> Timber.tag(TAG).i("customerInfo") - if (customerInfo.cardInfo == null && customerInfo.kycStatus == CustomerInfo.KycStatus.APPROVED) { + if (customerInfo.cardInfo == null && customerInfo.kycStatus == KycStatus.APPROVED) { // If order id wasn't saved -> start order creation and get customer info onboardingRepository.createOrder(userWalletId) } @@ -151,7 +152,7 @@ class TangemPayMainScreenCustomerInfoUseCase( info = CustomerInfo( customerId = null, productInstance = null, - kycStatus = CustomerInfo.KycStatus.APPROVED, + kycStatus = KycStatus.APPROVED, cardInfo = null, ), orderStatus = orderData.status, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/model/TangemPayTxHistoryListConfig.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/model/TangemPayTxHistoryListConfig.kt index fb61bc2965..5b68240025 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/model/TangemPayTxHistoryListConfig.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/model/TangemPayTxHistoryListConfig.kt @@ -1,3 +1,3 @@ package com.tangem.domain.tangempay.model -data class TangemPayTxHistoryListConfig(val customerWalletAddress: String, val shouldRefresh: Boolean) \ No newline at end of file +data class TangemPayTxHistoryListConfig(val shouldRefresh: Boolean) \ No newline at end of file diff --git a/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt b/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt index b18a7db430..2a24783240 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/AccountCreateEditComponent.kt @@ -15,7 +15,7 @@ interface AccountCreateEditComponent : ComposableContentComponent { ) : Params data class Edit( - val account: Account, + val account: Account.CryptoPortfolio, ) : Params } } \ No newline at end of file diff --git a/features/account/api/src/main/java/com/tangem/features/account/AccountDetailsComponent.kt b/features/account/api/src/main/java/com/tangem/features/account/AccountDetailsComponent.kt index d1c47280ea..99450a8474 100644 --- a/features/account/api/src/main/java/com/tangem/features/account/AccountDetailsComponent.kt +++ b/features/account/api/src/main/java/com/tangem/features/account/AccountDetailsComponent.kt @@ -7,5 +7,5 @@ import com.tangem.domain.models.account.Account interface AccountDetailsComponent : ComposableContentComponent { interface Factory : ComponentFactory - data class Params(val account: Account) + data class Params(val account: Account.CryptoPortfolio) } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt index d100998aca..4b69fc806d 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt @@ -25,7 +25,6 @@ import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.domain.models.account.DerivationIndex -import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.account.AccountCreateEditComponent import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents @@ -73,7 +72,7 @@ internal class AccountCreateEditModel @Inject constructor( when (params) { is AccountCreateEditComponent.Params.Create -> updateDerivationInfo(userWalletId = params.userWalletId) is AccountCreateEditComponent.Params.Edit -> { - val derivationIndex = params.account.derivationIndex?.value + val derivationIndex = params.account.derivationIndex.value val event = AccountSettingsAnalyticEvents.AccountEditScreenOpened(derivationIndex) analyticsEventHandler.send(event) } @@ -170,7 +169,7 @@ internal class AccountCreateEditModel @Inject constructor( val icon = CryptoPortfolioIconConverter.convertBack(state.account.portfolioIcon) val isNewName = name != params.account.accountName val isNewIcon = icon != params.account.portfolioIcon - val derivationIndex = params.account.derivationIndex?.value + val derivationIndex = params.account.derivationIndex.value analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon, derivationIndex)) uiState.value = uiState.value.toggleProgress(showProgress = true) @@ -182,7 +181,7 @@ internal class AccountCreateEditModel @Inject constructor( uiState.value = uiState.value.toggleProgress(showProgress = false) result - .onLeft { error -> handleEditAccountError(error, params.account.derivationIndex?.value) } + .onLeft { error -> handleEditAccountError(error, params.account.derivationIndex.value) } .onRight { showMessage(R.string.account_edit_success_message) router.pop() diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt index 47b06df25c..ee77d31aa2 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt @@ -14,12 +14,10 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.ToastMessage -import com.tangem.domain.account.producer.SingleAccountProducer import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase import com.tangem.domain.account.supplier.SingleAccountSupplier import com.tangem.domain.models.PortfolioId import com.tangem.domain.models.account.Account -import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.account.AccountDetailsComponent @@ -54,29 +52,29 @@ internal class AccountDetailsModel @Inject constructor( init { analyticsEventHandler.send(AccountSettingsAnalyticEvents.AccountSettingsScreenOpened()) - singleAccountSupplier(SingleAccountProducer.Params(accountId)) + singleAccountSupplier.filterCryptoPortfolioAccount(accountId) .onEach { account -> uiState.update { buildUI(account) } } .launchIn(modelScope) } - private fun onEditAccountClick(account: Account) { + private fun onEditAccountClick(account: Account.CryptoPortfolio) { analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonEdit()) router.push(AppRoute.EditAccount(account)) } - private fun onManageTokensClick(account: Account) { + private fun onManageTokensClick(account: Account.CryptoPortfolio) { val route = AppRoute.ManageTokens( source = AppRoute.ManageTokens.Source.ACCOUNT, portfolioId = PortfolioId(account.accountId), ) analyticsEventHandler.send( - AccountSettingsAnalyticEvents.ButtonManageTokens(account.derivationIndex?.value), + AccountSettingsAnalyticEvents.ButtonManageTokens(account.derivationIndex.value), ) router.push(route) } private fun onArchiveAccountClick() { - val accountDerivation = params.account.derivationIndex?.value + val accountDerivation = params.account.derivationIndex.value val event = AccountSettingsAnalyticEvents.ButtonArchiveAccount(accountDerivation) analyticsEventHandler.send(event) confirmArchiveDialog() @@ -86,7 +84,7 @@ internal class AccountDetailsModel @Inject constructor( val secondAction = EventMessageAction( title = resourceReference(R.string.common_cancel), onClick = { - val accountDerivation = params.account.derivationIndex?.value + val accountDerivation = params.account.derivationIndex.value val event = AccountSettingsAnalyticEvents.ButtonCancelAccountArchivation(accountDerivation) analyticsEventHandler.send(event) }, @@ -107,7 +105,7 @@ internal class AccountDetailsModel @Inject constructor( } private fun archiveCryptoPortfolio() = modelScope.launch { - val accountDerivation = params.account.derivationIndex?.value + val accountDerivation = params.account.derivationIndex.value val event = AccountSettingsAnalyticEvents.ButtonArchiveAccountConfirmation(accountDerivation) analyticsEventHandler.send(event) uiState.update { it.toggleProgress(true) } @@ -128,7 +126,7 @@ internal class AccountDetailsModel @Inject constructor( val event = AccountSettingsAnalyticEvents.AccountError( source = AccountSettingsAnalyticEvents.Source.ARCHIVE, error = error.tag, - accountDerivation = params.account.derivationIndex?.value, + accountDerivation = params.account.derivationIndex.value, ) analyticsEventHandler.send(event) val titleRes: Int @@ -155,16 +153,13 @@ internal class AccountDetailsModel @Inject constructor( messageSender.send(dialogMessage) } - private fun buildUI(account: Account): AccountDetailsUM { - val archiveMode = when (account) { - is Account.CryptoPortfolio -> when (account.isMainAccount) { - true -> ArchiveMode.None - false -> ArchiveMode.Available( - onArchiveAccountClick = ::onArchiveAccountClick, - isLoading = false, - ) - } - is Account.Payment -> TODO("[REDACTED_JIRA]") + private fun buildUI(account: Account.CryptoPortfolio): AccountDetailsUM { + val archiveMode = when (account.isMainAccount) { + true -> ArchiveMode.None + false -> ArchiveMode.Available( + onArchiveAccountClick = ::onArchiveAccountClick, + isLoading = false, + ) } val isMultiCurrency = getUserWalletUseCase(account.accountId.userWalletId).getOrNull() ?.isMultiCurrency == true diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt index 40ac56570b..fadde70778 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoModel.kt @@ -26,7 +26,6 @@ import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.AccountStatus -import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet @@ -277,9 +276,9 @@ internal class HotCryptoModel @Inject constructor( private fun updateCryptoCurrency( cryptoCurrency: CryptoCurrency, userWallet: UserWallet, - account: AccountStatus, + account: AccountStatus.CryptoPortfolio, ): CryptoCurrency? { - val derivationIndex = account.account.derivationIndex ?: return null + val derivationIndex = account.account.derivationIndex val blockchain = cryptoCurrency.network.toBlockchain() val network = networkFactory.create( diff --git a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt new file mode 100644 index 0000000000..0f2ae969eb --- /dev/null +++ b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.tangempay + +interface TangemPayFeatureToggles { + val isTangemPayAccountsRefactorEnabled: Boolean +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt new file mode 100644 index 0000000000..30897aff29 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt @@ -0,0 +1,10 @@ +package com.tangem.features.tangempay + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager + +internal class DefaultTangemPayFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : TangemPayFeatureToggles { + override val isTangemPayAccountsRefactorEnabled + get() = featureTogglesManager.isFeatureEnabled("TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED") +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt index 2b5736147e..58f5d9ef84 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt @@ -45,7 +45,6 @@ internal class TangemPayDetailsComponent( appComponentContext = child("txHistoryComponent"), params = DefaultTangemPayTxHistoryComponent.Params( userWalletId = params.userWalletId, - customerWalletAddress = params.config.customerWalletAddress, uiActions = model, ), ) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/DefaultTangemPayTxHistoryComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/DefaultTangemPayTxHistoryComponent.kt index bfd618dfa5..d27e63ad51 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/DefaultTangemPayTxHistoryComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/DefaultTangemPayTxHistoryComponent.kt @@ -25,7 +25,6 @@ internal class DefaultTangemPayTxHistoryComponent( data class Params( val userWalletId: UserWalletId, - val customerWalletAddress: String, val uiActions: TangemPayTxHistoryUiActions, ) } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayDetailsModule.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayDetailsModule.kt new file mode 100644 index 0000000000..a6ea142d28 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayDetailsModule.kt @@ -0,0 +1,21 @@ +package com.tangem.features.tangempay.di + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.features.tangempay.DefaultTangemPayFeatureToggles +import com.tangem.features.tangempay.TangemPayFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TangemPayDetailsModule { + + @Provides + @Singleton + fun provideTangemPayFeatureToggles(featureTogglesManager: FeatureTogglesManager): TangemPayFeatureToggles { + return DefaultTangemPayFeatureToggles(featureTogglesManager) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt index e1433f4d17..47b229ce5d 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryModel.kt @@ -31,7 +31,6 @@ internal class TangemPayTxHistoryModel @Inject constructor( private val listManager = TangemPayTxHistoryListManager( repository = tangemPayTxHistoryRepository, dispatchers = dispatchers, - customerWalletAddress = params.customerWalletAddress, txHistoryUiActions = params.uiActions, ) @@ -104,7 +103,7 @@ internal class TangemPayTxHistoryModel @Inject constructor( } private fun loadMoreItems(): Boolean { - modelScope.launch { listManager.loadMore(params.customerWalletAddress) } + modelScope.launch { listManager.loadMore() } return true } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt index f1b971367c..fa798e16bc 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayTxHistoryListManager.kt @@ -22,7 +22,6 @@ private typealias TangemPayTxHistoryBatchAction = BatchAction walletBalanceFetcher( - params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId), + params = WalletBalanceFetcher.Params( + userWalletId = userWallet.walletId, + isPaymentAccountRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled, + ), ) } } diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt index 6c53807de1..b6b47ea825 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt @@ -145,7 +145,7 @@ internal class AccountItemsDelegate @Inject constructor( return this.sortedBy { positionByAccountId[it.id] ?: Int.MAX_VALUE } } - private fun openAccountDetails(account: Account) { + private fun openAccountDetails(account: Account.CryptoPortfolio) { router.push(AppRoute.AccountDetails(account)) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt index 5e65dfb36a..a54705233c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt @@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped -import com.tangem.domain.pay.model.CustomerInfo +import com.tangem.domain.models.kyc.KycStatus import com.tangem.domain.pay.model.MainScreenCustomerInfo import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.tangempay.TangemPayAnalyticsEvents @@ -29,7 +29,7 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor( // ignore cancelled state on analytics customerInfo.orderStatus == OrderStatus.CANCELED -> return // ignore kyc not approved state on analytics - customerInfo.info.kycStatus != CustomerInfo.KycStatus.APPROVED -> return + customerInfo.info.kycStatus != KycStatus.APPROVED -> return cardInfo != null && productInstance != null -> return else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletContentFetcher.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletContentFetcher.kt index 46e14c7693..10de4aeeb6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletContentFetcher.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletContentFetcher.kt @@ -1,7 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.domain -import com.tangem.domain.tokens.wallet.WalletBalanceFetcher import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.tokens.wallet.WalletBalanceFetcher +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveInAndJoin @@ -27,6 +28,7 @@ import javax.inject.Singleton internal class WalletContentFetcher @Inject constructor( private val walletBalanceFetcher: WalletBalanceFetcher, private val dispatchers: CoroutineDispatcherProvider, + private val tangemPayFeatureToggles: TangemPayFeatureToggles, ) { private val fetchingJobMap = ConcurrentHashMap() @@ -64,8 +66,12 @@ internal class WalletContentFetcher @Inject constructor( Timber.d("Start fetching for $userWalletId") val maybeResult = launch { - walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId)) - .onLeft(Timber::e) + walletBalanceFetcher( + params = WalletBalanceFetcher.Params( + userWalletId = userWalletId, + isPaymentAccountRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled, + ), + ).onLeft(Timber::e) } .saveInAndJoin(jobHolder) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt index c07d25bdfc..2839523857 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.models.kyc.KycStatus import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.TangemPayDetailsConfig import com.tangem.domain.pay.model.CustomerInfo.CardInfo @@ -16,8 +17,6 @@ import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.domain.pay.model.CustomerInfo.KycStatus.APPROVED -import com.tangem.domain.pay.model.CustomerInfo import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM import java.util.Currency @@ -55,7 +54,7 @@ internal class TangemPayUpdateInfoStateTransformer( // when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing. return when { value.orderStatus == OrderStatus.CANCELED -> createCancelledState(customerId) - value.info.kycStatus != APPROVED && !value.info.customerId.isNullOrEmpty() -> + value.info.kycStatus != KycStatus.APPROVED && !value.info.customerId.isNullOrEmpty() -> createKycInProgressState(kycStatus = value.info.kycStatus, customerId = customerId) cardInfo != null && productInstance != null -> getCardInfoState(customerId, cardInfo, productInstance) @@ -79,7 +78,6 @@ internal class TangemPayUpdateInfoStateTransformer( cardId = productInstance.cardId, isPinSet = cardInfo.isPinSet, cardFrozenState = cardFrozenState, - customerWalletAddress = cardInfo.customerWalletAddress, cardNumberEnd = cardInfo.lastFourDigits, chainId = POLYGON_CHAIN_ID, ), @@ -94,25 +92,24 @@ internal class TangemPayUpdateInfoStateTransformer( } } - private fun createKycInProgressState(kycStatus: CustomerInfo.KycStatus, customerId: String): TangemPayState = - Progress( - title = TextReference.Res(R.string.tangempay_payment_account), - description = when (kycStatus) { - CustomerInfo.KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed) - else -> TextReference.Res(R.string.tangempay_kyc_in_progress) - }, - buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button), - iconRes = R.drawable.ic_promo_kyc_36, - onButtonClick = { - when (kycStatus) { - CustomerInfo.KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked( - userWalletId = userWalletId, - customerId = customerId, - ) - else -> tangemPayClickIntents.onKycProgressClicked(userWalletId) - } - }, - ) + private fun createKycInProgressState(kycStatus: KycStatus, customerId: String): TangemPayState = Progress( + title = TextReference.Res(R.string.tangempay_payment_account), + description = when (kycStatus) { + KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed) + else -> TextReference.Res(R.string.tangempay_kyc_in_progress) + }, + buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button), + iconRes = R.drawable.ic_promo_kyc_36, + onButtonClick = { + when (kycStatus) { + KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked( + userWalletId = userWalletId, + customerId = customerId, + ) + else -> tangemPayClickIntents.onKycProgressClicked(userWalletId) + } + }, + ) private fun createIssueProgressState(): TangemPayState = Progress( title = TextReference.Res(R.string.tangempay_payment_account), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt index b8f7e9a587..fbf1117370 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt @@ -8,10 +8,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.tangem.common.ui.R import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock @Composable @@ -36,6 +38,17 @@ private fun TangemPayMainScreenBlockPreview() { TangemThemePreview { Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false) + TangemPayMainScreenBlock( + state = TangemPayState.RefreshNeeded( + TangemPayRefreshNeeded( + tangemIcon = R.drawable.ic_tangem_24, + buttonText = resourceReference(id = R.string.home_button_scan), + onRefreshClick = {}, + shouldShowProgress = false, + ), + ), + isBalanceHidden = false, + ) TangemPayMainScreenBlock(state = TangemPayState.ExposedDevice, isBalanceHidden = false)