diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt index 052ae672ca..65dc402e4f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt @@ -293,4 +293,55 @@ sealed class TangemPayAnalyticsEvents( categoryName = "Visa Card Management", event = "Visa Extra Card Issuance Confirmed", ) + + class VaTopupButtonShowed : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Visa VA Topup Button Showed", + ) + + class VaTopupButtonClicked : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Visa VA Topup Button Clicked", + ) + + class VaConditionsPopupShowedFirstTime : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Conditions Popup Showed First Time", + ) + + class VaShowDetailsFirstTimeClicked : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Show Details First Time Clicked", + ) + + class VaSuccessScreenActivation : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Success Screen Activation", + ) + + class VaConditionsPopupShowed : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Conditions Popup Showed", + ) + + class VaShowDetailsClicked : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Show Details Clicked", + ) + + class VaBankingDetailsShowed : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Banking Details Showed", + ) + + class VaShareDetailsButtonClicked : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Share Details Button Clicked", + ) + + data class VaCopyFieldClicked(val field: String) : TangemPayAnalyticsEvents( + categoryName = "Visa VA Topup", + event = "Copy Field Clicked", + params = mapOf("field" to field), + ) } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt index be3f9eae88..a6ec072508 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt @@ -125,6 +125,9 @@ internal class TangemPayCardPageScreenComponent( dailyDepositLimit = VA_DAILY_DEPOSIT_LIMIT_PLACEHOLDER, shouldSkipIntro = true, listener = VirtualAccountAddFundsListener { model.bottomSheetNavigation.dismiss() }, + onDetailsShown = model::onVaBankingDetailsShown, + onShareClicked = model::onVaShareDetailsClicked, + onFieldCopied = model::onVaFieldCopied, ), ) is TangemPayCardNavigation.Receive -> tokenReceiveComponentFactory.create( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt index 052c5c99cf..edfe81adf4 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt @@ -154,6 +154,9 @@ internal class TangemPayDetailsComponent( dailyDepositLimit = VA_DAILY_DEPOSIT_LIMIT_PLACEHOLDER, shouldSkipIntro = true, listener = VirtualAccountAddFundsListener { model.bottomSheetNavigation.dismiss() }, + onDetailsShown = model::onVaBankingDetailsShown, + onShareClicked = model::onVaShareDetailsClicked, + onFieldCopied = model::onVaFieldCopied, ), ) is TangemPayDetailsNavigation.IssueAdditionalCard -> TangemPayIssueAdditionalCardComponent( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt index f8e50e9509..b385c734da 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt @@ -1,12 +1,14 @@ package com.tangem.features.tangempay.model import androidx.compose.runtime.Stable +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.models.ReceiveAddressModel import com.tangem.domain.models.ReceiveAddressModel.DisplayType import com.tangem.domain.pay.model.TangemPayTopUpData +import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.features.tangempay.components.TangemPayAddFundsComponent import com.tangem.features.tangempay.entity.TangemPayAddFundsUM @@ -21,13 +23,22 @@ internal class TangemPayAddFundsModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val tangemPayFeatureToggles: TangemPayFeatureToggles, - private val virtualAccountToggles: VirtualAccountFeatureToggles, + virtualAccountToggles: VirtualAccountFeatureToggles, + analytics: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() + private val isBankTransferShown = virtualAccountToggles.isVaMvp0Enabled && params.virtualAccountOnramp != null + val uiState: TangemPayAddFundsUM = getInitialState() + init { + if (isBankTransferShown) { + analytics.send(TangemPayAnalyticsEvents.VaTopupButtonShowed()) + } + } + private fun getInitialState(): TangemPayAddFundsUM { val data = TangemPayTopUpData( currency = params.cryptoCurrency, @@ -45,7 +56,7 @@ internal class TangemPayAddFundsModel @Inject constructor( return TangemPayAddFundsUMConverter( listener = params.listener, isRedesignEnabled = tangemPayFeatureToggles.isRedesignEnabled, - shouldShowBankTransfer = virtualAccountToggles.isVaMvp0Enabled && params.virtualAccountOnramp != null, + shouldShowBankTransfer = isBankTransferShown, ).convert(data) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt index 3911bfae65..2468874e6a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt @@ -489,6 +489,7 @@ internal class TangemPayCardPageModel @Inject constructor( override fun onClickBankTransfer() { val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return val onramp = loaded.virtualAccount ?: return + analytics.send(TangemPayAnalyticsEvents.VaTopupButtonClicked()) bottomSheetNavigation.dismiss() bottomSheetNavigation.activate( TangemPayCardNavigation.VirtualAccountDeposit( @@ -500,6 +501,7 @@ internal class TangemPayCardPageModel @Inject constructor( } fun onVirtualAccountOrderCreated() { + analytics.send(TangemPayAnalyticsEvents.VaSuccessScreenActivation()) bottomSheetNavigation.dismiss() router.push(TangemPayCardDetailsInnerRoute.VirtualAccountDepositSuccess) } @@ -514,6 +516,18 @@ internal class TangemPayCardPageModel @Inject constructor( ) } + fun onVaBankingDetailsShown() { + analytics.send(TangemPayAnalyticsEvents.VaBankingDetailsShowed()) + } + + fun onVaShareDetailsClicked() { + analytics.send(TangemPayAnalyticsEvents.VaShareDetailsButtonClicked()) + } + + fun onVaFieldCopied(field: String) { + analytics.send(TangemPayAnalyticsEvents.VaCopyFieldClicked(field)) + } + override fun onDismissAddFunds() { bottomSheetNavigation.dismiss() } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index e5aafdf942..00a93a43f5 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -314,6 +314,7 @@ internal class TangemPayDetailsModel @Inject constructor( override fun onClickBankTransfer() { val loaded = currentStatus.value.ifLoadedOrNull { it } ?: return val onramp = loaded.virtualAccount ?: return + analytics.send(TangemPayAnalyticsEvents.VaTopupButtonClicked()) bottomSheetNavigation.dismiss() bottomSheetNavigation.activate( TangemPayDetailsNavigation.VirtualAccountDeposit( @@ -325,6 +326,7 @@ internal class TangemPayDetailsModel @Inject constructor( } fun onVirtualAccountOrderCreated() { + analytics.send(TangemPayAnalyticsEvents.VaSuccessScreenActivation()) bottomSheetNavigation.dismiss() router.push(TangemPayAccountDetailsInnerRoute.VirtualAccountDepositSuccess) } @@ -339,6 +341,18 @@ internal class TangemPayDetailsModel @Inject constructor( ) } + fun onVaBankingDetailsShown() { + analytics.send(TangemPayAnalyticsEvents.VaBankingDetailsShowed()) + } + + fun onVaShareDetailsClicked() { + analytics.send(TangemPayAnalyticsEvents.VaShareDetailsButtonClicked()) + } + + fun onVaFieldCopied(field: String) { + analytics.send(TangemPayAnalyticsEvents.VaCopyFieldClicked(field)) + } + override fun onClickReceive(data: TangemPayTopUpData) { analytics.send(TangemPayAnalyticsEvents.ReceiveFundsClicked()) bottomSheetNavigation.dismiss() diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModel.kt index d11441c9b5..8850ca7669 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.tangempay.model import androidx.compose.runtime.Stable +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -10,6 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.models.account.VirtualAccountOnramp import com.tangem.domain.pay.usecase.CreateVirtualAccountOrderUseCase +import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.features.tangempay.components.TangemPayVirtualAccountDepositComponent import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.entity.TangemPayVirtualAccountDepositUM @@ -29,6 +31,7 @@ internal class TangemPayVirtualAccountDepositModel @Inject constructor( private val urlOpener: UrlOpener, private val uiMessageSender: UiMessageSender, private val createVirtualAccountOrderUseCase: CreateVirtualAccountOrderUseCase, + private val analytics: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() @@ -55,14 +58,29 @@ internal class TangemPayVirtualAccountDepositModel @Inject constructor( ), ) + init { + val event = if (params.virtualAccountOnramp is VirtualAccountOnramp.Eligible) { + TangemPayAnalyticsEvents.VaConditionsPopupShowedFirstTime() + } else { + TangemPayAnalyticsEvents.VaConditionsPopupShowed() + } + analytics.send(event) + } + fun onDismiss() { params.onDismiss() } private fun onShowDetailsClick() { when (params.virtualAccountOnramp) { - is VirtualAccountOnramp.Available -> params.onShowDetails(params.virtualAccountOnramp) - VirtualAccountOnramp.Eligible -> createVirtualAccountOrder() + is VirtualAccountOnramp.Available -> { + analytics.send(TangemPayAnalyticsEvents.VaShowDetailsClicked()) + params.onShowDetails(params.virtualAccountOnramp) + } + VirtualAccountOnramp.Eligible -> { + analytics.send(TangemPayAnalyticsEvents.VaShowDetailsFirstTimeClicked()) + createVirtualAccountOrder() + } } } diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModelTest.kt index 522a9deb45..91df97f7c9 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModelTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayVirtualAccountDepositModelTest.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.left import arrow.core.right import com.google.common.truth.Truth.assertThat +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.model.MutableParamsContainer import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.url.UrlOpener @@ -12,6 +13,7 @@ import com.tangem.domain.models.account.BankCredentials import com.tangem.domain.models.account.VirtualAccountOnramp import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.usecase.CreateVirtualAccountOrderUseCase +import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.error.VisaApiError import com.tangem.features.tangempay.components.TangemPayVirtualAccountDepositComponent import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider @@ -39,10 +41,11 @@ internal class TangemPayVirtualAccountDepositModelTest { private val createVirtualAccountOrderUseCase: CreateVirtualAccountOrderUseCase = mockk() private val onShowDetails: (VirtualAccountOnramp.Available) -> Unit = mockk(relaxed = true) private val onOrderCreated: () -> Unit = mockk(relaxed = true) + private val analytics: AnalyticsEventHandler = mockk(relaxed = true) @BeforeEach fun resetMocks() { - clearMocks(createVirtualAccountOrderUseCase, onShowDetails, onOrderCreated, uiMessageSender) + clearMocks(createVirtualAccountOrderUseCase, onShowDetails, onOrderCreated, uiMessageSender, analytics) } @Test @@ -58,6 +61,8 @@ internal class TangemPayVirtualAccountDepositModelTest { // Assert verify(exactly = 1) { onShowDetails(onramp) } coVerify(exactly = 0) { createVirtualAccountOrderUseCase(any(), any()) } + verify(exactly = 1) { analytics.send(ofType()) } + verify(exactly = 1) { analytics.send(ofType()) } } @Test @@ -74,6 +79,8 @@ internal class TangemPayVirtualAccountDepositModelTest { coVerify(exactly = 1) { createVirtualAccountOrderUseCase(userWalletId, paymentAccountAddress) } verify(exactly = 1) { onOrderCreated() } assertThat(model.uiState.value.isLoading).isFalse() + verify(exactly = 1) { analytics.send(ofType()) } + verify(exactly = 1) { analytics.send(ofType()) } } @Test @@ -130,6 +137,7 @@ internal class TangemPayVirtualAccountDepositModelTest { urlOpener = urlOpener, uiMessageSender = uiMessageSender, createVirtualAccountOrderUseCase = createVirtualAccountOrderUseCase, + analytics = analytics, ) private fun bankCredentials() = BankCredentials( diff --git a/features/virtual-accounts/details/api/src/main/kotlin/com/tangem/features/virtualaccount/details/component/VirtualAccountAddFundsBottomSheetComponent.kt b/features/virtual-accounts/details/api/src/main/kotlin/com/tangem/features/virtualaccount/details/component/VirtualAccountAddFundsBottomSheetComponent.kt index da2bf05f5a..91c9c5e583 100644 --- a/features/virtual-accounts/details/api/src/main/kotlin/com/tangem/features/virtualaccount/details/component/VirtualAccountAddFundsBottomSheetComponent.kt +++ b/features/virtual-accounts/details/api/src/main/kotlin/com/tangem/features/virtualaccount/details/component/VirtualAccountAddFundsBottomSheetComponent.kt @@ -18,6 +18,10 @@ interface VirtualAccountAddFundsBottomSheetComponent : ComposableBottomSheetComp val dailyDepositLimit: String, val listener: VirtualAccountAddFundsListener, val shouldSkipIntro: Boolean = false, + // Analytics hooks — supplied by callers that track this sheet (e.g. TangemPay VA topup); no-op otherwise. + val onDetailsShown: () -> Unit = {}, + val onShareClicked: () -> Unit = {}, + val onFieldCopied: (fieldName: String) -> Unit = {}, ) data class RequisitesRow( diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt index 56a80d6ce1..3de23e754e 100644 --- a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt +++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt @@ -35,6 +35,13 @@ internal class VirtualAccountAddFundsModel @Inject constructor( ), ) + init { + if (params.shouldSkipIntro) { + // send analytics + params.onDetailsShown() + } + } + fun onDismiss() { params.listener.onAddFundsDismiss() } @@ -44,6 +51,7 @@ internal class VirtualAccountAddFundsModel @Inject constructor( ) private fun showDetailsContent() { + params.onDetailsShown() uiState.update { state -> state.copy(content = buildDetailsContent()) } } @@ -52,13 +60,19 @@ internal class VirtualAccountAddFundsModel @Inject constructor( .map { detailItem(label = it.title, value = it.value) } .toImmutableList(), dailyLimit = params.dailyDepositLimit, - onShareClick = { shareManager.shareText(buildShareText()) }, + onShareClick = { + params.onShareClicked() + shareManager.shareText(buildShareText()) + }, ) private fun detailItem(label: String, value: String) = VirtualAccountAddFundsUM.DetailItem( label = stringReference(label), value = value, - onCopyClick = { clipboardManager.setText(text = value, isSensitive = true) }, + onCopyClick = { + params.onFieldCopied(label) + clipboardManager.setText(text = value, isSensitive = true) + }, ) private fun buildShareText(): String {