Updated on 2026-08-14
This commit is contained in:
commit
47dc1c38df
762 changed files with 23960 additions and 10924 deletions
|
|
@ -2,14 +2,16 @@ 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 com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -17,57 +19,59 @@ import kotlinx.coroutines.sync.withLock
|
|||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultTangemPayEligibilityManager @Inject constructor(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val coroutineScope: AppCoroutineScope,
|
||||
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()
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
||||
init {
|
||||
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 {
|
||||
|
|
@ -79,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 +126,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(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
|||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
|
|
@ -38,8 +39,6 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -112,6 +111,7 @@ internal interface TangemPayDataModule {
|
|||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
scope: AppCoroutineScope,
|
||||
): PaymentAccountStatusesStore {
|
||||
return PaymentAccountStatusesStore(
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
|
|
@ -122,9 +122,9 @@ internal interface TangemPayDataModule {
|
|||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "payment_account_statuses") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
scope = scope,
|
||||
),
|
||||
dispatchers = dispatchers,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.api.pay.models.response.OrderResponse
|
|||
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
|
||||
|
|
@ -211,19 +212,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 {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ 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.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
|
|
@ -45,9 +46,9 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
private val storage: TangemPayStorage,
|
||||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
private val errorConverter: TangemPayErrorConverter,
|
||||
private val pollingScope: AppCoroutineScope,
|
||||
) : TangemPayCardDetailsRepository {
|
||||
|
||||
private val pollingScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val pollingJobs = mutableMapOf<String, Job>()
|
||||
private val storePollingMutex = Mutex()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,9 @@ import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
|
|||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.extensions.addHexPrefix
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -43,6 +41,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
|
||||
private const val TAG = "TangemPaySwapRepository"
|
||||
private const val MAX_POLLING_ATTEMPTS = 6
|
||||
|
||||
private data class PollingKey(val userWalletId: String, val orderId: String)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -54,9 +53,9 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
|
|||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val orderRepository: CustomerOrderRepository,
|
||||
private val withdrawPollingScope: AppCoroutineScope,
|
||||
) : TangemPayWithdrawRepository {
|
||||
|
||||
private val pollingScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private val pollingJobs = mutableMapOf<PollingKey, Job>()
|
||||
private val pollingMutex = Mutex()
|
||||
|
||||
|
|
@ -139,7 +138,7 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
|
|||
orderId: String,
|
||||
exchangeData: TangemPayWithdrawExchangeState,
|
||||
) {
|
||||
pollingScope.launch {
|
||||
withdrawPollingScope.launch {
|
||||
startWithdrawOrderPolling(userWallet, orderId, exchangeData)
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +153,7 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
|
|||
pollingMutex.withLock {
|
||||
if (pollingJobs.containsKey(key)) return@withLock
|
||||
|
||||
val pollingJob = pollingScope.launch {
|
||||
val pollingJob = withdrawPollingScope.launch {
|
||||
try {
|
||||
var attemptCount = 0
|
||||
while (isActive && attemptCount < MAX_POLLING_ATTEMPTS) {
|
||||
|
|
@ -194,7 +193,7 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
|
|||
}
|
||||
|
||||
private fun stopPolling(userWalletId: String, orderId: String) {
|
||||
pollingScope.launch {
|
||||
withdrawPollingScope.launch {
|
||||
pollingMutex.withLock {
|
||||
val key = PollingKey(userWalletId, orderId)
|
||||
pollingJobs[key]?.cancel()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@ import androidx.datastore.core.DataStore
|
|||
import com.tangem.data.pay.converter.PaymentAccountStatusDMConverter
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusDM
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.PaymentAccountStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
|
@ -28,11 +26,9 @@ internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatu
|
|||
internal class PaymentAccountStatusesStore(
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithPaymentStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithPaymentStatusDM>,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
private val scope: AppCoroutineScope,
|
||||
) {
|
||||
|
||||
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue