Updated on 2026-08-14
This commit is contained in:
parent
a41dd9aace
commit
41fb5d5bcc
55 changed files with 709 additions and 173 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -59,5 +59,9 @@
|
|||
{
|
||||
"name": "GASLESS_APPROVAL_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Throwable, Unit> =
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PaymentAccountStatus>
|
||||
get() = PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL).some()
|
||||
|
||||
override fun produce(): Flow<PaymentAccountStatus> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<VisaApiError, Boolean> {
|
||||
override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,34 +77,22 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
limit: Int,
|
||||
): List<TangemPayTxHistoryItem> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<PaymentAccountStatus> {
|
||||
return emptyFlow()
|
||||
}
|
||||
|
||||
fun getSyncOrNull(userWalletId: UserWalletId): PaymentAccountStatus? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun store(userWalletId: UserWalletId, status: PaymentAccountStatus) {
|
||||
}
|
||||
}
|
||||
|
|
@ -22,4 +22,8 @@ abstract class SingleAccountSupplier(
|
|||
fun filterPaymentAccount(accountId: AccountId): Flow<Account.Payment> {
|
||||
return invoke(params = SingleAccountProducer.Params(accountId)).filterIsInstance()
|
||||
}
|
||||
|
||||
fun filterCryptoPortfolioAccount(accountId: AccountId): Flow<Account.CryptoPortfolio> {
|
||||
return invoke(params = SingleAccountProducer.Params(accountId)).filterIsInstance()
|
||||
}
|
||||
}
|
||||
1
domain/kyc/models/.gitignore
vendored
Normal file
1
domain/kyc/models/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
9
domain/kyc/models/build.gradle.kts
Normal file
9
domain/kyc/models/build.gradle.kts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ enum class FetchingSource {
|
|||
NETWORK,
|
||||
QUOTE,
|
||||
STAKING,
|
||||
TANGEM_PAY,
|
||||
}
|
||||
|
|
@ -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<WalletBalanceFetcher.Params> {
|
||||
|
|
@ -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<CryptoCurrency>) {
|
||||
private suspend fun BaseWalletBalanceFetcher.fetch(
|
||||
userWalletId: UserWalletId,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
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<Throwable, Unit> {
|
||||
if (!paymentAccountRefactorEnabled) return Unit.right()
|
||||
|
||||
return paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(userWalletId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Params of [WalletBalanceFetcher]
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
*/
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
data class Params(val userWalletId: UserWalletId, val isPaymentAccountRefactorEnabled: Boolean)
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ internal class MultiWalletBalanceFetcher(
|
|||
FetchingSource.NETWORK,
|
||||
FetchingSource.QUOTE,
|
||||
FetchingSource.STAKING,
|
||||
FetchingSource.TANGEM_PAY,
|
||||
)
|
||||
|
||||
override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<PaymentAccountStatusFetcher.Params> {
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -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<PaymentAccountStatus> {
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
|
||||
interface Factory : FlowProducer.Factory<Params, PaymentAccountStatusProducer>
|
||||
}
|
||||
|
|
@ -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<PaymentAccountStatusProducer, PaymentAccountStatusProducer.Params, PaymentAccountStatus>()
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ interface OnboardingRepository {
|
|||
|
||||
suspend fun getOrderId(userWalletId: UserWalletId): String?
|
||||
|
||||
suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
|
||||
suspend fun checkCustomerEligibility(): Boolean
|
||||
suspend fun getCustomerEligibility(): Boolean
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
package com.tangem.domain.tangempay.model
|
||||
|
||||
data class TangemPayTxHistoryListConfig(val customerWalletAddress: String, val shouldRefresh: Boolean)
|
||||
data class TangemPayTxHistoryListConfig(val shouldRefresh: Boolean)
|
||||
|
|
@ -15,7 +15,7 @@ interface AccountCreateEditComponent : ComposableContentComponent {
|
|||
) : Params
|
||||
|
||||
data class Edit(
|
||||
val account: Account,
|
||||
val account: Account.CryptoPortfolio,
|
||||
) : Params
|
||||
}
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ import com.tangem.domain.models.account.Account
|
|||
interface AccountDetailsComponent : ComposableContentComponent {
|
||||
interface Factory : ComponentFactory<Params, AccountDetailsComponent>
|
||||
|
||||
data class Params(val account: Account)
|
||||
data class Params(val account: Account.CryptoPortfolio)
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.tangempay
|
||||
|
||||
interface TangemPayFeatureToggles {
|
||||
val isTangemPayAccountsRefactorEnabled: Boolean
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -45,7 +45,6 @@ internal class TangemPayDetailsComponent(
|
|||
appComponentContext = child("txHistoryComponent"),
|
||||
params = DefaultTangemPayTxHistoryComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
customerWalletAddress = params.config.customerWalletAddress,
|
||||
uiActions = model,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ internal class DefaultTangemPayTxHistoryComponent(
|
|||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val customerWalletAddress: String,
|
||||
val uiActions: TangemPayTxHistoryUiActions,
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ private typealias TangemPayTxHistoryBatchAction = BatchAction<Int, TangemPayTxHi
|
|||
internal class TangemPayTxHistoryListManager(
|
||||
private val repository: TangemPayTxHistoryRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val customerWalletAddress: String,
|
||||
private val txHistoryUiActions: TangemPayTxHistoryUiActions,
|
||||
) {
|
||||
private val jobHolder = JobHolder()
|
||||
|
|
@ -56,20 +55,13 @@ internal class TangemPayTxHistoryListManager(
|
|||
|
||||
suspend fun reload() {
|
||||
actionsFlow.emit(
|
||||
BatchAction.Reload(
|
||||
requestParams = TangemPayTxHistoryListConfig(
|
||||
customerWalletAddress = customerWalletAddress,
|
||||
shouldRefresh = true,
|
||||
),
|
||||
),
|
||||
BatchAction.Reload(requestParams = TangemPayTxHistoryListConfig(shouldRefresh = true)),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loadMore(customerWalletAddress: String) {
|
||||
suspend fun loadMore() {
|
||||
actionsFlow.emit(
|
||||
BatchAction.LoadMore(
|
||||
requestParams = TangemPayTxHistoryListConfig(customerWalletAddress, shouldRefresh = false),
|
||||
),
|
||||
BatchAction.LoadMore(requestParams = TangemPayTxHistoryListConfig(shouldRefresh = false)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
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.CustomerInfo.KycStatus
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ dependencies {
|
|||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.tokenRecieve.api)
|
||||
implementation(projects.features.yieldSupply.api)
|
||||
implementation(projects.features.tangempay.details.api)
|
||||
|
||||
implementation(deps.decompose.ext.compose)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
|||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -48,6 +49,7 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
) : TokenDetailsDeepLinkHandler {
|
||||
|
||||
|
|
@ -128,7 +130,10 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
id = cryptoCurrency.id,
|
||||
)
|
||||
!isMultiCurrency -> walletBalanceFetcher(
|
||||
params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId),
|
||||
params = WalletBalanceFetcher.Params(
|
||||
userWalletId = userWallet.walletId,
|
||||
isPaymentAccountRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UserWalletId, JobHolder>()
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue