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 65dc402e4f..057fd1e52d 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 @@ -344,4 +344,96 @@ sealed class TangemPayAnalyticsEvents( event = "Copy Field Clicked", params = mapOf("field" to field), ) + + object Tiers { + + private const val CATEGORY = "Visa Tiers" + + class TierSelectionScreenShowed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Tier Selection Screen Showed", + ) + + class TiersSwiped : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Tiers Swiped", + ) + + class ComparePlansClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Compare Plans Clicked", + ) + + class PlansComparisonPopupShowed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plans Comparison Popup Showed", + ) + + class PlansComparisonPopupClosed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plans Comparison Popup Closed", + ) + + data class PlanSelectedClick(val tierId: String) : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plan Selected Click", + params = mapOf("Plan" to tierId), + ) + + class TopUpBannerForPlusShowed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Topup Banner For Plus Showed", + ) + + class CancelPlusMoveToBasicClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Cancel Plus Move To Basic Clicked", + ) + + class CurrentPlanClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Current Plan Clicked", + ) + + class ChangePlanClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Change Plan Clicked", + ) + + data class PlanChangeConfirmationScreenShowed(val tierId: String) : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plan Change Confirmation Screen Showed", + params = mapOf("Plan" to tierId), + ) + + class PlanChangeCancelClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plan Change Cancel Clicked", + ) + + class PlanChangeUpgradeClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plan Change Upgrade Clicked", + ) + + class PlusCardsClosureWarningBannerShowed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Plus Cards Closure Warning Banner Showed", + ) + + class StayOnPlusConditionsClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Stay On Plus Conditions Clicked", + ) + + class StayOnPlusPopupShowed : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Stay On Plus Popup Showed", + ) + + class StayOnPlusPopupClicked : TangemPayAnalyticsEvents( + categoryName = CATEGORY, + event = "Stay On Plus Popup Clicked", + ) + } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt index f7c119df82..0325330f91 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt @@ -37,17 +37,18 @@ internal class TangemPayDetailsNotificationFactory( ) fun createAwaitingDepositConfig(tariffPlan: TangemPayTariffPlanState?): NotificationConfig? { - if (!isTiersPlusPlanEnabled) return null - if (tariffPlan == null) return null - - if (tariffPlan.tariff.status == TangemPayCustomerTariffPlan.Status.SYSTEM_DOWNGRADE_PENDING) { - return createTariffSystemDownGradePendingConfig(tariffPlan) + tariffPlan ?: return null + return when (TangemPayTiersBannerType.fromPlan(isTiersPlusPlanEnabled, tariffPlan)) { + null -> null + TangemPayTiersBannerType.TierSystemDowngrade -> createTariffSystemDowngradePendingConfig(tariffPlan) + TangemPayTiersBannerType.TopUpForTierUpgrade -> createTopUpForTierUpgradeConfig(tariffPlan) } + } + private fun createTopUpForTierUpgradeConfig(tariffPlan: TangemPayTariffPlanState): NotificationConfig? { val order = tariffPlan.order ?: return null val orderStep = order.step if (orderStep !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit) return null - val feeText = orderStep.toPlan.formatRecurringFeeOrNull() ?: return null return NotificationConfig( @@ -81,7 +82,7 @@ internal class TangemPayDetailsNotificationFactory( iconResId = R.drawable.ic_alert_circle_24, ) - private fun createTariffSystemDownGradePendingConfig(tariffPlan: TangemPayTariffPlanState): NotificationConfig? { + private fun createTariffSystemDowngradePendingConfig(tariffPlan: TangemPayTariffPlanState): NotificationConfig? { val date = tariffPlan.tariff.formatNextBillingDateOrNull() ?: return null val planName = tariffPlan.tariff.plan.name return NotificationConfig( @@ -97,4 +98,30 @@ internal class TangemPayDetailsNotificationFactory( ), ) } +} + +internal enum class TangemPayTiersBannerType { + TopUpForTierUpgrade, + TierSystemDowngrade, + ; + + internal companion object { + fun fromPlan( + isTiersPlusPlanEnabled: Boolean, + tariffPlan: TangemPayTariffPlanState, + ): TangemPayTiersBannerType? { + if (!isTiersPlusPlanEnabled) return null + + if (tariffPlan.tariff.status == TangemPayCustomerTariffPlan.Status.SYSTEM_DOWNGRADE_PENDING) { + tariffPlan.tariff.formatNextBillingDateOrNull() ?: return null + return TierSystemDowngrade + } + + val orderStep = tariffPlan.order?.step + if (orderStep !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit) return null + orderStep.toPlan.formatRecurringFeeOrNull() ?: return null + + return TopUpForTierUpgrade + } + } } \ No newline at end of file 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 b8ed603638..b1b8d9efef 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 @@ -24,11 +24,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.models.WalletMetaInfo import com.tangem.domain.models.TokenReceiveConfig -import com.tangem.domain.models.account.PaymentAccountStatusValue -import com.tangem.domain.models.account.TangemPayCustomerTariffPlan -import com.tangem.domain.models.account.TangemPayTariffPlanState -import com.tangem.domain.models.account.VirtualAccountOnramp -import com.tangem.domain.models.account.isPlanTransitioningState +import com.tangem.domain.models.account.* import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier @@ -36,12 +32,7 @@ import com.tangem.domain.pay.model.TangemPayTopUpData import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.pay.repository.TangemPayWithdrawRepository -import com.tangem.domain.pay.usecase.CancelTangemPayOrderUseCase -import com.tangem.domain.pay.usecase.GetCashbackDeactivationDismissedUseCase -import com.tangem.domain.pay.usecase.GetCashbackSummaryUseCase -import com.tangem.domain.pay.usecase.GetCustomerOffersUseCase -import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase -import com.tangem.domain.pay.usecase.SetCashbackDeactivationDismissedUseCase +import com.tangem.domain.pay.usecase.* import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.features.tangempay.TangemPayConstants @@ -51,10 +42,7 @@ import com.tangem.features.tangempay.components.AddFundsListener import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent import com.tangem.features.tangempay.components.TangemPayIssueAdditionalCardComponent import com.tangem.features.tangempay.details.impl.R -import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType -import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation -import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory -import com.tangem.features.tangempay.entity.TangemPayDetailsUM +import com.tangem.features.tangempay.entity.* import com.tangem.features.tangempay.model.transformers.* import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute import com.tangem.features.tangempay.utils.* @@ -139,6 +127,8 @@ internal class TangemPayDetailsModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() + private var shownTiersBanner: TangemPayTiersBannerType? = null + init { analytics.send(TangemPayAnalyticsEvents.MainScreenOpened()) handleBalanceHiding() @@ -147,6 +137,7 @@ internal class TangemPayDetailsModel @Inject constructor( .onEach { status -> currentStatus.update { status } } .map { it.value } .onEach { state -> + sendTiersAnalytics(state) when (state) { is PaymentAccountStatusValue.Deactivated -> uiState.update { stateFactory.getDeactivatedState(state) @@ -166,6 +157,7 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onCancelPlusTransition(orderId: String) { + analytics.send(TangemPayAnalyticsEvents.Tiers.CancelPlusMoveToBasicClicked()) uiState.update(TangemPayErrorNotificationTransformer(shouldShowProgress = true)) modelScope.launch { cancelTangemPayOrderUseCase(userWalletId = userWalletId, orderId = orderId) @@ -445,6 +437,7 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onClickCurrentPlan(tariffPlan: TangemPayCustomerTariffPlan) { + analytics.send(TangemPayAnalyticsEvents.Tiers.CurrentPlanClicked()) router.push(TangemPayAccountDetailsInnerRoute.CurrentPlan(tariffPlan)) } @@ -547,4 +540,30 @@ internal class TangemPayDetailsModel @Inject constructor( private fun showBottomSheetError(type: TangemPayDetailsErrorType) { uiMessageSender.send(message = TangemPayMessagesFactory.createErrorMessage(errorType = type)) } + + private fun sendTiersAnalytics(state: PaymentAccountStatusValue) { + val tariffPlan = when (state) { + is PaymentAccountStatusValue.Loaded -> state.tariffPlan + is PaymentAccountStatusValue.Inactive -> state.tariffPlan + else -> null + } + val banner = tariffPlan?.let { + TangemPayTiersBannerType.fromPlan(tangemPayFeatureToggles.isTiersPlusPlanEnabled, tariffPlan) + } + + if (banner == shownTiersBanner) return + shownTiersBanner = banner + + val event = when (banner) { + TangemPayTiersBannerType.TopUpForTierUpgrade -> { + TangemPayAnalyticsEvents.Tiers.TopUpBannerForPlusShowed() + } + TangemPayTiersBannerType.TierSystemDowngrade -> { + TangemPayAnalyticsEvents.Tiers.PlusCardsClosureWarningBannerShowed() + } + null -> null + } + + event?.let { analytics.send(it) } + } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt index acb3050b16..d5ea36aa62 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.tangempay.tiers.current 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 @@ -13,6 +14,7 @@ import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.models.account.TangemPayCustomerTariffPlan import com.tangem.domain.models.account.TangemPayTariffPlan import com.tangem.domain.pay.usecase.CancelTariffPlanPendingTransitionUseCase +import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute import com.tangem.features.tangempay.tiers.formatNextBillingDateOrNull @@ -34,6 +36,7 @@ internal class TangemPayCurrentPlanModel @Inject constructor( private val router: Router, private val cancelPendingTransition: CancelTariffPlanPendingTransitionUseCase, private val uiMessageSender: UiMessageSender, + private val analytics: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() @@ -48,12 +51,17 @@ internal class TangemPayCurrentPlanModel @Inject constructor( notification = createNotification(customerPlan), sections = buildSections(customerPlan.plan), onBackClick = router::pop, - onChangePlanClick = { router.push(TangemPayAccountDetailsInnerRoute.SelectPlan(params.tariffPlan)) } + onChangePlanClick = ::onChangePlanClick .takeIf { customerPlan.status != TangemPayCustomerTariffPlan.Status.DOWNGRADE_PENDING }, ) + private fun onChangePlanClick() { + analytics.send(TangemPayAnalyticsEvents.Tiers.ChangePlanClicked()) + router.push(TangemPayAccountDetailsInnerRoute.SelectPlan(params.tariffPlan)) + } + private fun createNotification(customerPlan: TangemPayCustomerTariffPlan): TangemPayCurrentPlanUM.Notification? { val date = customerPlan.formatNextBillingDateOrNull(formatter = DateTimeFormatters.dateMMMd) ?: return null val feeText = customerPlan.plan.formatRecurringFeeOrNull() ?: return null @@ -89,6 +97,8 @@ internal class TangemPayCurrentPlanModel @Inject constructor( if (isProcessing) return val customerPlan = params.tariffPlan val targetPlanName = customerPlan.pendingPlan?.name ?: return + analytics.send(TangemPayAnalyticsEvents.Tiers.StayOnPlusConditionsClicked()) + analytics.send(TangemPayAnalyticsEvents.Tiers.StayOnPlusPopupShowed()) uiMessageSender.send( message = TangemPayMessagesFactory.createStayOnPlanMessage( planName = customerPlan.plan.name, @@ -100,6 +110,7 @@ internal class TangemPayCurrentPlanModel @Inject constructor( private fun confirmStayOnPlan() { if (isProcessing) return + analytics.send(TangemPayAnalyticsEvents.Tiers.StayOnPlusPopupClicked()) isProcessing = true state.value = createState(params.tariffPlan) modelScope.launch { diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt index a0f20dbf5b..23e9a7a92e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.tangempay.tiers.select import androidx.compose.runtime.Stable import arrow.core.Either +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 @@ -16,6 +17,7 @@ import com.tangem.domain.models.account.TangemPayTariffPlanTransition import com.tangem.domain.pay.usecase.CreateTariffPlanTransitionOrderUseCase import com.tangem.domain.pay.usecase.GetTangemPayTariffPlanTransitionsUseCase import com.tangem.domain.pay.usecase.SetTariffPlanPendingTransitionUseCase +import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.error.VisaApiError import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute @@ -42,6 +44,7 @@ internal class TangemPaySelectPlanModel @Inject constructor( private val createTransitionOrder: CreateTariffPlanTransitionOrderUseCase, private val setPendingTransition: SetTariffPlanPendingTransitionUseCase, private val uiMessageSender: UiMessageSender, + private val analytics: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() @@ -62,6 +65,7 @@ internal class TangemPaySelectPlanModel @Inject constructor( field = MutableStateFlow(buildState()) init { + analytics.send(TangemPayAnalyticsEvents.Tiers.TierSelectionScreenShowed()) loadTransitions() } @@ -77,27 +81,37 @@ internal class TangemPaySelectPlanModel @Inject constructor( private fun onPlanSelected(index: Int) { if (index == selectedIndex) return selectedIndex = index + analytics.send(TangemPayAnalyticsEvents.Tiers.TiersSwiped()) state.update { buildState() } } private fun onSelectClick() { - if (allowedTransitions.isEmpty()) return + val transition = allowedTransitions.getOrNull(selectedIndex) ?: return + + val tierId = transition.plan.tierId + analytics.send(TangemPayAnalyticsEvents.Tiers.PlanSelectedClick(tierId)) + analytics.send(TangemPayAnalyticsEvents.Tiers.PlanChangeConfirmationScreenShowed(tierId)) + isConfirm = true state.update { buildState() } } private fun onComparePlansClick() { if (allowedTransitions.isEmpty()) return + analytics.send(TangemPayAnalyticsEvents.Tiers.ComparePlansClicked()) + analytics.send(TangemPayAnalyticsEvents.Tiers.PlansComparisonPopupShowed()) state.update { buildState(showPlanCompare = true) } } private fun onCompareDismiss() { + analytics.send(TangemPayAnalyticsEvents.Tiers.PlansComparisonPopupClosed()) state.update { buildState(showPlanCompare = false) } } private fun onBackClick() { if (isProcessing) return if (isConfirm) { + analytics.send(TangemPayAnalyticsEvents.Tiers.PlanChangeCancelClicked()) isConfirm = false state.update { buildState() } } else { @@ -109,6 +123,9 @@ internal class TangemPaySelectPlanModel @Inject constructor( if (isProcessing) return val transition = allowedTransitions.getOrNull(selectedIndex) ?: return + if (transition.type == TangemPayTariffPlanTransition.Type.UPGRADE) { + analytics.send(TangemPayAnalyticsEvents.Tiers.PlanChangeUpgradeClicked()) + } when (transition.type) { TangemPayTariffPlanTransition.Type.ACTIVATION, TangemPayTariffPlanTransition.Type.UPGRADE,