Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-05 14:53:27 +05:00
parent e326873ac1
commit 16b8957165
10 changed files with 120 additions and 7 deletions

View file

@ -14,6 +14,7 @@ dependencies {
api(projects.core.pagination) api(projects.core.pagination)
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.error) implementation(projects.core.error)
implementation(projects.core.analytics.models)
/** Project - Domain */ /** Project - Domain */
api(projects.domain.models) api(projects.domain.models)

View file

@ -0,0 +1,50 @@
package com.tangem.domain.tangempay
import com.tangem.core.analytics.models.AnalyticsEvent
sealed class TangemPayAnalyticsEvents(
categoryName: String,
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = categoryName, event = event, params = params) {
data object ActivationScreenOpened : TangemPayAnalyticsEvents(
categoryName = "Visa Onboarding",
event = "Activation Screen Opened",
)
data object ViewTermsClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Onboarding",
event = "Button - Visa View Terms",
)
data object GetCardClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Onboarding",
event = "Button - Visa Get Card",
)
data object KycFlowOpened : TangemPayAnalyticsEvents(
categoryName = "Visa Onboarding",
event = "Visa KYC Flow Opened",
)
data object IssuingBannerDisplayed : TangemPayAnalyticsEvents(
categoryName = "Visa Onboarding",
event = "Visa Issuing Banner Displayed",
)
data object MainScreenOpened : TangemPayAnalyticsEvents(
categoryName = "Visa Screen",
event = "Visa Main Screen Opened",
)
data object ReceiveFundsClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Screen",
event = "Button - Visa Receive",
)
data object SwapClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Screen",
event = "Button - Visa Swap",
)
}

View file

