Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-20 21:54:46 +04:00
parent 8b75e37e30
commit a9bca131c3
386 changed files with 1369 additions and 1347 deletions

View file

@ -54,7 +54,6 @@ dependencies {
implementation(deps.arrow.core)
implementation(deps.arrow.fx)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.androidx.paging.runtime)
implementation(deps.moshi.kotlin)
ksp(deps.moshi.kotlin.codegen)

View file

@ -11,7 +11,7 @@ import com.tangem.data.pay.util.TangemPayErrorConverter
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
import timber.log.Timber
import com.tangem.utils.logging.TangemLogger
import javax.inject.Inject
private const val TAG = "TangemPay: DefaultTangemPayCryptoCurrencyFactory"
@ -53,7 +53,7 @@ internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
decimals = TOKEN_DECIMALS,
)
}.mapLeft { exception ->
Timber.tag(TAG).e(exception)
TangemLogger.withTag(TAG).e("Error", exception)
errorConverter.convert(exception)
}
}

View file

@ -16,7 +16,7 @@ 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 com.tangem.utils.logging.TangemLogger
import javax.inject.Inject
private const val TAG = "PaymentAccountStatusFetcher"
@ -31,12 +31,14 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
override suspend fun invoke(params: PaymentAccountStatusFetcher.Params): Either<Throwable, Unit> =
eitherOn(dispatchers.default) {
Timber.tag(TAG).i("fetch: ${params.userWalletId.stringValue}")
TangemLogger.withTag(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}")
TangemLogger.withTag(TAG).i("fetch security info: rooted: ${deviceSecurity.isRooted}")
TangemLogger.withTag(TAG).i("fetch security info: xposed: ${deviceSecurity.isXposed}")
TangemLogger.withTag(
TAG,
).i("fetch security info: bootloader unlocked: ${deviceSecurity.isBootloaderUnlocked}")
return@eitherOn paymentAccountStatusesStore.store(
userWalletId = params.userWalletId,
@ -47,7 +49,9 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
val status = onboardingRepository.hasTangemPayInWallet(userWalletId = params.userWalletId)
.fold(
ifLeft = { error ->
Timber.tag(TAG).e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}")
TangemLogger.withTag(
TAG,
).e("Failed check wallet ${params.userWalletId}: ${error.javaClass.simpleName}")
when (error) {
is VisaApiError.NotPaeraCustomer -> PaymentAccountStatus.NotCreated
else -> PaymentAccountStatus.Error.Unavailable(source = StatusSource.ACTUAL)
@ -57,7 +61,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
proceedHasTangemPayResult(userWalletId = params.userWalletId, hasTangemPay = hasTangemPay)
},
)
Timber.tag(TAG).i("invoke status ${params.userWalletId}: $status")
TangemLogger.withTag(TAG).i("invoke status ${params.userWalletId}: $status")
paymentAccountStatusesStore.store(userWalletId = params.userWalletId, status = status)
}
@ -65,7 +69,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
userWalletId: UserWalletId,
hasTangemPay: Boolean,
): PaymentAccountStatus {
Timber.tag(TAG).i("proceedHasTangemPayResult for $userWalletId hasTangemPay: $hasTangemPay")
TangemLogger.withTag(TAG).i("proceedHasTangemPayResult for $userWalletId hasTangemPay: $hasTangemPay")
return if (hasTangemPay) {
fetchTangemPayAccountStatus(userWalletId = userWalletId)
} else {
@ -98,15 +102,15 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
private suspend fun proceedWithoutOrder(userWalletId: UserWalletId): PaymentAccountStatus {
return onboardingRepository.getCustomerInfo(userWalletId).fold(
ifLeft = { error ->
Timber.tag(TAG).e("proceedWithoutOrder $userWalletId error: $error")
TangemLogger.withTag(TAG).e("proceedWithoutOrder $userWalletId error: $error")
error.mapToPaymentAccountStatus()
},
ifRight = { customerInfo ->
Timber.tag(TAG).i("proceedWithoutOrder data customerInfo $userWalletId")
TangemLogger.withTag(TAG).i("proceedWithoutOrder data customerInfo $userWalletId")
val status = customerInfo.mapToPaymentAccountStatus()
if (customerInfo.productInstance == null) {
onboardingRepository.createOrder(userWalletId)
.onLeft { Timber.tag(TAG).e("createOrder failed: $it") }
.onLeft { TangemLogger.withTag(TAG).e("createOrder failed: $it") }
}
status
},
@ -116,11 +120,11 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
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")
TangemLogger.withTag(TAG).e("proceedWithOrderId $userWalletId orderId: $orderId error: $error")
error.mapToPaymentAccountStatus()
},
ifRight = { orderData ->
Timber.tag(TAG).i("proceedWithOrderId $userWalletId: $orderId status: ${orderData.status}")
TangemLogger.withTag(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,

View file

@ -19,18 +19,21 @@ 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
import com.tangem.domain.pay.model.TangemPayCardDetails
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import kotlinx.coroutines.*
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@ -296,7 +299,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
cardFrozenStateStore.store(cardId, finalState)
}
}.onLeft { error ->
Timber.e("error ${error.errorCode}")
TangemLogger.e("error ${error.errorCode}")
// stop retrying after 3 errors
if (retryCount > MAX_POLLING_RETRIES) {
pollingJobs.remove(key = orderId)
@ -305,7 +308,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
retryCount++
}
} catch (e: Exception) {
Timber.e(e)
TangemLogger.e("Error", e)
storePollingMutex.withLock {
pollingJobs.remove(orderId)
}
@ -338,7 +341,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
}
private fun <T> catchException(throwable: Throwable): Either<UniversalError, T> {
Timber.tag(TAG).e(throwable)
TangemLogger.withTag(TAG).e("Error", throwable)
return errorConverter.convert(throwable).left()
}

View file

@ -24,13 +24,13 @@ 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 com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.Currency
@ -176,14 +176,14 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
}
}
if (attemptCount >= MAX_POLLING_ATTEMPTS) {
Timber.tag(TAG).e("Polling stopped after $attemptCount unsuccessful attempts")
TangemLogger.withTag(TAG).e("Polling stopped after $attemptCount unsuccessful attempts")
tangemPayStorage.deleteWithdrawOrder(userWalletId = userWallet.walletId, orderId = orderId)
pollingMutex.withLock { pollingJobs.remove(key) }
}
} catch (exception: CancellationException) {
throw exception
} catch (exception: Exception) {
Timber.tag(TAG).e(exception)
TangemLogger.withTag(TAG).e("Error", exception)
tangemPayStorage.deleteWithdrawOrder(userWalletId = userWallet.walletId, orderId = orderId)
pollingMutex.withLock { pollingJobs.remove(key) }
}
@ -240,7 +240,7 @@ internal class DefaultTangemPayWithdrawRepository @Inject constructor(
} catch (exception: CancellationException) {
throw exception
} catch (exception: Exception) {
Timber.tag(TAG).e(exception)
TangemLogger.withTag(TAG).e("Error", exception)
}
}
}

