Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-13 19:00:03 +03:00
parent 2a8e79ed67
commit 012792e586
7 changed files with 248 additions and 49 deletions

View file

@ -46,7 +46,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either<Throwable, Unit> =
Either.catchOn(dispatchers.default) {
val account = Account.Payment(userWalletId = params.userWalletId)
logger.i("fetch: ${params.userWalletId.stringValue}")
logger.i("invoke() start: ${params.userWalletId.stringValue}")
if (deviceSecurity.isSecurityExposed()) {
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
@ -80,11 +80,14 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
userWalletId = params.userWalletId,
status = AccountStatus.Payment(account = account, value = status),
)
}.onLeft {
}.onLeft { throwable ->
logger.e("invoke() ${params.userWalletId} threw, falling back to ONLY_CACHE", throwable)
paymentAccountStatusesStore.updateStatusSource(
userWalletId = params.userWalletId,
source = StatusSource.ONLY_CACHE,
)
}.also { result ->
logger.i("invoke() end ${params.userWalletId}: isRight=${result.isRight()}")
}
private suspend fun proceedHasTangemPayResult(
@ -101,7 +104,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private suspend fun fetchTangemPayAccountStatus(account: Account.Payment): PaymentAccountStatusValue {
val prevResult = paymentAccountStatusesStore.getSyncOrNull(account.userWalletId)
logger.i(
"fetchTangemPayAccountStatus ${account.userWalletId}: " +
"prevResultType=${prevResult?.value?.let { it::class.simpleName } ?: "null"}",
)
if (prevResult == null || prevResult.value is PaymentAccountStatusValue.Error.Unavailable) {
logger.i("fetchTangemPayAccountStatus ${account.userWalletId}: writing Loading placeholder to store")
paymentAccountStatusesStore.store(
userWalletId = account.userWalletId,
status = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Loading),
@ -112,10 +120,13 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
}
private suspend fun proceedWithOrderId(account: Account.Payment): PaymentAccountStatusValue {
return if (!onboardingRepository.isTangemPayInitialDataProduced(account.userWalletId)) {
val isInitial = onboardingRepository.isTangemPayInitialDataProduced(account.userWalletId)
logger.i("proceedWithOrderId ${account.userWalletId}: isTangemPayInitialDataProduced=$isInitial")
return if (!isInitial) {
PaymentAccountStatusValue.Error.NotSynced
} else {
val orderId = onboardingRepository.getOrderId(account.userWalletId)
logger.i("proceedWithOrderId ${account.userWalletId}: orderIdPresent=${orderId != null}")
if (orderId != null) {
proceedWithOrderId(account = account, orderId = orderId)
} else {

View file

@ -9,12 +9,17 @@ import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
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.onEach
import kotlinx.coroutines.flow.onEmpty
import kotlinx.coroutines.flow.onStart
private const val TAG = "PaymentAccountStatusProducer"
internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor(
@Assisted private val params: PaymentAccountStatusProducer.Params,
@ -24,13 +29,20 @@ internal class DefaultPaymentAccountStatusProducer @AssistedInject constructor(
) : PaymentAccountStatusProducer {
private val account = Account.Payment(userWalletId = params.userWalletId)
private val logger = TangemLogger.withTag(TAG)
override val fallback: Option<AccountStatus.Payment>
get() = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Error.Unavailable).some()
override fun produce(): Flow<AccountStatus.Payment> {
logger.i("[${params.userWalletId}] produce() called")
return paymentAccountStatusesStore.get(userWalletId = params.userWalletId)
.onEmpty { emit(value = AccountStatus.Payment(account, PaymentAccountStatusValue.NotCreated)) }
.onStart { logger.i("[${params.userWalletId}] flow subscribed to store") }
.onEach { logger.i("[${params.userWalletId}] flow emits statusType=${it.value::class.simpleName}") }
.onEmpty {
logger.i("[${params.userWalletId}] onEmpty triggered: emitting NotCreated fallback")
emit(value = AccountStatus.Payment(account, PaymentAccountStatusValue.NotCreated))
}
.flowOn(dispatchers.default)
}

View file

@ -15,11 +15,15 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
internal typealias WalletIdWithPaymentStatus = Map<String, AccountStatus.Payment>
internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatusValueDM>
private const val TAG = "PaymentAccountStatusesStore"
/**
* Store for payment account statuses with dual storage (runtime + persistence).
*
@ -32,10 +36,18 @@ internal class PaymentAccountStatusesStore(
scope: AppCoroutineScope,
) {
private val logger = TangemLogger.withTag(TAG)
init {
scope.launch {
logger.i("init: loading cached payment statuses from persistence")
try {
val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
val cachedStatuses = persistenceDataStore.data.firstOrNull()
if (cachedStatuses == null) {
logger.i("init: persistence empty (firstOrNull == null), runtimeStore stays empty")
return@launch
}
logger.i("init: loaded ${cachedStatuses.size} cached entries; populating runtimeStore")
runtimeStore.store(
value = cachedStatuses.mapValues { (rawUserWalletId, statusDM) ->
val account = Account.Payment(userWalletId = UserWalletId(rawUserWalletId))
@ -43,18 +55,32 @@ internal class PaymentAccountStatusesStore(
AccountStatus.Payment(account = account, value = statusValue)
},
)
logger.i("init: runtimeStore populated with ${cachedStatuses.size} entries")
} catch (e: Exception) {
TangemLogger.e("Error while loading cached payment account statuses", e)
logger.e("Error while loading cached payment account statuses", e)
}
}
}
fun get(userWalletId: UserWalletId): Flow<AccountStatus.Payment> {
return runtimeStore.get().mapNotNull { it[userWalletId.stringValue] }
return runtimeStore.get()
.onStart { logger.i("get($userWalletId): subscribed to runtimeStore") }
.onEach { map ->
logger.i(
"get($userWalletId): runtimeStore emitted map size=${map.size}, " +
"hasEntry=${map.containsKey(userWalletId.stringValue)}",
)
}
.mapNotNull { it[userWalletId.stringValue] }
.onEach { status ->
logger.i("get($userWalletId): emitting statusType=${status.value::class.simpleName}")
}
}
suspend fun getSyncOrNull(userWalletId: UserWalletId): AccountStatus.Payment? {
return runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
val result = runtimeStore.getSyncOrNull()?.get(userWalletId.stringValue)
logger.i("getSyncOrNull($userWalletId) valueType=${result?.value?.let { it::class.simpleName } ?: "null"}")
return result
}
suspend fun updateStatusSource(userWalletId: UserWalletId, source: StatusSource) {
@ -68,6 +94,7 @@ internal class PaymentAccountStatusesStore(
}
suspend fun store(userWalletId: UserWalletId, status: AccountStatus.Payment) {
logger.i("store($userWalletId): valueType=${status.value::class.simpleName}")
coroutineScope {
launch { storeInRuntime(userWalletId = userWalletId, status = status) }
launch { storeInPersistence(userWalletId = userWalletId, status = status.value) }