diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardBalanceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardBalanceResponse.kt index 164915f64d..6eae94e330 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardBalanceResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardBalanceResponse.kt @@ -4,5 +4,4 @@ import com.squareup.moshi.Json data class CardBalanceResponse( @Json(name = "result") val result: BalanceResponse?, - @Json(name = "error") val error: String?, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt index 007bcf7428..17b226bacc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt @@ -5,7 +5,6 @@ import com.squareup.moshi.JsonClass data class CardDetailsResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt index b926f0fa05..01ee25582c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class CheckCustomerWalletResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { data class Result( @Json(name = "id") val id: String?, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt index 8ddcfab3f7..48e148069c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerEligibilityResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class CustomerEligibilityResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt index cc0edd82a4..69405843f7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class CustomerMeResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/DeeplinkValidityResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/DeeplinkValidityResponse.kt index 638055ef5c..71b094a2cd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/DeeplinkValidityResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/DeeplinkValidityResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class DeeplinkValidityResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/FreezeUnfreezeCardResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/FreezeUnfreezeCardResponse.kt index 7ec1155bdc..e42230d2fc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/FreezeUnfreezeCardResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/FreezeUnfreezeCardResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) class FreezeUnfreezeCardResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( 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 806012dfa3..a476b5dc1b 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 @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class OrderResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt index 8b75b8634b..009b96d99c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/TangemPayTxHistoryResponse.kt @@ -7,7 +7,6 @@ import java.math.BigDecimal @JsonClass(generateAdapter = true) data class TangemPayTxHistoryResponse( - @Json(name = "error") val error: String?, @Json(name = "result") val result: Result, ) { @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawDataResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawDataResponse.kt index 8bc4fcbf1e..af5860ab39 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawDataResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawDataResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WithdrawDataResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawResponse.kt index 2b347f27c0..5a353ae6bf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/WithdrawResponse.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class WithdrawResponse( @Json(name = "result") val result: Result?, - @Json(name = "error") val error: String?, ) { @JsonClass(generateAdapter = true) data class Result( 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 97a6d9e7ca..f2d850f463 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 @@ -9,26 +9,43 @@ import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.hotwallet.HotWalletFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject internal class DefaultTangemPayEligibilityManager @Inject constructor( + dispatchers: CoroutineDispatcherProvider, private val userWalletsListManager: UserWalletsListManager, private val userWalletsListRepository: UserWalletsListRepository, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val onboardingRepository: OnboardingRepository, ) : TangemPayEligibilityManager { - private var cachedEligibleWallets: List? = null - private var eligibleWalletsDeferred: Deferred>? = null + private var cachedEligibleWallets: List? = null + private var eligibleWalletsDeferred: Deferred>? = null private val loadMutex = Mutex() + private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default) - override suspend fun getEligibleWallets(): List { + init { + resetDataWhenWalletsUpdate() + } + + override suspend fun getEligibleWallets(shouldExcludePaeraCustomers: Boolean): List { + return getUserWalletsData().mapNotNull { + if (!it.isPaeraCustomer || !shouldExcludePaeraCustomers) it.userWallet else null + } + } + + private suspend fun getUserWalletsData(): List { cachedEligibleWallets?.let { return it } return loadMutex.withLock { @@ -38,7 +55,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( coroutineScope { val deferred = async { getPossibleWalletsForTangemPay() - .excludePaeraCustomers() + .addPaeraCustomersData() .also { cachedEligibleWallets = it } } eligibleWalletsDeferred = deferred @@ -73,8 +90,8 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( is UserWallet.Hot -> true } - private suspend fun List.excludePaeraCustomers(): List { - if (isEmpty()) return this + private suspend fun List.addPaeraCustomersData(): List { + if (isEmpty()) return emptyList() return coroutineScope { map { wallet -> @@ -86,9 +103,30 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( } } .awaitAll() - .mapNotNull { (wallet, isCustomer) -> - wallet.takeUnless { isCustomer } + .map { (userWallet, isPaeraCustomer) -> + UserWalletData(userWallet, isPaeraCustomer) } } } + + private fun resetDataWhenWalletsUpdate() { + coroutineScope.launch { + if (hotWalletFeatureToggles.isHotWalletEnabled) { + userWalletsListRepository.userWallets.collectLatest { reset() } + } else { + userWalletsListManager.userWallets.collectLatest { reset() } + } + } + } + + private fun reset() { + cachedEligibleWallets = null + eligibleWalletsDeferred?.cancel() + eligibleWalletsDeferred = null + } + + private data class UserWalletData( + val userWallet: UserWallet, + val isPaeraCustomer: Boolean, + ) } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt index b68cd25513..5ad9a9d3d9 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt @@ -4,5 +4,5 @@ import com.tangem.domain.models.wallet.UserWallet interface TangemPayEligibilityManager { - suspend fun getEligibleWallets(): List + suspend fun getEligibleWallets(shouldExcludePaeraCustomers: Boolean): List } \ No newline at end of file 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 9636a5136b..16ebd064e6 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 @@ -46,7 +46,11 @@ class TangemPayMainScreenCustomerInfoUseCase( .fold( ifLeft = { error -> Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") - updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + if (error is VisaApiError.NotPaeraCustomer) { + showOnboardingBannerIfEligible(userWalletId) + } else { + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } }, ifRight = { hasTangemPay -> Timber.tag(TAG).i("checkCustomerWallet for $userWalletId: $hasTangemPay") @@ -61,21 +65,27 @@ class TangemPayMainScreenCustomerInfoUseCase( updateState(userWalletId, result) } else { // if there's no tangem pay, check eligibility and show onboarding banner - val isEligible = eligibilityManager.getEligibleWallets().any { it.walletId == userWalletId } - if (isEligible) { - if (onboardingRepository.getHideMainOnboardingBanner(userWalletId)) { - updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) - } else { - updateState(userWalletId, MainCustomerInfoContentState.OnboardingBanner.right()) - } - } else { - updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) - } + showOnboardingBannerIfEligible(userWalletId) } }, ) } + private suspend fun showOnboardingBannerIfEligible(userWalletId: UserWalletId) { + val isEligible = eligibilityManager + .getEligibleWallets(shouldExcludePaeraCustomers = false) + .any { it.walletId == userWalletId } + if (isEligible) { + if (onboardingRepository.getHideMainOnboardingBanner(userWalletId)) { + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } else { + updateState(userWalletId, MainCustomerInfoContentState.OnboardingBanner.right()) + } + } else { + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } + } + operator fun invoke( userWalletId: UserWalletId, ): Flow> { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 69fe323b69..f5d5d79eb5 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -248,7 +248,9 @@ internal class DetailsModel @Inject constructor( private fun addTangemPayItemIfEligible() { if (!tangemPayFeatureToggles.isTangemPayEnabled) return modelScope.launch { - val isEligible = tangemPayEligibilityManager.getEligibleWallets().isNotEmpty() + val isEligible = tangemPayEligibilityManager + .getEligibleWallets(shouldExcludePaeraCustomers = true) + .isNotEmpty() if (isEligible) { items.update { itemsBuilder.addVisaItem(it) } } diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt index 764ff69042..83700ab313 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt @@ -65,7 +65,9 @@ internal class TangemPayOnboardingModel @Inject constructor( .onRight { isValid -> if (isValid) showOnboarding() else back() } .onLeft { back() } } - is TangemPayOnboardingComponent.Params.ContinueOnboarding, + is TangemPayOnboardingComponent.Params.ContinueOnboarding -> { + openKyc(userWalletId = params.userWalletId) + } is TangemPayOnboardingComponent.Params.FromBannerInSettings, is TangemPayOnboardingComponent.Params.FromBannerOnMain, -> showOnboarding() @@ -113,8 +115,13 @@ internal class TangemPayOnboardingModel @Inject constructor( private fun onGetCardClick() { analytics.send(TangemPayAnalyticsEvents.GetCardClicked()) + // if user came from deeplink or banner in settings and already is a paera customer -> exclude this wallet + val shouldExcludePaeraCustomers = params is TangemPayOnboardingComponent.Params.FromBannerInSettings || + params is TangemPayOnboardingComponent.Params.Deeplink modelScope.launch { - val eligibleWalletsIds = eligibilityManager.getEligibleWallets().map { it.walletId } + val eligibleWalletsIds = eligibilityManager + .getEligibleWallets(shouldExcludePaeraCustomers = shouldExcludePaeraCustomers) + .map { it.walletId } if (eligibleWalletsIds.isEmpty()) { back() return@launch diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt index b57061cb36..62e59a5eed 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayOnboardingBanner.kt @@ -4,21 +4,9 @@ import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset @@ -28,8 +16,9 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -57,28 +46,20 @@ internal fun TangemPayOnboardingBanner(state: TangemPayState.OnboardingBanner, m ) .clickable(onClick = state.onClick), ) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(IntrinsicSize.Min) - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, + ConstraintLayout( + modifier = Modifier.fillMaxWidth(), ) { - Image( - painter = painterResource(R.drawable.img_tangem_pay_onboarding_banner), - contentDescription = null, - modifier = Modifier - .padding(horizontal = 8.dp) - .width(55.dp) - .height(95.dp) - .offset(y = 10.dp), - ) - - SpacerH4() + val (image, text, close) = createRefs() Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.Center, + modifier = Modifier + .constrainAs(text) { + top.linkTo(parent.top) + start.linkTo(image.end, margin = 12.dp) + end.linkTo(parent.end) + width = Dimension.fillToConstraints + } + .padding(top = 16.dp, end = 16.dp, bottom = 16.dp), ) { Text( text = stringResourceSafe(R.string.tangempay_onboarding_banner_title), @@ -94,22 +75,32 @@ internal fun TangemPayOnboardingBanner(state: TangemPayState.OnboardingBanner, m color = TangemTheme.colors.text.tertiary, ) } - Box( - modifier = Modifier.fillMaxHeight(), - contentAlignment = Alignment.TopEnd, - ) { - Image( - modifier = Modifier - .clip(TangemTheme.shapes.roundedCornersXMedium) - .clickable(onClick = state.closeOnClick) - .padding(4.dp) - .padding(top = 12.dp) - .size(12.dp), - painter = painterResource(id = R.drawable.ic_close_24), - colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive), - contentDescription = null, - ) - } + + Image( + painter = painterResource(R.drawable.img_tangem_pay_onboarding_banner), + contentDescription = null, + modifier = Modifier + .constrainAs(image) { + start.linkTo(parent.start) + top.linkTo(text.top) + bottom.linkTo(text.bottom) + height = Dimension.fillToConstraints + } + .padding(top = 8.dp, start = 24.dp), + ) + + Image( + painter = painterResource(R.drawable.ic_close_24), + contentDescription = null, + modifier = Modifier + .size(16.dp) + .clickable(onClick = state.closeOnClick) + .constrainAs(close) { + top.linkTo(parent.top, margin = 16.dp) + end.linkTo(parent.end, margin = 16.dp) + }, + colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive), + ) } } }