View file

@ -17,10 +17,10 @@ import com.tangem.domain.visa.error.VisaApiError
import com.tangem.domain.visa.model.TangemPayAuthTokens
import com.tangem.domain.visa.model.getAuthHeader
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@ -69,7 +69,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
}
},
catch = { errorConverter.convert(it).left() },
).onLeft { visaApiError -> Timber.tag(TAG).e(visaApiError.toString()) }
).onLeft { visaApiError -> TangemLogger.withTag(TAG).e(visaApiError.toString()) }
}
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String {
@ -129,7 +129,7 @@ internal class TangemPayRequestPerformer @Inject constructor(
idempotencyKey = UUID.randomUUID().toString(),
)
}.mapLeft { error ->
Timber.tag(TAG).e("Can not refresh auth tokens: $error")
TangemLogger.withTag(TAG).e("Can not refresh auth tokens: $error")
if (error is VisaApiError.ServerUnavailable) error else VisaApiError.RefreshTokenExpired
}.onRight { tokens ->
tangemPayStorage.storeAuthTokens(customerWalletAddress = customerWalletAddress, tokens = tokens)

View file

@ -4,15 +4,15 @@ 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.AppCoroutineScope
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import timber.log.Timber
internal typealias WalletIdWithPaymentStatus = Map<String, PaymentAccountStatus>
internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatusDM>
@ -39,7 +39,7 @@ internal class PaymentAccountStatusesStore(
},
)
} catch (e: Exception) {
Timber.e(e, "Error while loading cached payment account statuses")
TangemLogger.e("Error while loading cached payment account statuses", e)
}
}
}

View file

@ -7,9 +7,9 @@ import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.extensions.isZero
import com.tangem.utils.extensions.orZero
import com.tangem.utils.logging.TangemLogger
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import timber.log.Timber
import java.util.Currency
internal class TangemPayTxHistoryItemConverter(moshi: Moshi) :
@ -26,7 +26,7 @@ internal class TangemPayTxHistoryItemConverter(moshi: Moshi) :
?: value.fee?.let { convertFee(id = value.id, fee = it) }
?: value.collateral?.let { convertCollateral(id = value.id, collateral = it) }
?: run {
Timber.wtf("unknown type of transaction: $value")
TangemLogger.e("unknown type of transaction: $value")
null
}
}
@ -82,7 +82,7 @@ internal class TangemPayTxHistoryItemConverter(moshi: Moshi) :
collateral: TangemPayTxHistoryResponse.Collateral,
): TangemPayTxHistoryItem.Collateral? {
val date = collateral.postedAt ?: return run {
Timber.e("Collateral transaction postedAt is null: $collateral")
TangemLogger.e("Collateral transaction postedAt is null: $collateral")
return@run null
}
return TangemPayTxHistoryItem.Collateral(

View file

@ -7,10 +7,10 @@ import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import timber.log.Timber
internal class VisaTxHistoryPagingSource(
params: Params,
@ -56,7 +56,7 @@ internal class VisaTxHistoryPagingSource(
LoadResult.Page(items, prevOffset, nextOffset)
} catch (e: Throwable) {
Timber.e(e, "Unable to load the transaction history for the requested offset: $offsetToLoad")
TangemLogger.e("Unable to load the transaction history for the requested offset: $offsetToLoad", e)
LoadResult.Error(e)
}
}