@ -1,11 +1,13 @@
package com.tangem.features.kyc package com.tangem.features.kyc
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.pay.KycStartInfo import com.tangem.domain.pay.KycStartInfo
import com.tangem.domain.pay.repository.KycRepository import com.tangem.domain.pay.repository.KycRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -17,6 +19,7 @@ import javax.inject.Inject
@ModelScoped @ModelScoped
class DefaultKycModel @Inject constructor( class DefaultKycModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
analytics: AnalyticsEventHandler,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val kycRepository: KycRepository, private val kycRepository: KycRepository,
) : Model() { ) : Model() {
@ -27,6 +30,7 @@ class DefaultKycModel @Inject constructor(
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
init { init {
analytics.send(TangemPayAnalyticsEvents.KycFlowOpened)
modelScope.launch { modelScope.launch {
try { try {
kycRepository.getKycStartInfo(params.userWalletId).getOrNull()?.let { _uiState.emit(it) } kycRepository.getKycStartInfo(params.userWalletId).getOrNull()?.let { _uiState.emit(it) }

View file

@ -13,12 +13,13 @@ android {
dependencies { dependencies {
/** Core */ /** Core */
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.configToggles) implementation(projects.core.configToggles)
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.error) implementation(projects.core.error)
implementation(projects.core.navigation) implementation(projects.core.navigation)
implementation(projects.core.ui) implementation(projects.core.ui)
implementation(projects.core.analytics.models)
/** Common */ /** Common */
implementation(projects.common.ui) implementation(projects.common.ui)
@ -26,8 +27,8 @@ dependencies {
/** Features api */ /** Features api */
implementation(projects.features.tangempay.details.api) implementation(projects.features.tangempay.details.api)
implementation(projects.features.txhistory.api)
implementation(projects.features.tokenRecieve.api) implementation(projects.features.tokenRecieve.api)
implementation(projects.features.txhistory.api)
/** Domain */ /** Domain */
implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding)

View file

@ -5,6 +5,7 @@ import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
@ -22,6 +23,7 @@ import com.tangem.domain.pay.TangemPaySwapDataFactory
import com.tangem.domain.pay.model.TangemPayCardBalance import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.CustomerOrderRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.features.tangempay.TangemPayConstants import com.tangem.features.tangempay.TangemPayConstants
@ -59,6 +61,7 @@ import javax.inject.Inject
internal class TangemPayDetailsModel @Inject constructor( internal class TangemPayDetailsModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
private val router: Router, private val router: Router,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
private val cardDetailsRepository: TangemPayCardDetailsRepository, private val cardDetailsRepository: TangemPayCardDetailsRepository,
@ -328,6 +331,7 @@ internal class TangemPayDetailsModel @Inject constructor(
} }
override fun onClickSwap(data: TangemPayTopUpData) { override fun onClickSwap(data: TangemPayTopUpData) {
analytics.send(TangemPayAnalyticsEvents.SwapClicked)
bottomSheetNavigation.dismiss() bottomSheetNavigation.dismiss()
router.push( router.push(
AppRoute.Swap( AppRoute.Swap(
@ -346,6 +350,7 @@ internal class TangemPayDetailsModel @Inject constructor(
} }
override fun onClickReceive(data: TangemPayTopUpData) { override fun onClickReceive(data: TangemPayTopUpData) {
analytics.send(TangemPayAnalyticsEvents.ReceiveFundsClicked)
bottomSheetNavigation.dismiss() bottomSheetNavigation.dismiss()
val config = TokenReceiveConfig( val config = TokenReceiveConfig(
shouldShowWarning = false, shouldShowWarning = false,

View file

@ -13,12 +13,11 @@ android {
dependencies { dependencies {
/** Core */ /** Core */
implementation(projects.core.analytics)
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.core.error) implementation(projects.core.error)
implementation(projects.core.navigation) implementation(projects.core.navigation)
implementation(deps.arrow.core) implementation(projects.core.ui)
implementation(deps.arrow.core)
/** Common */ /** Common */
implementation(projects.common.routing) implementation(projects.common.routing)
@ -47,4 +46,5 @@ dependencies {
/** Other */ /** Other */
implementation(deps.timber) implementation(deps.timber)
implementation(deps.arrow.core)
} }

View file

@ -2,6 +2,7 @@ package com.tangem.features.tangempay.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -10,6 +11,7 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.data.pay.util.TangemPayWalletsManager import com.tangem.data.pay.util.TangemPayWalletsManager
import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayConstants import com.tangem.features.tangempay.TangemPayConstants
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer
@ -28,8 +30,9 @@ import com.tangem.utils.transformer.update as transformerUpdate
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal class TangemPayOnboardingModel @Inject constructor( internal class TangemPayOnboardingModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val analytics: AnalyticsEventHandler,
private val router: Router,
private val repository: OnboardingRepository, private val repository: OnboardingRepository,
private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase, private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
@ -41,6 +44,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
field = MutableStateFlow(getInitialState()) field = MutableStateFlow(getInitialState())
init { init {
analytics.send(TangemPayAnalyticsEvents.ActivationScreenOpened)
modelScope.launch { modelScope.launch {
when (params) { when (params) {
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> { is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
@ -89,10 +93,12 @@ internal class TangemPayOnboardingModel @Inject constructor(
} }
private fun onTermsClick() { private fun onTermsClick() {
analytics.send(TangemPayAnalyticsEvents.ViewTermsClicked)
urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK) urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK)
} }
private fun onGetCardClick() { private fun onGetCardClick() {
analytics.send(TangemPayAnalyticsEvents.GetCardClicked)
uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true)) uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true))
modelScope.launch { modelScope.launch {
// TODO implement selector // TODO implement selector

View file

@ -0,0 +1,38 @@
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import javax.inject.Inject
@ModelScoped
internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val screenLifecycleProvider: ScreenLifecycleProvider,
) {
private val sentEvents = mutableSetOf<TangemPayAnalyticsEvents>()
fun send(customerInfo: MainScreenCustomerInfo) {
if (screenLifecycleProvider.isBackgroundState.value) return
val cardInfo = customerInfo.info.cardInfo
val productInstance = customerInfo.info.productInstance
// TODO: TangemPay refactor analytics
// when statement copied from TangemPayUpdateInfoStateTransformer. Be careful when editing
val event = when {
customerInfo.orderStatus == OrderStatus.CANCELED -> return // ignore cancelled state on analytics
!customerInfo.info.isKycApproved -> return // ignore kyc not approved state on analytics
cardInfo != null && productInstance != null -> TangemPayAnalyticsEvents.MainScreenOpened
else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed
}
if (sentEvents.add(event)) {
analyticsEventHandler.send(event)
}
}
}

View file

@ -46,6 +46,8 @@ internal class TangemPayUpdateInfoStateTransformer(
private fun createInitialState(): TangemPayState { private fun createInitialState(): TangemPayState {
val cardInfo = value?.info?.cardInfo val cardInfo = value?.info?.cardInfo
val productInstance = value?.info?.productInstance val productInstance = value?.info?.productInstance
// when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing.
return when { return when {
value == null -> TangemPayState.Empty value == null -> TangemPayState.Empty
value.orderStatus == OrderStatus.CANCELED -> createCancelledState(onIssuingFailed) value.orderStatus == OrderStatus.CANCELED -> createCancelledState(onIssuingFailed)

View file

@ -9,6 +9,7 @@ import com.tangem.domain.visa.model.TangemPayCardFrozenState
import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHiddenStateTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHiddenStateTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer
@ -21,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import timber.log.Timber import timber.log.Timber
@Suppress("LongParameterList")
internal class TangemPayMainSubscriber @AssistedInject constructor( internal class TangemPayMainSubscriber @AssistedInject constructor(
@Assisted private val userWallet: UserWallet, @Assisted private val userWallet: UserWallet,
private val stateController: WalletStateController, private val stateController: WalletStateController,
@ -28,6 +30,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
private val innerWalletRouter: InnerWalletRouter, private val innerWalletRouter: InnerWalletRouter,
private val cardDetailsRepository: TangemPayCardDetailsRepository, private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val tangemPayMainInfoManager: TangemPayMainInfoManager, private val tangemPayMainInfoManager: TangemPayMainInfoManager,
private val analytics: WalletTangemPayAnalyticsEventSender,
) : WalletSubscriber() { ) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> { override fun create(coroutineScope: CoroutineScope): Flow<*> {
@ -63,7 +66,10 @@ internal class TangemPayMainSubscriber @AssistedInject constructor(
) )
} }
} }
}.onRight { data -> updateTangemPay(data, userWalletId) } }.onRight { data ->
updateTangemPay(data, userWalletId)
analytics.send(customerInfo = data)
}
} }
} }