Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-20 05:16:09 -07:00
parent dd59d9f92e
commit 081a9c6408
5 changed files with 190 additions and 24 deletions

View file

@ -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
}
}
}

View file

@ -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<TangemPayDetailsNavigation> = 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) }
}
}

View file

@ -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<TangemPayCurrentPlanComponent.Params>()
@ -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 {

View file

@ -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<TangemPaySelectPlanComponent.Params>()
@ -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,