NTP-60706 | Deregistration decoupling (#16328)

This commit is contained in:
shreyansu raj
2025-06-04 13:35:32 +05:30
committed by GitHub
parent 4277d9c972
commit 7d08449271
12 changed files with 383 additions and 3 deletions

View File

@@ -41,6 +41,7 @@ import com.navi.base.utils.orTrue
import com.navi.common.checkmate.core.CheckMateManager
import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.commoncomposables.utils.ScrollToTopListener
import com.navi.common.commoncomposables.utils.resetScrollToTop
import com.navi.common.network.models.RepoResult
import com.navi.common.ui.compose.DrawerState
import com.navi.uitron.model.UiTronResponse
@@ -125,6 +126,7 @@ fun ProfileScreen(
drawerState = drawerState,
appUpdateState = appUpdateState,
inAppUpdateBridge = activity,
scrollToTop = { profileVM.resetScrollToTop(true, PROFILE) },
)
}
}

View File

@@ -30,6 +30,7 @@ fun ProfileScreenWidgetRenderer(
drawerState: () -> DrawerState,
appUpdateState: AppUpdateState,
inAppUpdateBridge: InAppUpdateBridge,
scrollToTop: () -> Unit = {},
) {
if (widget == null) return
return when (widget.widgetType) {
@@ -46,7 +47,8 @@ fun ProfileScreenWidgetRenderer(
when (widget.widgetName) {
HomeCustomWidgetType.ProfileHeaderWidget.value ->
AnimatedProfileContent(widget, viewModel, drawerState)
HomeCustomWidgetType.UpiSettingsWidget.value -> UPISettingSDK(drawerState)
HomeCustomWidgetType.UpiSettingsWidget.value ->
UPISettingSDK(drawerState = drawerState, scrollToTop = scrollToTop)
else -> Unit
}
}

View File

@@ -0,0 +1,17 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.settingscreen.model
import com.google.gson.annotations.SerializedName
import com.navi.pay.common.model.view.PspType
data class DeregisterNaviPayRequest(
@SerializedName("deviceFingerPrint") val deviceFingerPrint: String,
@SerializedName("merchantCustomerId") val merchantCustomerId: String,
@SerializedName("provider") val pspType: PspType,
)

View File

@@ -0,0 +1,15 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.settingscreen.model
import com.google.gson.annotations.SerializedName
data class DeregisterNaviPayResponse(
@SerializedName("merchantCustomerId") val merchantCustomerId: String,
@SerializedName("status") val status: String,
)

View File

@@ -0,0 +1,30 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.settingscreen.repository
import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.network.models.RepoResult
import com.navi.common.network.retrofit.ResponseCallback
import com.navi.pay.common.settingscreen.model.DeregisterNaviPayRequest
import com.navi.pay.common.settingscreen.model.DeregisterNaviPayResponse
import com.navi.pay.network.retrofit.NaviPayRetrofitService
import javax.inject.Inject
class SettingSDKRepository
@Inject
constructor(private val naviPayRetrofitService: NaviPayRetrofitService) : ResponseCallback() {
suspend fun deregisterNaviPay(
deregisterNaviPayRequest: DeregisterNaviPayRequest,
metricInfo: MetricInfo<RepoResult<DeregisterNaviPayResponse>>,
) =
apiResponseCallback(
response = naviPayRetrofitService.deregisterNaviPay(deregisterNaviPayRequest),
metricInfo = metricInfo,
)
}

View File

@@ -26,6 +26,7 @@ import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -62,8 +63,13 @@ import com.navi.pay.common.settingscreen.utils.downloadQR
import com.navi.pay.common.settingscreen.utils.getQRBitmapForWallpaper
import com.navi.pay.common.settingscreen.utils.saveQrInGallery
import com.navi.pay.common.settingscreen.utils.shareQR
import com.navi.pay.common.settingscreen.view.UpiSettingBottomSheetUIState
import com.navi.pay.common.settingscreen.viewmodel.SettingSDKViewmodel
import com.navi.pay.common.theme.color.NaviPayColor
import com.navi.pay.common.ui.BottomSheetContentWithIconHeaderDescButton
import com.navi.pay.common.ui.BottomSheetContentWithIconHeaderPrimarySecondaryButtonCtaLoader
import com.navi.pay.common.ui.NaviPayModalBottomSheet
import com.navi.pay.common.ui.SuccessBottomSheetContent
import com.navi.pay.common.utils.NaviPayCommonUtils
import com.navi.pay.common.utils.launchOnboardingSDK
import com.navi.pay.utils.ACCOUNT_ID
@@ -76,6 +82,7 @@ import kotlinx.coroutines.launch
@Composable
fun UPISettingSDK(
drawerState: () -> DrawerState,
scrollToTop: () -> Unit,
settingSDKViewmodel: SettingSDKViewmodel = hiltViewModel(),
) {
val activity = LocalActivity.current ?: return
@@ -137,6 +144,10 @@ fun UPISettingSDK(
settingSDKViewmodel.isRccRewardsExperimentEnabled.collectAsStateWithLifecycle()
val rccRewardStripType by settingSDKViewmodel.rccRewardStripType.collectAsStateWithLifecycle()
val isRccAccountPresent by settingSDKViewmodel.isRccAccountPresent.collectAsStateWithLifecycle()
val bottomSheetStateHolder by
settingSDKViewmodel.bottomsheetStateHolder.collectAsStateWithLifecycle()
val showLoader by settingSDKViewmodel.showLoader.collectAsStateWithLifecycle()
val scrollToTop by settingSDKViewmodel.scrollToTop.collectAsStateWithLifecycle()
val galleryLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result
@@ -149,6 +160,12 @@ fun UPISettingSDK(
}
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(scrollToTop) {
if (scrollToTop) {
scrollToTop()
}
}
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
@@ -251,6 +268,41 @@ fun UPISettingSDK(
}
}
val bottomSheetState =
rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { bottomSheetStateHolder.bottomSheetStateChange },
)
val scope = rememberCoroutineScope()
val onDismissBottomSheet: () -> Unit = {
scope
.launch { bottomSheetState.hide() }
.invokeOnCompletion {
if (!bottomSheetState.isVisible || !bottomSheetStateHolder.bottomSheetStateChange) {
settingSDKViewmodel.updateBottomSheetUIState(showBottomSheet = false)
}
}
}
if (bottomSheetStateHolder.showBottomSheet) {
NaviPayModalBottomSheet(
modifier = Modifier.fillMaxWidth(),
bottomSheetState = bottomSheetState,
onDismissRequest = onDismissBottomSheet,
shouldDismissOnBackPress = true,
bottomSheetContent = {
RenderSettingSdkBottomsheet(
bottomSheetUIState = bottomSheetStateHolder.bottomSheetUIState,
showLoader = showLoader,
onDismissBottomSheet = onDismissBottomSheet,
onDeregisterClicked = settingSDKViewmodel::deregisterNaviPay,
)
},
)
}
Box {
UPISettingContent(
upiSettingDetails = upiSettingDetails,
@@ -369,3 +421,62 @@ private fun SnackBarSection(
}
}
}
@Composable
fun RenderSettingSdkBottomsheet(
bottomSheetUIState: UpiSettingBottomSheetUIState,
showLoader: Boolean,
onDismissBottomSheet: () -> Unit,
onDeregisterClicked: () -> Unit,
) {
when (bottomSheetUIState) {
UpiSettingBottomSheetUIState.DeregisterNaviPayBottomSheet -> {
BottomSheetContentWithIconHeaderPrimarySecondaryButtonCtaLoader(
header = stringResource(id = R.string.np_deregister_navi_upi),
description = stringResource(id = R.string.np_deregister_navi_upi_description),
primaryButton = stringResource(id = R.string.continue_text),
secondaryButton = stringResource(id = R.string.cancel),
onPrimaryButtonClicked = onDeregisterClicked,
onSecondaryButtonClicked = onDismissBottomSheet,
showLoader = showLoader,
)
}
UpiSettingBottomSheetUIState.ActiveLiteAccountFoundBottomSheet -> {
BottomSheetContentWithIconHeaderDescButton(
headerTextId = R.string.np_active_upi_lite_deregister_failed,
descriptionTextId = R.string.np_active_upi_lite_deregister_failed_description,
buttonTextId = R.string.np_okay_got_it,
onButtonClicked = onDismissBottomSheet,
)
}
UpiSettingBottomSheetUIState.ActiveMandateFoundBottomSheet -> {
BottomSheetContentWithIconHeaderDescButton(
headerTextId = R.string.np_active_mandate_deregister_failed,
descriptionTextId = R.string.np_active_mandate_deregister_failed_description,
buttonTextId = R.string.np_okay_got_it,
onButtonClicked = onDismissBottomSheet,
)
}
UpiSettingBottomSheetUIState.ActiveMandateAndLiteFoundBottomSheet -> {
BottomSheetContentWithIconHeaderDescButton(
headerTextId = R.string.np_active_mandate_and_lite_deregister_failed,
descriptionTextId =
R.string.np_active_mandate_and_lite_deregister_failed_description,
buttonTextId = R.string.np_okay_got_it,
onButtonClicked = onDismissBottomSheet,
)
}
UpiSettingBottomSheetUIState.DeregisterSuccessBottomSheet -> {
SuccessBottomSheetContent(
title = stringResource(R.string.np_deregister_navi_upi_success),
isDescriptionClickable = false,
)
}
UpiSettingBottomSheetUIState.None -> {}
}
}

View File

@@ -0,0 +1,14 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.settingscreen.view
data class UpiSettingBottomSheetStateHolder(
val showBottomSheet: Boolean,
val bottomSheetStateChange: Boolean,
val bottomSheetUIState: UpiSettingBottomSheetUIState,
)

View File

@@ -0,0 +1,23 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.settingscreen.view
sealed class UpiSettingBottomSheetUIState {
data object DeregisterNaviPayBottomSheet : UpiSettingBottomSheetUIState()
data object ActiveLiteAccountFoundBottomSheet : UpiSettingBottomSheetUIState()
data object ActiveMandateFoundBottomSheet : UpiSettingBottomSheetUIState()
data object ActiveMandateAndLiteFoundBottomSheet : UpiSettingBottomSheetUIState()
data object DeregisterSuccessBottomSheet : UpiSettingBottomSheetUIState()
data object None : UpiSettingBottomSheetUIState()
}

View File

@@ -15,22 +15,30 @@ import com.navi.base.cache.model.NaviCacheEntity
import com.navi.base.cache.repository.NaviCacheRepository
import com.navi.base.model.ActionData
import com.navi.common.network.models.LitmusExperimentResponse
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.upi.AUTO_PAY_REQUEST
import com.navi.common.upi.PENDING_REQUEST
import com.navi.pay.analytics.NaviPayAnalytics
import com.navi.pay.analytics.NaviPayAnalytics.Companion.NAVI_PAY_UPI_SETTINGS_SDK
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.common.model.view.PspType
import com.navi.pay.common.repository.SharedPreferenceRepository
import com.navi.pay.common.settingscreen.model.DeregisterNaviPayRequest
import com.navi.pay.common.settingscreen.model.QrAsWallpaperStateHolder
import com.navi.pay.common.settingscreen.model.QrDetails
import com.navi.pay.common.settingscreen.model.UpiSettingDetails
import com.navi.pay.common.settingscreen.repository.SettingSDKRepository
import com.navi.pay.common.settingscreen.utils.WallpaperManager
import com.navi.pay.common.settingscreen.utils.isOnboardingRequired
import com.navi.pay.common.settingscreen.view.UpiSettingBottomSheetStateHolder
import com.navi.pay.common.settingscreen.view.UpiSettingBottomSheetUIState
import com.navi.pay.common.settingscreen.viewmodel.RccRewardStripType.Companion.getRewardStripType
import com.navi.pay.common.setup.NaviPayCustomerStatusHandler
import com.navi.pay.common.setup.NaviPayManager
import com.navi.pay.common.usecase.LinkedAccountsUseCase
import com.navi.pay.common.utils.NaviPayNotificationHandler
import com.navi.pay.common.utils.NaviPayOnboardingNavigator
import com.navi.pay.common.utils.getMetricInfo
import com.navi.pay.common.viewmodel.NaviPayBaseVM
import com.navi.pay.management.common.sendmoney.util.NaviPayOffersHelper
import com.navi.pay.management.common.settings.util.SettingsCountManager
@@ -38,7 +46,11 @@ import com.navi.pay.management.common.utils.NaviPayPspManager
import com.navi.pay.network.di.NaviPayGsonBuilder
import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.utils.ERR_ACTIVE_LITE_ACCOUNT_FOUND
import com.navi.pay.utils.ERR_LITE_AND_MANDATE_ACTIVE
import com.navi.pay.utils.ERR_MANDATE_PRESENT
import com.navi.pay.utils.LITMUS_EXPERIMENT_NAVIPAY_RCC_LANDING_EXP
import com.navi.pay.utils.NAVI_PAY_DEREGISTER
import com.navi.pay.utils.NAVI_PAY_SETTING_QR_PAGER_ANIMATION_COUNTER
import com.navi.pay.utils.PENDING_REQUEST_REFRESHED_REQUIRED
import com.navi.pay.utils.RCC
@@ -46,8 +58,10 @@ import com.navi.rr.common.models.OfferData
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.collections.map
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -77,6 +91,8 @@ constructor(
private val wallpaperManager: WallpaperManager,
private val naviPayOffersHelper: NaviPayOffersHelper,
@NaviPayGsonBuilder private val gson: Gson,
private val settingSDKRepository: SettingSDKRepository,
private val naviPayCustomerStatusHandler: NaviPayCustomerStatusHandler,
) : NaviPayBaseVM() {
private val _upiSettingDetails = MutableStateFlow(UpiSettingDetails())
@@ -120,6 +136,25 @@ constructor(
private val _isRccAccountPresent = MutableStateFlow(false)
val isRccAccountPresent = _isRccAccountPresent.asStateFlow()
private var pspType: PspType? = null
private val _bottomsheetStateHolder =
MutableStateFlow(
UpiSettingBottomSheetStateHolder(
showBottomSheet = false,
bottomSheetStateChange = false,
bottomSheetUIState = UpiSettingBottomSheetUIState.None,
)
)
val bottomsheetStateHolder = _bottomsheetStateHolder.asStateFlow()
private val _showLoader = MutableStateFlow(false)
val showLoader = _showLoader.asStateFlow()
private val _scrollToTop = MutableStateFlow(false)
val scrollToTop = _scrollToTop.asStateFlow()
init {
viewModelScope.launch(Dispatchers.IO) {
linkedAccountsUseCase.execute(screenName = screenName).collectLatest { linkedAccounts ->
@@ -313,12 +348,29 @@ constructor(
naviPayFlowType = NaviPayFlowType.DEVICE_BINDING,
screenName = screenName,
onPspEvaluated = { pspEvaluationResult ->
if (pspEvaluationResult.onboardingDataEntity != null) {
_navigateToCtaUrl.emit(actionData)
if (pspEvaluationResult.onboardingDataEntity == null) {
return@evaluateAndBindAnySupportedPspForFlow
}
pspType = pspEvaluationResult.onboardingDataEntity.pspType
handleCtaActionBasedOnUrl(actionData)
},
)
} else {
handleCtaActionBasedOnUrl(actionData)
}
}
}
private suspend fun handleCtaActionBasedOnUrl(actionData: ActionData) {
when (actionData.url) {
NAVI_PAY_DEREGISTER -> {
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState = UpiSettingBottomSheetUIState.DeregisterNaviPayBottomSheet,
allowStateChange = true,
)
}
else -> {
_navigateToCtaUrl.emit(actionData)
}
}
@@ -346,6 +398,97 @@ constructor(
}
}
fun deregisterNaviPay() {
viewModelScope.launch(Dispatchers.IO) {
updateLoaderState(showLoader = true)
val customerOnboardingEntity =
naviPayCustomerStatusHandler.getCustomerOnboardingEntity(
pspType = pspType ?: PspType.JUSPAY_AXIS
)
val deregisterUpiRequest =
DeregisterNaviPayRequest(
deviceFingerPrint = customerOnboardingEntity?.deviceFingerPrint.orEmpty(),
merchantCustomerId = customerOnboardingEntity?.merchantCustomerId.orEmpty(),
pspType = customerOnboardingEntity?.pspType ?: PspType.JUSPAY_AXIS,
)
val response =
settingSDKRepository.deregisterNaviPay(
deregisterNaviPayRequest = deregisterUpiRequest,
metricInfo = getMetricInfo(screenName),
)
if (response.isSuccessWithData()) {
naviPayManager.triggerNaviPayHardLogout()
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState = UpiSettingBottomSheetUIState.DeregisterSuccessBottomSheet,
allowStateChange = true,
)
updateScrollToTopState(true)
delay(2.seconds) // Delay to show de-registration success BS
updateBottomSheetUIState(showBottomSheet = false)
} else {
handleDeregistrationFailure(response.errors?.get(0)?.code.orEmpty())
}
updateLoaderState(showLoader = false)
}
}
private fun handleDeregistrationFailure(errorCode: String) {
when (errorCode) {
ERR_ACTIVE_LITE_ACCOUNT_FOUND -> {
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState =
UpiSettingBottomSheetUIState.ActiveLiteAccountFoundBottomSheet,
allowStateChange = true,
)
}
ERR_MANDATE_PRESENT -> {
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState = UpiSettingBottomSheetUIState.ActiveMandateFoundBottomSheet,
allowStateChange = true,
)
}
ERR_LITE_AND_MANDATE_ACTIVE -> {
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState =
UpiSettingBottomSheetUIState.ActiveMandateAndLiteFoundBottomSheet,
allowStateChange = true,
)
}
else -> {
notifyError()
}
}
}
fun updateBottomSheetUIState(
bottomSheetUIState: UpiSettingBottomSheetUIState? = null,
showBottomSheet: Boolean,
allowStateChange: Boolean? = null,
) {
_bottomsheetStateHolder.update {
it.copy(
showBottomSheet = showBottomSheet,
bottomSheetStateChange = allowStateChange ?: it.bottomSheetStateChange,
bottomSheetUIState = bottomSheetUIState ?: it.bottomSheetUIState,
)
}
}
private fun updateScrollToTopState(scrollToTop: Boolean) {
_scrollToTop.update { scrollToTop }
}
private fun updateLoaderState(showLoader: Boolean) {
_showLoader.update { showLoader }
}
override val screenName: String
get() = NAVI_PAY_UPI_SETTINGS_SDK
}

View File

@@ -16,6 +16,8 @@ import com.navi.pay.common.model.network.DeactivateVpaRequest
import com.navi.pay.common.model.network.RequestIdResponse
import com.navi.pay.common.model.network.ValidateVpaRequest
import com.navi.pay.common.model.network.ValidateVpaResponse
import com.navi.pay.common.settingscreen.model.DeregisterNaviPayRequest
import com.navi.pay.common.settingscreen.model.DeregisterNaviPayResponse
import com.navi.pay.management.blockedusers.model.network.BlockedUsersListRequest
import com.navi.pay.management.blockedusers.model.network.BlockedUsersListResponse
import com.navi.pay.management.blockedusers.model.network.UnblockActionRequest
@@ -442,4 +444,9 @@ interface NaviPayRetrofitService {
suspend fun forecloseEmi(
@Body forecloseEmiRequest: ForecloseEmiRequest
): Response<GenericResponse<ForecloseEmiResponse>>
@POST("/gateway-service/$NAVI_PAY_API_VERSION2/navipay/customer/deregister")
suspend fun deregisterNaviPay(
@Body deregisterNaviPayRequest: DeregisterNaviPayRequest
): Response<GenericResponse<DeregisterNaviPayResponse>>
}

View File

@@ -510,6 +510,7 @@ const val ALL_ENABLED_ACCOUNTS = "[\"SAVINGS\", \"CREDIT\", \"UPICREDIT\"]"
const val LINKED_ACCOUNT_SCREEN_SOURCE = "linkedAccountScreenSource"
const val CHECK_BALANCE = "CheckBalance"
const val SELF_TRANSFER = "SelfTransfer"
const val NAVI_PAY_DEREGISTER = "NAVI_PAY_DEREGISTER"
// Link upi number constants
const val AVAILABILITY_ACTION_CHECK = "CHECK"
@@ -632,3 +633,8 @@ const val ERR_FAILED_TO_FETCH_EXTERNAL_CUSTOMER_ID = "ERR_FAILED_TO_FETCH_EXTERN
// Lrn mismatch error
const val LRN_MISMATCH_ERROR = "lrn_mismatch_error"
// Deregister navi upi error codes
const val ERR_ACTIVE_LITE_ACCOUNT_FOUND = "ERR_ACTIVE_LITE_ACCOUNT_FOUND"
const val ERR_MANDATE_PRESENT = "ERR_MANDATE_PRESENT"
const val ERR_LITE_AND_MANDATE_ACTIVE = "ERR_LITE_AND_MANDATE_ACTIVE"

View File

@@ -1151,4 +1151,14 @@
<string name="np_statement_transaction_detail_transaction_id_prefix_upi">UPI txn ID:</string>
<string name="np_statement_transaction_detail_transaction_id_prefix_non_upi">Bank txn ID (UTR):</string>
<string name="np_statement_transaction_detail_notes_id_prefix_upi">Note:</string>
<string name="np_deregister_navi_upi">Are you sure you want to de-register from Navi UPI?</string>
<string name="np_deregister_navi_upi_description">De-registering from Navi UPI will remove all your payment methods and deactivate all UPI IDs.</string>
<string name="np_deregister_navi_upi_success">De-registered from Navi UPI succesfully</string>
<string name="np_active_upi_lite_deregister_failed">De-registration failed as UPI Lite is active</string>
<string name="np_active_upi_lite_deregister_failed_description">UPI Lite is currently active. Please disable UPI Lite and try de-registering again.</string>
<string name="np_active_mandate_deregister_failed">De-registration failed as autopay mandate(s) are active</string>
<string name="np_active_mandate_deregister_failed_description">Mandate(s) are currently active. Please delete all mandates and try de-registering again.</string>
<string name="np_active_mandate_and_lite_deregister_failed">De-registration failed as autopay mandate(s) and UPI Lite are active</string>
<string name="np_active_mandate_and_lite_deregister_failed_description">Mandate(s) and UPI Lite are currently active. Please disable UPI Lite, delete all mandates and try de-registering again.</string>
</resources>