Updated on 2026-08-14
This commit is contained in:
parent
2070f1d21c
commit
8ca7bc0ca7
15 changed files with 132 additions and 53 deletions
|
|
@ -231,12 +231,22 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
) == true
|
||||
}
|
||||
|
||||
override suspend fun storeTangemPayEligibility(eligibility: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY, value = eligibility)
|
||||
override suspend fun storeTangemPayEligibility(eligibility: Set<String>) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.store(
|
||||
key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY,
|
||||
value = eligibility,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTangemPayEligibility(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY, default = false)
|
||||
override suspend fun getTangemPayEligibility(): Set<String> {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY,
|
||||
default = emptySet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ interface TangemPayApi {
|
|||
@POST("v1/deeplink/validate")
|
||||
suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse<DeeplinkValidityResponse>
|
||||
|
||||
@GET("v1/customer/eligibility")
|
||||
suspend fun checkCustomerEligibility(): ApiResponse<CustomerEligibilityResponse>
|
||||
@GET("v1/eligibility/channels")
|
||||
suspend fun getEligibilityChannels(): ApiResponse<TangemPayEligibilityChannels>
|
||||
|
||||
@GET("v1/order/{order_id}")
|
||||
suspend fun getOrder(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerEligibilityResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
data class TangemPayEligibilityChannels(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "is_tangem_pay_available") val isTangemPayAvailable: Boolean,
|
||||
@Json(name = "channels") val channels: List<String>,
|
||||
)
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ object PreferencesKeys {
|
|||
val TANGEM_PAY_ACTIVE_WITHDRAW_ORDERS_KEY by lazy {
|
||||
stringPreferencesKey(name = "tangemPayActiveWithdrawOrdersKey")
|
||||
}
|
||||
val TANGEM_PAY_ELIGIBILITY_KEY by lazy { booleanPreferencesKey(name = "tangemPayEligibility") }
|
||||
val TANGEM_PAY_ELIGIBILITY_KEY by lazy { stringSetPreferencesKey(name = "tangemPayEligibilityList") }
|
||||
|
||||
fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key")
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ interface TangemPayStorage {
|
|||
suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean)
|
||||
suspend fun storeTangemPayEligibility(eligibility: Boolean)
|
||||
suspend fun getTangemPayEligibility(): Boolean
|
||||
suspend fun storeTangemPayEligibility(eligibility: Set<String>)
|
||||
suspend fun getTangemPayEligibility(): Set<String>
|
||||
|
||||
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@ package com.tangem.data.pay
|
|||
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.TangemPayEligibilityType
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -22,7 +24,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
private val onboardingRepository: OnboardingRepository,
|
||||
) : TangemPayEligibilityManager {
|
||||
|
||||
private var cachedEligibleWallets: List<UserWalletData>? = null
|
||||
private var cachedEligibleWallets: Map<TangemPayEntryPoint?, List<UserWalletData>> = emptyMap()
|
||||
private var eligibleWalletsDeferred: Deferred<List<UserWalletData>>? = null
|
||||
private val loadMutex = Mutex()
|
||||
|
||||
|
|
@ -30,43 +32,46 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
resetDataWhenWalletsUpdate()
|
||||
}
|
||||
|
||||
override suspend fun getEligibleWallets(shouldExcludePaeraCustomers: Boolean): List<UserWallet> {
|
||||
return getUserWalletsData().mapNotNull {
|
||||
override suspend fun getEligibleWallets(
|
||||
shouldExcludePaeraCustomers: Boolean,
|
||||
entryPoint: TangemPayEntryPoint?,
|
||||
): List<UserWallet> {
|
||||
return getUserWalletsData(entryPoint = entryPoint).mapNotNull {
|
||||
if (!it.isPaeraCustomer || !shouldExcludePaeraCustomers) it.userWallet else null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPossibleWalletsIds(shouldExcludePaeraCustomers: Boolean): List<UserWalletId> {
|
||||
return getPossibleWalletsForTangemPay().addPaeraCustomersData().mapNotNull {
|
||||
return getPossibleWalletsForTangemPay(entryPoint = null).addPaeraCustomersData().mapNotNull {
|
||||
if (!it.isPaeraCustomer || !shouldExcludePaeraCustomers) it.userWallet.walletId else null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTangemPayAvailability(): Boolean {
|
||||
return onboardingRepository.checkCustomerEligibility()
|
||||
override suspend fun getTangemPayAvailability(entryPoint: TangemPayEntryPoint): Boolean {
|
||||
val eligibility = onboardingRepository.getCustomerEligibility().ifEmpty {
|
||||
onboardingRepository.checkCustomerEligibility()
|
||||
}
|
||||
val type = entryPoint.toEligibilityType()
|
||||
return eligibility.any { it == type }
|
||||
.also { isEligible -> if (!isEligible) reset() }
|
||||
}
|
||||
|
||||
override suspend fun isPaeraCustomerForAnyWallet(): Boolean {
|
||||
return getUserWalletsData().any { it.isPaeraCustomer }
|
||||
override suspend fun isPaeraCustomerForAnyWallet(entryPoint: TangemPayEntryPoint): Boolean {
|
||||
return getUserWalletsData(entryPoint = entryPoint).any { it.isPaeraCustomer }
|
||||
}
|
||||
|
||||
private suspend fun getUserWalletsData(): List<UserWalletData> {
|
||||
cachedEligibleWallets?.let { return it }
|
||||
private suspend fun getUserWalletsData(entryPoint: TangemPayEntryPoint?): List<UserWalletData> {
|
||||
cachedEligibleWallets[entryPoint]?.let { return it }
|
||||
|
||||
return loadMutex.withLock {
|
||||
cachedEligibleWallets?.let { return it }
|
||||
cachedEligibleWallets[entryPoint]?.let { return it }
|
||||
eligibleWalletsDeferred?.let { return it.await() }
|
||||
|
||||
coroutineScope {
|
||||
val deferred = async {
|
||||
if (!checkTangemPayEligibility()) {
|
||||
emptyList()
|
||||
} else {
|
||||
getPossibleWalletsForTangemPay()
|
||||
.addPaeraCustomersData()
|
||||
.also { cachedEligibleWallets = it }
|
||||
}
|
||||
getPossibleWalletsForTangemPay(entryPoint = entryPoint)
|
||||
.addPaeraCustomersData()
|
||||
.also { cachedEligibleWallets += mapOf(entryPoint to it) }
|
||||
}
|
||||
eligibleWalletsDeferred = deferred
|
||||
try {
|
||||
|
|
@ -78,8 +83,8 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getPossibleWalletsForTangemPay(): List<UserWallet> {
|
||||
if (!checkTangemPayEligibility()) {
|
||||
private suspend fun getPossibleWalletsForTangemPay(entryPoint: TangemPayEntryPoint?): List<UserWallet> {
|
||||
if (!checkTangemPayEligibility(entryPoint = entryPoint)) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
|
|
@ -122,13 +127,25 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
|||
}
|
||||
|
||||
override fun reset() {
|
||||
cachedEligibleWallets = null
|
||||
cachedEligibleWallets = emptyMap()
|
||||
eligibleWalletsDeferred?.cancel()
|
||||
eligibleWalletsDeferred = null
|
||||
}
|
||||
|
||||
private suspend fun checkTangemPayEligibility(): Boolean {
|
||||
return onboardingRepository.getCustomerEligibility() || onboardingRepository.checkCustomerEligibility()
|
||||
private suspend fun checkTangemPayEligibility(entryPoint: TangemPayEntryPoint?): Boolean {
|
||||
val eligibility = onboardingRepository.getCustomerEligibility().ifEmpty {
|
||||
onboardingRepository.checkCustomerEligibility()
|
||||
}
|
||||
return if (entryPoint == null) {
|
||||
eligibility.isNotEmpty()
|
||||
} else {
|
||||
eligibility.any { it == entryPoint.toEligibilityType() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TangemPayEntryPoint.toEligibilityType(): TangemPayEligibilityType = when (this) {
|
||||
TangemPayEntryPoint.BANNER -> TangemPayEligibilityType.BANNER
|
||||
TangemPayEntryPoint.DETAILS -> TangemPayEligibilityType.DETAILS
|
||||
}
|
||||
|
||||
private data class UserWalletData(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,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.TangemPayEligibilityType
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -207,19 +208,20 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun checkCustomerEligibility(): Boolean {
|
||||
override suspend fun checkCustomerEligibility(): List<TangemPayEligibilityType> {
|
||||
val response = requestHelper.performWithStaticToken {
|
||||
tangemPayApi.checkCustomerEligibility()
|
||||
tangemPayApi.getEligibilityChannels()
|
||||
}.getOrNull()
|
||||
|
||||
val isAvailable = response?.result?.isTangemPayAvailable == true
|
||||
tangemPayStorage.storeTangemPayEligibility(eligibility = isAvailable)
|
||||
val channels = response?.result?.channels
|
||||
val eligibility = channels.orEmpty().toSet()
|
||||
tangemPayStorage.storeTangemPayEligibility(eligibility = eligibility)
|
||||
|
||||
return isAvailable
|
||||
return eligibility.map(TangemPayEligibilityType::fromString)
|
||||
}
|
||||
|
||||
override suspend fun getCustomerEligibility(): Boolean {
|
||||
return tangemPayStorage.getTangemPayEligibility()
|
||||
override suspend fun getCustomerEligibility(): List<TangemPayEligibilityType> {
|
||||
return tangemPayStorage.getTangemPayEligibility().map(TangemPayEligibilityType::fromString)
|
||||
}
|
||||
|
||||
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.models
|
||||
|
||||
enum class TangemPayEligibilityType {
|
||||
|
||||
BANNER,
|
||||
DETAILS,
|
||||
UNKNOWN,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String): TangemPayEligibilityType = when (value.lowercase()) {
|
||||
"banner" -> BANNER
|
||||
"details" -> DETAILS
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,14 @@ package com.tangem.domain.pay
|
|||
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
|
||||
interface TangemPayEligibilityManager {
|
||||
|
||||
suspend fun getEligibleWallets(shouldExcludePaeraCustomers: Boolean): List<UserWallet>
|
||||
suspend fun getEligibleWallets(
|
||||
shouldExcludePaeraCustomers: Boolean,
|
||||
entryPoint: TangemPayEntryPoint?,
|
||||
): List<UserWallet>
|
||||
|
||||
/**
|
||||
* Returns all compatible user wallets without checking Tangem Pay eligibility, only used when opening deeplink
|
||||
|
|
@ -13,9 +17,9 @@ interface TangemPayEligibilityManager {
|
|||
* */
|
||||
suspend fun getPossibleWalletsIds(shouldExcludePaeraCustomers: Boolean): List<UserWalletId>
|
||||
|
||||
suspend fun getTangemPayAvailability(): Boolean
|
||||
suspend fun getTangemPayAvailability(entryPoint: TangemPayEntryPoint): Boolean
|
||||
|
||||
suspend fun isPaeraCustomerForAnyWallet(): Boolean
|
||||
suspend fun isPaeraCustomerForAnyWallet(entryPoint: TangemPayEntryPoint): Boolean
|
||||
|
||||
fun reset()
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
enum class TangemPayEntryPoint {
|
||||
BANNER,
|
||||
DETAILS,
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.TangemPayEligibilityType
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
|
@ -25,8 +26,8 @@ interface OnboardingRepository {
|
|||
|
||||
suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
|
||||
suspend fun checkCustomerEligibility(): Boolean
|
||||
suspend fun getCustomerEligibility(): Boolean
|
||||
suspend fun checkCustomerEligibility(): List<TangemPayEligibilityType>
|
||||
suspend fun getCustomerEligibility(): List<TangemPayEligibilityType>
|
||||
|
||||
fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo?
|
||||
|
||||
|
|
|
|||
|
|
@ -69,12 +69,16 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
|||
}
|
||||
|
||||
private suspend fun showOnboardingBannerIfEligible(userWalletId: UserWalletId) {
|
||||
if (eligibilityManager.isPaeraCustomerForAnyWallet()) {
|
||||
val tangemPayEntryPoint = TangemPayEntryPoint.BANNER
|
||||
if (eligibilityManager.isPaeraCustomerForAnyWallet(tangemPayEntryPoint)) {
|
||||
updateState(userWalletId, MainCustomerInfoContentState.Empty.right())
|
||||
return
|
||||
}
|
||||
val isEligible = eligibilityManager
|
||||
.getEligibleWallets(shouldExcludePaeraCustomers = false)
|
||||
.getEligibleWallets(
|
||||
shouldExcludePaeraCustomers = false,
|
||||
entryPoint = tangemPayEntryPoint,
|
||||
)
|
||||
.any { it.walletId == userWalletId }
|
||||
if (isEligible) {
|
||||
if (onboardingRepository.getHideMainOnboardingBanner(userWalletId)) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.domain.feedback.models.WalletMetaInfo
|
|||
import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
|
||||
|
|
@ -255,7 +256,10 @@ internal class DetailsModel @Inject constructor(
|
|||
private fun addTangemPayItemIfEligible() {
|
||||
modelScope.launch {
|
||||
val isEligible = tangemPayEligibilityManager
|
||||
.getEligibleWallets(shouldExcludePaeraCustomers = true)
|
||||
.getEligibleWallets(
|
||||
shouldExcludePaeraCustomers = true,
|
||||
entryPoint = TangemPayEntryPoint.DETAILS,
|
||||
)
|
||||
.isNotEmpty()
|
||||
if (isEligible) {
|
||||
items.update { itemsBuilder.addTangemPayItem(items = it, onClick = ::onTangemPayItemClicked) }
|
||||
|
|
@ -266,7 +270,7 @@ internal class DetailsModel @Inject constructor(
|
|||
private fun onTangemPayItemClicked() {
|
||||
modelScope.launch {
|
||||
analyticsEventHandler.send(TangemPayAnalyticsEvents.DetailsVisaPermanentButtonClicked())
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability()
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability(TangemPayEntryPoint.DETAILS)
|
||||
if (isEligible) {
|
||||
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ 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.TangemPayEntryPoint
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
|
|
@ -127,7 +128,10 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
val shouldExcludePaeraCustomers = params is TangemPayOnboardingComponent.Params.FromBannerInSettings
|
||||
modelScope.launch {
|
||||
val eligibleWalletsIds = eligibilityManager
|
||||
.getEligibleWallets(shouldExcludePaeraCustomers = shouldExcludePaeraCustomers)
|
||||
.getEligibleWallets(
|
||||
shouldExcludePaeraCustomers = shouldExcludePaeraCustomers,
|
||||
entryPoint = params.toEntryPointOrNull(),
|
||||
)
|
||||
.map { it.walletId }
|
||||
if (eligibleWalletsIds.isEmpty()) {
|
||||
back()
|
||||
|
|
@ -204,4 +208,13 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
override fun onWalletSelectorDismiss() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
||||
private fun TangemPayOnboardingComponent.Params.toEntryPointOrNull() = when (this) {
|
||||
is TangemPayOnboardingComponent.Params.FromBannerInSettings -> TangemPayEntryPoint.DETAILS
|
||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain -> TangemPayEntryPoint.BANNER
|
||||
|
||||
is TangemPayOnboardingComponent.Params.Deeplink,
|
||||
is TangemPayOnboardingComponent.Params.ContinueOnboarding,
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
|
|
@ -246,7 +247,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
override fun onOnboardingBannerClick(userWalletId: UserWalletId) {
|
||||
modelScope.launch {
|
||||
analyticsEventHandler.send(TangemPayAnalyticsEvents.MainVisaPermanentBannerClicked())
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability()
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability(TangemPayEntryPoint.BANNER)
|
||||
if (isEligible) {
|
||||
router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain)
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue