NTP-65849 | pay bill view model modularization (#16378)

This commit is contained in:
Mohit Rajput
2025-05-30 00:16:43 -07:00
committed by GitHub
parent 509404436d
commit 2e4494b5a3
6 changed files with 620 additions and 460 deletions

View File

@@ -136,10 +136,6 @@ object NaviBbpsCommonUtils {
return categoryId == CATEGORY_ID_CREDIT_CARD
}
fun isCategoryOfTypeAmountChipsRequired(categoryId: String): Boolean {
return categoryId in listOf(CATEGORY_ID_DTH, CATEGORY_ID_FASTAG)
}
fun isCategoryOfTypeLocationRequired(categoryId: String): Boolean {
return categoryId in
listOf(

View File

@@ -10,16 +10,12 @@ package com.navi.bbps.feature.paybill
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.navi.adverse.flux.provider.GsonProvider.gson
import com.navi.base.cache.repository.NaviCacheRepository
import com.navi.base.model.CtaData
import com.navi.base.utils.DateUtils
import com.navi.base.utils.EMPTY
import com.navi.base.utils.NaviNetworkConnectivity
import com.navi.base.utils.ResourceProvider
import com.navi.base.utils.TrustedTimeAccessor
import com.navi.base.utils.ZERO_STRING
import com.navi.base.utils.isNotNull
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.bbps.R
@@ -32,29 +28,23 @@ import com.navi.bbps.common.CATEGORY_ID_DTH
import com.navi.bbps.common.CATEGORY_ID_MOBILE_POSTPAID
import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID
import com.navi.bbps.common.CoinsSyncManager
import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR
import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR_COMMA_TIME
import com.navi.bbps.common.DISPLAYABLE_MOBILE_NUMBER_KEY
import com.navi.bbps.common.NaviBbpsAnalytics
import com.navi.bbps.common.NaviBbpsScreen
import com.navi.bbps.common.SYMBOL_RUPEE
import com.navi.bbps.common.TAG_BILL_FETCH_ERROR
import com.navi.bbps.common.model.NaviBbpsVmData
import com.navi.bbps.common.model.config.ChipsConfigData
import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig
import com.navi.bbps.common.repository.BbpsCommonRepository
import com.navi.bbps.common.session.DuplicatePaymentBottomSheetViewTracker
import com.navi.bbps.common.session.NaviBbpsSessionHelper
import com.navi.bbps.common.session.ZeroPlatformFeeBottomSheetViewTracker
import com.navi.bbps.common.usecase.FetchBillHandler
import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase
import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase
import com.navi.bbps.common.usecase.RewardNudgeUseCase
import com.navi.bbps.common.utils.NaviBbpsCommonUtils
import com.navi.bbps.common.utils.NaviBbpsCommonUtils.evaluateMvelExpression
import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getBbpsMetricInfo
import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getValidatedAmountNumber
import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isCategoryOfTypeAmountChipsRequired
import com.navi.bbps.common.utils.NaviBbpsDateUtils
import com.navi.bbps.common.utils.getDefaultConfig
import com.navi.bbps.common.utils.getDisplayableAmount
@@ -70,7 +60,6 @@ import com.navi.bbps.feature.category.model.view.BillCategoryEntity
import com.navi.bbps.feature.contactlist.model.view.PhoneContactEntity
import com.navi.bbps.feature.customerinput.model.network.DeviceDetails
import com.navi.bbps.feature.customerinput.model.view.BillDetailsEntity
import com.navi.bbps.feature.customerinput.model.view.BillPeriod
import com.navi.bbps.feature.customerinput.model.view.BillerAdditionalParamsEntity
import com.navi.bbps.feature.customerinput.model.view.BillerDetailsEntity
import com.navi.bbps.feature.destinations.BbpsPostPaymentScreenDestination
@@ -78,10 +67,12 @@ import com.navi.bbps.feature.destinations.BbpsTransactionDetailsScreenDestinatio
import com.navi.bbps.feature.destinations.PrepaidRechargeScreenDestination
import com.navi.bbps.feature.mybills.MyBillsRepository
import com.navi.bbps.feature.mybills.model.view.MyBillEntity
import com.navi.bbps.feature.paybill.helper.BbpsArcHelper
import com.navi.bbps.feature.paybill.helper.NotifyDuplicatePaymentHandler
import com.navi.bbps.feature.paybill.helper.PayBillAmountChipsHelper
import com.navi.bbps.feature.paybill.model.network.PayBillRequest
import com.navi.bbps.feature.paybill.model.network.PayBillResponse
import com.navi.bbps.feature.paybill.model.network.PaymentAmountExactness
import com.navi.bbps.feature.paybill.model.view.AmountChipEntity
import com.navi.bbps.feature.paybill.model.view.CoinUtilisationProperties
import com.navi.bbps.feature.paybill.model.view.CoinUtilisationPropertiesV2
import com.navi.bbps.feature.paybill.model.view.CreditCardAmountType
@@ -107,13 +98,10 @@ import com.navi.common.utils.Constants
import com.navi.common.utils.Constants.LITMUS_EXPERIMENT_NAVIPAY_NAVI_POWER_PLAY
import com.navi.common.utils.TemporaryStorageHelper
import com.navi.common.utils.toJsonObject
import com.navi.pay.tstore.list.model.view.OrderStatusOfView
import com.navi.payment.nativepayment.utils.NaviPaymentRewardsEventBus
import com.navi.payment.nativepayment.utils.getDiscountAdjustedAmount
import com.navi.payment.paymentscreen.utils.PaymentNavigator
import com.navi.payment.tstore.repository.TStoreOrderHandler
import com.navi.payments.shared.feature.arc.constant.ARC_NUDGE_RESPONSE_CACHE_KEY
import com.navi.payments.shared.feature.arc.model.network.ArcNudgeResponse
import com.navi.uitron.utils.isNotNullAndNotEmpty
import com.navi.uitron.utils.orVal
import com.ramcosta.composedestinations.spec.Direction
@@ -134,7 +122,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.joda.time.DateTime
import org.json.JSONObject
typealias OrderReferenceId = String
@@ -158,22 +145,21 @@ constructor(
private val resProvider: ResourceProvider,
private val rewardsNudgeEntityFetchUseCase: RewardNudgeUseCase,
private val naviPaymentRewardEventBus: NaviPaymentRewardsEventBus,
private val naviCacheRepository: NaviCacheRepository,
private val naviBbpsCommonRepository: BbpsCommonRepository,
private val resourceProvider: ResourceProvider,
private val litmusExperimentsUseCase: LitmusExperimentsUseCase,
private val findLastOrderWithSuccessfulPaymentUseCase:
FindLastOrderWithSuccessfulPaymentUseCase,
private val duplicatePaymentBottomSheetViewTracker: DuplicatePaymentBottomSheetViewTracker,
private val zeroPlatformFeeBottomSheetViewTracker: ZeroPlatformFeeBottomSheetViewTracker,
val payBillAmountChipsHelper: PayBillAmountChipsHelper,
val bbpsArcHelper: BbpsArcHelper,
val notifyDuplicatePaymentHandler: NotifyDuplicatePaymentHandler,
) :
NaviBbpsBaseVM(
naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_PAY_BILL_SCREEN)
) {
companion object {
private const val CC_MIN_DUE_AMOUNT_KEY = "MinimumDueAmount"
private const val BILLER_MIN_ACCEPTED_AMOUNT = "Biller_Min_Accepted_Amount"
private const val BILLER_MAX_ACCEPTED_AMOUNT = "Biller_Max_Accepted_Amount"
const val BILLER_MIN_ACCEPTED_AMOUNT = "Biller_Min_Accepted_Amount"
const val BILLER_MAX_ACCEPTED_AMOUNT = "Biller_Max_Accepted_Amount"
}
private val naviBbpsAnalytics: NaviBbpsAnalytics.PayBill = NaviBbpsAnalytics.INSTANCE.PayBill()
@@ -202,13 +188,10 @@ constructor(
private val _showErrorText = MutableStateFlow(false)
val showErrorText = _showErrorText.asStateFlow()
private val _amountChipEntityList = MutableStateFlow<List<AmountChipEntity>>(emptyList())
val amountChipEntityList = _amountChipEntityList.asStateFlow()
private val _shouldAutoFocusOnAmount = MutableStateFlow(false)
val shouldAutoFocusOnAmount = _shouldAutoFocusOnAmount.asStateFlow()
val paymentAmountExactness = MutableStateFlow(PaymentAmountExactness.EXACT)
private val paymentAmountExactness = MutableStateFlow(PaymentAmountExactness.EXACT)
// ignore this for case when isAdhoc is true, for others use it to show error text
private val _isAdhoc = MutableStateFlow(false)
@@ -284,21 +267,11 @@ constructor(
private val _isConsentViewVisible = MutableStateFlow(false)
val isConsentViewVisible = _isConsentViewVisible.asStateFlow()
private val _isArcProtected = MutableStateFlow<Boolean>(false)
val isArcProtected = _isArcProtected.asStateFlow()
private val amountChips = MutableStateFlow(emptyList<String>())
private val _showBillDetailsUpdatedBottomSheet = MutableSharedFlow<Boolean>()
val showBillDetailsUpdatedBottomSheet = _showBillDetailsUpdatedBottomSheet.asSharedFlow()
private val _showDuplicatePaymentBottomSheet = MutableSharedFlow<Boolean>()
val showDuplicatePaymentBottomSheet = _showDuplicatePaymentBottomSheet.asSharedFlow()
var isPayButtonClicked = false
var arcNudgeResponse: ArcNudgeResponse? = null
@Inject lateinit var paymentNavigator: PaymentNavigator
val errorMessageId =
@@ -336,13 +309,17 @@ constructor(
private val _isLottieAnimationShown = MutableStateFlow(false)
val isLottieAnimationShown = _isLottieAnimationShown.asStateFlow()
var isBillFetchFailed = false
private var isBillFetchFailed = false
init {
viewModelScope.launch(dispatcherProvider.io) {
// Concurrent calls section
launch { initConfig() }
launch { updateArcProtectedStatus() }
launch {
bbpsArcHelper.updateArcProtectedStatus(
billerId = billDetailsEntity.value?.billerId.orEmpty()
)
}
launch { getLitmusExperimentValues() }
launch { fetchBillAndUpdateScreen() }
launch { updateScreenState() }
@@ -476,6 +453,30 @@ constructor(
_isBillLoading.update { isLoading }
}
private fun notifyDuplicatePayment() {
notifyDuplicatePaymentHandler.notifyDuplicatePayment(
coroutineScope = viewModelScope,
source = source,
initialSource = initialSource,
billDetailsEntity = billDetailsEntity.value,
onPayBillBottomSheetType = { payBillBottomSheetType ->
_payBillBottomSheetType.update { payBillBottomSheetType }
},
navigateToOrderHistoryDetailsScreen = { orderReferenceId ->
viewModelScope.launch(dispatcherProvider.io) {
_navigateToOrderDetailsScreen.emit(
Pair(first = true, second = orderReferenceId)
)
}
},
navigateToBillHistoryScreen = {
viewModelScope.launch(dispatcherProvider.io) {
_navigateToBillHistoryScreen.emit(value = true)
}
},
)
}
private suspend fun fetchBillAndUpdateScreen() {
if (billDetailsEntity.value == null) {
if (!naviNetworkConnectivity.isInternetConnected()) {
@@ -582,137 +583,28 @@ constructor(
}
}
if (
!isCategoryOfTypeAmountChipsRequired(
!payBillAmountChipsHelper.isCategoryOfTypeAmountChipsRequired(
categoryId = billCategoryEntity?.categoryId.orEmpty()
)
) {
updateShouldAutoFocusOnAmount(paymentAmount = paymentAmount.value)
return
}
updateAmountChips(chipsConfigData = naviBbpsDefaultConfig.value.chipsConfigData)
}
private fun updateAmountChips(chipsConfigData: ChipsConfigData) {
var minChipAmount = chipsConfigData.genericMinAmount
var maxChipAmount = chipsConfigData.genericMaxAmount
when (paymentAmountExactness.value) {
PaymentAmountExactness.EXACT_AND_ABOVE -> {
initialPaymentAmount.value.toDoubleOrNull()?.let {
minChipAmount =
minChipAmount.coerceAtLeast(initialPaymentAmount.value.toDouble().toInt())
}
}
PaymentAmountExactness.EXACT_AND_BELOW -> {
initialPaymentAmount.value.toDoubleOrNull()?.let {
maxChipAmount =
maxChipAmount.coerceAtMost(initialPaymentAmount.value.toDouble().toInt())
}
}
else -> {}
}
val billerLevelMinAmountEntity =
billerAdditionalParams.value.find { it.paramName == BILLER_MIN_ACCEPTED_AMOUNT }
val billerLevelMaxAmountEntity =
billerAdditionalParams.value.find { it.paramName == BILLER_MAX_ACCEPTED_AMOUNT }
billerLevelMinAmountEntity?.value?.toIntOrNull()?.let {
minChipAmount = minChipAmount.coerceAtLeast(billerLevelMinAmountEntity.value.toInt())
}
billerLevelMaxAmountEntity?.value?.toIntOrNull()?.let {
maxChipAmount = maxChipAmount.coerceAtMost(billerLevelMaxAmountEntity.value.toInt())
}
viewModelScope.launch(dispatcherProvider.io) {
val secondChipAmount =
calculateChipAmountFromFormula(
minChipAmount = minChipAmount.toInt(),
maxChipAmount = maxChipAmount.toInt(),
chipFormulaExpression = chipsConfigData.secondChipFormulaExpression,
)
if (secondChipAmount == ZERO_STRING) {
updateShouldAutoFocusOnAmount(paymentAmount = paymentAmount.value)
return@launch
}
val thirdChipAmount =
calculateChipAmountFromFormula(
minChipAmount = minChipAmount.toInt(),
maxChipAmount = maxChipAmount.toInt(),
chipFormulaExpression = chipsConfigData.thirdChipFormulaExpression,
)
if (thirdChipAmount == ZERO_STRING) {
updateShouldAutoFocusOnAmount(paymentAmount = paymentAmount.value)
return@launch
}
val shouldShowChip =
maxChipAmount - minChipAmount >= chipsConfigData.minimumRequiredDifference
if (!shouldShowChip) {
updateShouldAutoFocusOnAmount(paymentAmount = paymentAmount.value)
return@launch
}
amountChips.update {
listOf(
minChipAmount.toString(),
secondChipAmount,
thirdChipAmount,
maxChipAmount.toString(),
)
}
transformAmountChipsToAmountChipEntity(amountChips = amountChips.value)
if (
initialPaymentAmount.value == ZERO_STRING ||
initialPaymentAmount.value == amountChipEntityList.value.first().amount
) {
updatePaymentAmount(newAmountValue = amountChipEntityList.value.first().amount)
updateAmountChipEntity(amount = amountChipEntityList.value.first().amount)
}
}
}
private suspend fun calculateChipAmountFromFormula(
minChipAmount: Int,
maxChipAmount: Int,
chipFormulaExpression: String,
): String {
return evaluateMvelExpression(
key = chipFormulaExpression,
data = mapOf("minChipAmount" to minChipAmount, "maxChipAmount" to maxChipAmount),
defaultValue = ZERO_STRING,
)
}
private fun transformAmountChipsToAmountChipEntity(amountChips: List<String>) {
_amountChipEntityList.update {
amountChips.map { amount -> AmountChipEntity(amount = amount, isSelected = false) }
}
}
open fun updateAmountChipEntity(amount: String) {
amountChipEntityList.value.isNotNull().let {
updateAmountChipEntityList(
amountChipList =
amountChipEntityList.value.map { amountChipEntity ->
amountChipEntity.copy(isSelected = amountChipEntity.amount == amount)
}
payBillAmountChipsHelper.updateAmountChips(
chipsConfigData = naviBbpsDefaultConfig.value.chipsConfigData,
paymentAmountExactness = paymentAmountExactness.value,
initialPaymentAmount = initialPaymentAmount.value,
paymentAmount = paymentAmount.value,
billerAdditionalParams = billerAdditionalParams.value,
onUpdateShouldAutoFocusOnAmount = { paymentAmount ->
updateShouldAutoFocusOnAmount(paymentAmount = paymentAmount)
},
onUpdatePaymentAmount = { amount -> updatePaymentAmount(newAmountValue = amount) },
)
}
}
private fun updateAmountChipEntityList(amountChipList: List<AmountChipEntity>) {
_amountChipEntityList.update { amountChipList }
}
private fun updateCreditCardPaymentOptions(billDetailsEntity: BillDetailsEntity) {
val creditCardPaymentOptions = mutableListOf<CreditCardPaymentOption>()
@@ -875,8 +767,12 @@ constructor(
}
}
if (isCategoryOfTypeAmountChipsRequired(billCategoryEntity?.categoryId.orEmpty())) {
updateAmountChipEntity(amount = newAmountValue)
if (
payBillAmountChipsHelper.isCategoryOfTypeAmountChipsRequired(
billCategoryEntity?.categoryId.orEmpty()
)
) {
payBillAmountChipsHelper.updateSelectedChip(amount = newAmountValue)
}
}
@@ -1326,7 +1222,7 @@ constructor(
isIPLPowerPlayThemeExperimentEnabled =
isIPLPowerPlayThemeExperimentEnabled,
billCategoryEntity = billCategoryEntity,
showScratchAnimation = !upiRequestId.isNullOrEmpty(),
showScratchAnimation = upiRequestId.isNotEmpty(),
),
second = true,
)
@@ -1550,33 +1446,40 @@ constructor(
fun onCreditCardPaymentOptionSelected(creditCardPaymentOption: CreditCardPaymentOption) {
if (creditCardPaymentOption.isSelected) return
creditCardPaymentOptions.indexOf(creditCardPaymentOption).let { selectedIndex ->
creditCardPaymentOptions.forEachIndexed { index, option ->
creditCardPaymentOptions[index] = option.copy(isSelected = index == selectedIndex)
}
}
updateCreditCardPaymentOptionSelection(creditCardPaymentOption)
when (creditCardPaymentOption.type) {
CreditCardAmountType.TOTAL -> {
updatePaymentAmount(
newAmountValue = billDetailsEntity.value?.amount.orEmpty().getNormalisedAmount()
)
val totalAmount = billDetailsEntity.value?.amount.orEmpty().getNormalisedAmount()
updatePaymentAmount(newAmountValue = totalAmount)
}
CreditCardAmountType.MINIMUM -> {
val minAmountParamEntity =
billDetailsEntity.value?.billerAdditionalParams?.find {
it.paramName == CC_MIN_DUE_AMOUNT_KEY
}
updatePaymentAmount(
newAmountValue = minAmountParamEntity?.value.orEmpty().getNormalisedAmount()
)
val minAmountParamEntity = findMinimumDueAmountParam()
val minimumAmount = minAmountParamEntity?.value.orEmpty().getNormalisedAmount()
updatePaymentAmount(newAmountValue = minimumAmount)
}
CreditCardAmountType.OTHER -> {
updatePaymentAmount(newAmountValue = "")
}
}
}
private fun updateCreditCardPaymentOptionSelection(selectedOption: CreditCardPaymentOption) {
val selectedIndex = creditCardPaymentOptions.indexOf(selectedOption)
creditCardPaymentOptions.forEachIndexed { index, option ->
creditCardPaymentOptions[index] = option.copy(isSelected = index == selectedIndex)
}
}
private fun findMinimumDueAmountParam(): BillerAdditionalParamsEntity? {
return billDetailsEntity.value?.billerAdditionalParams?.find {
it.paramName == CC_MIN_DUE_AMOUNT_KEY
}
}
private fun updateBottomRewardsNudgeEntity() {
viewModelScope.launch(dispatcherProvider.io) {
val nudgeDetailEntity = rewardsNudgeEntityFetchUseCase.execute()
@@ -1631,281 +1534,10 @@ constructor(
}
}
private fun updateArcProtectedStatus() {
viewModelScope.launch(Dispatchers.IO) {
arcNudgeResponse =
try {
gson.fromJson(
naviCacheRepository.get(key = ARC_NUDGE_RESPONSE_CACHE_KEY)?.value,
ArcNudgeResponse::class.java,
)
} catch (_: Exception) {
null
}
val isArcProtected =
arcNudgeResponse?.isArcProtected.orFalse() &&
arcNudgeResponse
?.blockedBillerIds
?.contains(billDetailsEntity.value?.billerId) == false
_isArcProtected.update { isArcProtected }
}
}
suspend fun onSavedBillCtaClicked(apiUrl: String) {
naviBbpsCommonRepository.getSavedBillsDetailsOnError(
path = apiUrl,
metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName),
)
}
fun notifyDuplicatePayment() {
suspend fun notifySuccessBottomSheet(
billId: String,
billPeriod: String,
billDate: String,
orderTimestamp: DateTime,
orderReferenceId: String,
orderStatus: OrderStatusOfView,
orderAmount: String,
billerId: String,
formattedAmount: String,
formattedDate: String,
) {
val currentBillGenerationDate: DateTime =
DateUtils.getDateTimeObjectFromDateTimeString(
dateTime = billDate,
format = DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR,
)
val nextBillGenerationDate =
currentBillGenerationDate.apply {
when (billPeriod) {
BillPeriod.BILL_PERIOD_DAILY.name -> plusDays(1)
BillPeriod.BILL_PERIOD_WEEKLY.name -> plusWeeks(1)
BillPeriod.BILL_PERIOD_MONTHLY.name -> plusMonths(1)
BillPeriod.BILL_PERIOD_BIMONTHLY.name -> plusMonths(2)
BillPeriod.BILL_PERIOD_QUARTERLY.name -> plusMonths(3)
BillPeriod.BILL_PERIOD_HALFYEARLY.name -> plusMonths(6)
BillPeriod.BILL_PERIOD_YEARLY.name -> plusYears(1)
}
}
val currentBillingCycle = currentBillGenerationDate..<nextBillGenerationDate
if (DateTime.now() in currentBillingCycle && orderTimestamp in currentBillingCycle) {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetLanded(
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
duplicatePaymentBottomSheetViewTracker.incrementViewCount(billId)
_payBillBottomSheetType.update {
PayBillBottomSheetType.DuplicatePayment(
headerMainlineTextResId = R.string.bbps_duplicate_payment_success_title,
descriptionMainlineTextResId =
R.string.bbps_duplicate_payment_success_description,
descriptionAnnotatedAmountText = formattedAmount,
descriptionAnnotatedDateText = formattedDate,
primaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_success_primary_cta_text
),
secondaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_success_secondary_cta_text
),
onPrimaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "ViewPastTransactions",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
viewModelScope.safeLaunch {
_navigateToBillHistoryScreen.emit(value = true)
}
},
onSecondaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "PayAgain",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
viewModelScope.safeLaunch {
_showDuplicatePaymentBottomSheet.emit(value = false)
}
},
)
}
_showDuplicatePaymentBottomSheet.emit(value = true)
}
}
suspend fun notifyPendingBottomSheet(
billId: String,
orderReferenceId: String,
orderStatus: OrderStatusOfView,
orderTimestamp: DateTime,
orderAmount: String,
billerId: String,
formattedAmount: String,
formattedDate: String,
) {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetLanded(
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
duplicatePaymentBottomSheetViewTracker.incrementViewCount(billId)
_payBillBottomSheetType.update {
PayBillBottomSheetType.DuplicatePayment(
headerMainlineTextResId = R.string.bbps_duplicate_payment_pending_title,
descriptionMainlineTextResId =
R.string.bbps_duplicate_payment_pending_description,
descriptionAnnotatedAmountText = formattedAmount,
descriptionAnnotatedDateText = formattedDate,
primaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_pending_primary_cta_text
),
secondaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_pending_secondary_cta_text
),
onPrimaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "ViewTransactionDetails",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
viewModelScope.safeLaunch {
_navigateToOrderDetailsScreen.emit(
value = Pair(first = true, second = orderReferenceId)
)
}
},
onSecondaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "PayAgain",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
viewModelScope.safeLaunch {
_showDuplicatePaymentBottomSheet.emit(value = false)
}
},
)
}
_showDuplicatePaymentBottomSheet.emit(value = true)
}
viewModelScope.safeLaunch(coroutineContext = dispatcherProvider.io) {
billDetailsEntity.value?.let { billDetails ->
val billId = billDetails.billId
val billerId = billDetails.billerId
val billDate = billDetails.billDate
val billPeriod = billDetails.billPeriod
if (duplicatePaymentBottomSheetViewTracker.shouldShowBottomSheet(billId = billId)) {
val latestOrder = findLastOrderWithSuccessfulPaymentUseCase.find(billId)
latestOrder?.let {
val orderReferenceId = latestOrder.orderReferenceId
val orderStatus = latestOrder.orderStatusOfView
val orderTimestamp = latestOrder.orderTimestamp
val orderAmount = latestOrder.amount
val formattedDate =
DateUtils.getFormattedDateTimeAsStringFromDateTimeObject(
dateTime = orderTimestamp,
format = DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR,
)
val formattedAmount = SYMBOL_RUPEE + orderAmount.getDisplayableAmount()
when (orderStatus) {
OrderStatusOfView.Debit -> {
notifySuccessBottomSheet(
billId = billId,
billerId = billerId,
billDate = billDate,
billPeriod = billPeriod,
orderReferenceId = orderReferenceId,
orderStatus = orderStatus,
orderTimestamp = orderTimestamp,
orderAmount = orderAmount,
formattedDate = formattedDate,
formattedAmount = formattedAmount,
)
}
OrderStatusOfView.Pending -> {
notifyPendingBottomSheet(
billId = billId,
billerId = billerId,
orderReferenceId = orderReferenceId,
orderStatus = orderStatus,
orderTimestamp = orderTimestamp,
orderAmount = orderAmount,
formattedDate = formattedDate,
formattedAmount = formattedAmount,
)
}
else -> {
// Do nothing
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.paybill.helper
import com.navi.adverse.flux.provider.GsonProvider.gson
import com.navi.base.cache.repository.NaviCacheRepository
import com.navi.base.utils.orFalse
import com.navi.payments.shared.feature.arc.constant.ARC_NUDGE_RESPONSE_CACHE_KEY
import com.navi.payments.shared.feature.arc.model.network.ArcNudgeResponse
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class BbpsArcHelper @Inject constructor(private val naviCacheRepository: NaviCacheRepository) {
private val _isArcProtected = MutableStateFlow(false)
val isArcProtected: StateFlow<Boolean> = _isArcProtected.asStateFlow()
suspend fun updateArcProtectedStatus(billerId: String) {
val arcNudgeResponse = fetchArcNudgeResponse()
val isProtected = determineArcProtectionStatus(arcNudgeResponse, billerId)
_isArcProtected.update { isProtected }
}
private suspend fun fetchArcNudgeResponse(): ArcNudgeResponse? {
return try {
naviCacheRepository.get(key = ARC_NUDGE_RESPONSE_CACHE_KEY)?.value?.let { cacheValue ->
gson.fromJson(cacheValue, ArcNudgeResponse::class.java)
}
} catch (_: Exception) {
null
}
}
private fun determineArcProtectionStatus(
response: ArcNudgeResponse?,
billerId: String,
): Boolean {
// A biller is ARC protected if:
// 1. ARC protection is globally enabled
// 2. The biller is not in the blocked billers list
return response?.isArcProtected.orFalse() &&
response?.blockedBillerIds?.contains(billerId) == false
}
}

View File

@@ -0,0 +1,303 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.paybill.helper
import com.navi.base.utils.DateUtils
import com.navi.base.utils.ResourceProvider
import com.navi.bbps.R
import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR
import com.navi.bbps.common.NaviBbpsAnalytics
import com.navi.bbps.common.SYMBOL_RUPEE
import com.navi.bbps.common.session.DuplicatePaymentBottomSheetViewTracker
import com.navi.bbps.common.session.NaviBbpsSessionHelper
import com.navi.bbps.common.usecase.FindLastOrderWithSuccessfulPaymentUseCase
import com.navi.bbps.common.utils.getDisplayableAmount
import com.navi.bbps.feature.customerinput.model.view.BillDetailsEntity
import com.navi.bbps.feature.customerinput.model.view.BillPeriod
import com.navi.bbps.feature.paybill.model.view.PayBillBottomSheetType
import com.navi.common.di.CoroutineDispatcherProvider
import com.navi.common.utils.safeLaunch
import com.navi.pay.tstore.list.model.view.OrderStatusOfView
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import org.joda.time.DateTime
/**
* Handler responsible for detecting and notifying users about duplicate bill payments. Shows
* appropriate bottom sheets based on the payment status and handles user actions.
*/
class NotifyDuplicatePaymentHandler
@Inject
constructor(
private val duplicatePaymentBottomSheetViewTracker: DuplicatePaymentBottomSheetViewTracker,
private val resourceProvider: ResourceProvider,
private val naviBbpsSessionHelper: NaviBbpsSessionHelper,
private val dispatcherProvider: CoroutineDispatcherProvider,
private val findLastOrderWithSuccessfulPaymentUseCase: FindLastOrderWithSuccessfulPaymentUseCase,
) {
private val naviBbpsAnalytics: NaviBbpsAnalytics.PayBill = NaviBbpsAnalytics.INSTANCE.PayBill()
private val _showDuplicatePaymentBottomSheet = MutableSharedFlow<Boolean>()
val showDuplicatePaymentBottomSheet = _showDuplicatePaymentBottomSheet.asSharedFlow()
fun notifyDuplicatePayment(
coroutineScope: CoroutineScope,
source: String,
initialSource: String,
billDetailsEntity: BillDetailsEntity?,
onPayBillBottomSheetType: (PayBillBottomSheetType) -> Unit,
navigateToOrderHistoryDetailsScreen: (String) -> Unit,
navigateToBillHistoryScreen: () -> Unit,
) {
suspend fun notifySuccessBottomSheet(
billId: String,
billPeriod: String,
billDate: String,
orderTimestamp: DateTime,
orderReferenceId: String,
orderStatus: OrderStatusOfView,
orderAmount: String,
billerId: String,
formattedAmount: String,
formattedDate: String,
) {
val currentBillGenerationDate =
DateUtils.getDateTimeObjectFromDateTimeString(
dateTime = billDate,
format = DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR,
)
val nextBillGenerationDate =
currentBillGenerationDate.apply {
when (billPeriod) {
BillPeriod.BILL_PERIOD_DAILY.name -> plusDays(1)
BillPeriod.BILL_PERIOD_WEEKLY.name -> plusWeeks(1)
BillPeriod.BILL_PERIOD_MONTHLY.name -> plusMonths(1)
BillPeriod.BILL_PERIOD_BIMONTHLY.name -> plusMonths(2)
BillPeriod.BILL_PERIOD_QUARTERLY.name -> plusMonths(3)
BillPeriod.BILL_PERIOD_HALFYEARLY.name -> plusMonths(6)
BillPeriod.BILL_PERIOD_YEARLY.name -> plusYears(1)
}
}
val currentBillingCycle = currentBillGenerationDate..<nextBillGenerationDate
if (DateTime.now() in currentBillingCycle && orderTimestamp in currentBillingCycle) {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetLanded(
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
duplicatePaymentBottomSheetViewTracker.incrementViewCount(billId)
onPayBillBottomSheetType(
PayBillBottomSheetType.DuplicatePayment(
headerMainlineTextResId = R.string.bbps_duplicate_payment_success_title,
descriptionMainlineTextResId =
R.string.bbps_duplicate_payment_success_description,
descriptionAnnotatedAmountText = formattedAmount,
descriptionAnnotatedDateText = formattedDate,
primaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_success_primary_cta_text
),
secondaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_success_secondary_cta_text
),
onPrimaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "ViewPastTransactions",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute =
naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
navigateToBillHistoryScreen()
},
onSecondaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "PayAgain",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute =
naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
coroutineScope.safeLaunch {
_showDuplicatePaymentBottomSheet.emit(value = false)
}
},
)
)
_showDuplicatePaymentBottomSheet.emit(value = true)
}
}
suspend fun notifyPendingBottomSheet(
billId: String,
orderReferenceId: String,
orderStatus: OrderStatusOfView,
orderTimestamp: DateTime,
orderAmount: String,
billerId: String,
formattedAmount: String,
formattedDate: String,
) {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetLanded(
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
duplicatePaymentBottomSheetViewTracker.incrementViewCount(billId)
onPayBillBottomSheetType(
PayBillBottomSheetType.DuplicatePayment(
headerMainlineTextResId = R.string.bbps_duplicate_payment_pending_title,
descriptionMainlineTextResId =
R.string.bbps_duplicate_payment_pending_description,
descriptionAnnotatedAmountText = formattedAmount,
descriptionAnnotatedDateText = formattedDate,
primaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_pending_primary_cta_text
),
secondaryButtonText =
resourceProvider.getString(
resId = R.string.bbps_duplicate_payment_pending_secondary_cta_text
),
onPrimaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "ViewTransactionDetails",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
navigateToOrderHistoryDetailsScreen(orderReferenceId)
},
onSecondaryButtonClicked = {
naviBbpsAnalytics.onDuplicatePaymentBottomSheetActionClicked(
clickAction = "PayAgain",
previousOrderReferenceId = orderReferenceId,
previousOrderStatus = orderStatus,
previousOrderTimestamp = orderTimestamp,
previousOrderAmount = orderAmount,
billId = billId,
billerId = billerId,
sessionAttribute = naviBbpsSessionHelper.getNaviBbpsSessionAttributes(),
source = source,
initialSource = initialSource,
)
coroutineScope.safeLaunch {
_showDuplicatePaymentBottomSheet.emit(value = false)
}
},
)
)
_showDuplicatePaymentBottomSheet.emit(value = true)
}
coroutineScope.safeLaunch(coroutineContext = dispatcherProvider.io) {
billDetailsEntity?.let { billDetails ->
val billId = billDetails.billId
val billerId = billDetails.billerId
val billDate = billDetails.billDate
val billPeriod = billDetails.billPeriod
if (duplicatePaymentBottomSheetViewTracker.shouldShowBottomSheet(billId = billId)) {
val latestOrder = findLastOrderWithSuccessfulPaymentUseCase.find(billId)
latestOrder?.let {
val orderReferenceId = latestOrder.orderReferenceId
val orderStatus = latestOrder.orderStatusOfView
val orderTimestamp = latestOrder.orderTimestamp
val orderAmount = latestOrder.amount
val formattedDate =
DateUtils.getFormattedDateTimeAsStringFromDateTimeObject(
dateTime = orderTimestamp,
format = DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR,
)
val formattedAmount = SYMBOL_RUPEE + orderAmount.getDisplayableAmount()
when (orderStatus) {
OrderStatusOfView.Debit -> {
notifySuccessBottomSheet(
billId = billId,
billerId = billerId,
billDate = billDate,
billPeriod = billPeriod,
orderReferenceId = orderReferenceId,
orderStatus = orderStatus,
orderTimestamp = orderTimestamp,
orderAmount = orderAmount,
formattedDate = formattedDate,
formattedAmount = formattedAmount,
)
}
OrderStatusOfView.Pending -> {
notifyPendingBottomSheet(
billId = billId,
billerId = billerId,
orderReferenceId = orderReferenceId,
orderStatus = orderStatus,
orderTimestamp = orderTimestamp,
orderAmount = orderAmount,
formattedDate = formattedDate,
formattedAmount = formattedAmount,
)
}
else -> {
// Do nothing for other order statuses
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,174 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.bbps.feature.paybill.helper
import com.navi.base.utils.ZERO_STRING
import com.navi.bbps.common.CATEGORY_ID_DTH
import com.navi.bbps.common.CATEGORY_ID_FASTAG
import com.navi.bbps.common.model.config.ChipsConfigData
import com.navi.bbps.common.utils.NaviBbpsCommonUtils.evaluateMvelExpression
import com.navi.bbps.feature.customerinput.model.view.BillerAdditionalParamsEntity
import com.navi.bbps.feature.paybill.PayBillViewModel.Companion.BILLER_MAX_ACCEPTED_AMOUNT
import com.navi.bbps.feature.paybill.PayBillViewModel.Companion.BILLER_MIN_ACCEPTED_AMOUNT
import com.navi.bbps.feature.paybill.model.network.PaymentAmountExactness
import com.navi.bbps.feature.paybill.model.view.AmountChipEntity
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
/**
* Helper class that manages amount chips for bill payments Handles chip calculation, selection, and
* state management
*/
class PayBillAmountChipsHelper @Inject constructor() {
private val _amountChipEntityList = MutableStateFlow<List<AmountChipEntity>>(emptyList())
val amountChipEntityList: StateFlow<List<AmountChipEntity>> =
_amountChipEntityList.asStateFlow()
private val _availableAmounts = MutableStateFlow(emptyList<String>())
suspend fun updateAmountChips(
chipsConfigData: ChipsConfigData,
paymentAmountExactness: PaymentAmountExactness,
initialPaymentAmount: String,
paymentAmount: String,
billerAdditionalParams: List<BillerAdditionalParamsEntity>,
onUpdateShouldAutoFocusOnAmount: (String) -> Unit,
onUpdatePaymentAmount: (String) -> Unit,
): Boolean {
val bounds =
calculateAmountBounds(
chipsConfigData,
paymentAmountExactness,
initialPaymentAmount,
billerAdditionalParams,
)
if (!validateBounds(bounds, chipsConfigData.minimumRequiredDifference)) {
onUpdateShouldAutoFocusOnAmount(paymentAmount)
return false
}
val secondAmount =
calculateChipAmountFromFormula(
bounds.first,
bounds.second,
chipsConfigData.secondChipFormulaExpression,
)
if (secondAmount == ZERO_STRING) {
onUpdateShouldAutoFocusOnAmount(paymentAmount)
return false
}
val thirdAmount =
calculateChipAmountFromFormula(
bounds.first,
bounds.second,
chipsConfigData.thirdChipFormulaExpression,
)
if (thirdAmount == ZERO_STRING) {
onUpdateShouldAutoFocusOnAmount(paymentAmount)
return false
}
_availableAmounts.update {
listOf(bounds.first.toString(), secondAmount, thirdAmount, bounds.second.toString())
}
transformToChipEntities()
if (shouldAutoSelectFirstChip(initialPaymentAmount)) {
val firstAmount = amountChipEntityList.value.firstOrNull()?.amount ?: return false
onUpdatePaymentAmount(firstAmount)
updateSelectedChip(firstAmount)
}
return true
}
fun updateSelectedChip(amount: String) {
_amountChipEntityList.update { currentList ->
currentList.map { chip -> chip.copy(isSelected = chip.amount == amount) }
}
}
private fun calculateAmountBounds(
chipsConfigData: ChipsConfigData,
paymentAmountExactness: PaymentAmountExactness,
initialPaymentAmount: String,
billerAdditionalParams: List<BillerAdditionalParamsEntity>,
): Pair<Int, Int> {
var minAmount = chipsConfigData.genericMinAmount
var maxAmount = chipsConfigData.genericMaxAmount
when (paymentAmountExactness) {
PaymentAmountExactness.EXACT_AND_ABOVE -> {
initialPaymentAmount.toDoubleOrNull()?.let { amount ->
minAmount = minAmount.coerceAtLeast(amount.toInt())
}
}
PaymentAmountExactness.EXACT_AND_BELOW -> {
initialPaymentAmount.toDoubleOrNull()?.let { amount ->
maxAmount = maxAmount.coerceAtMost(amount.toInt())
}
}
else -> {
// No adjustment needed
}
}
billerAdditionalParams
.find { it.paramName == BILLER_MIN_ACCEPTED_AMOUNT }
?.value
?.toIntOrNull()
?.let { billerMin -> minAmount = minAmount.coerceAtLeast(billerMin) }
billerAdditionalParams
.find { it.paramName == BILLER_MAX_ACCEPTED_AMOUNT }
?.value
?.toIntOrNull()
?.let { billerMax -> maxAmount = maxAmount.coerceAtMost(billerMax) }
return minAmount to maxAmount
}
private fun validateBounds(bounds: Pair<Int, Int>, minimumDifference: Int): Boolean {
return bounds.second - bounds.first >= minimumDifference
}
private suspend fun calculateChipAmountFromFormula(
minAmount: Int,
maxAmount: Int,
formula: String,
): String {
return evaluateMvelExpression(
key = formula,
data = mapOf("minChipAmount" to minAmount, "maxChipAmount" to maxAmount),
defaultValue = ZERO_STRING,
)
}
private fun shouldAutoSelectFirstChip(initialPaymentAmount: String): Boolean {
return initialPaymentAmount == ZERO_STRING ||
amountChipEntityList.value.firstOrNull()?.let { initialPaymentAmount == it.amount }
?: false
}
private fun transformToChipEntities() {
_amountChipEntityList.update {
_availableAmounts.value.map { amount ->
AmountChipEntity(amount = amount, isSelected = false)
}
}
}
fun isCategoryOfTypeAmountChipsRequired(categoryId: String): Boolean {
return categoryId in listOf(CATEGORY_ID_DTH, CATEGORY_ID_FASTAG)
}
}

View File

@@ -298,7 +298,8 @@ fun PayBillScreen(
val creditCardPaymentOptions = payBillViewModel.creditCardPaymentOptions
val payBillBottomSheetType by
payBillViewModel.payBillBottomSheetType.collectAsStateWithLifecycle()
val amountChipEntityList by payBillViewModel.amountChipEntityList.collectAsStateWithLifecycle()
val amountChipEntityList by
payBillViewModel.payBillAmountChipsHelper.amountChipEntityList.collectAsStateWithLifecycle()
val offerDataList by payBillViewModel.offerDataList.collectAsStateWithLifecycle()
val coinBurnData by payBillViewModel.coinBurnData.collectAsStateWithLifecycle()
val amountAfterCoinDiscount by
@@ -311,7 +312,8 @@ fun PayBillScreen(
payBillViewModel.naviBbpsDefaultConfig.collectAsStateWithLifecycle()
val isConsentViewVisible by payBillViewModel.isConsentViewVisible.collectAsStateWithLifecycle()
val isConsentProvided by payBillViewModel.isConsentProvided.collectAsStateWithLifecycle()
val isArcProtected by payBillViewModel.isArcProtected.collectAsStateWithLifecycle()
val isArcProtected by
payBillViewModel.bbpsArcHelper.isArcProtected.collectAsStateWithLifecycle()
val sortedOfferList by
remember(offerDataList, paymentAmount) {
@@ -576,14 +578,14 @@ fun PayBillScreen(
}
LaunchedEffect(Unit) {
payBillViewModel.showDuplicatePaymentBottomSheet.collectLatest {
showDuplicatePaymentBottomSheet ->
if (showDuplicatePaymentBottomSheet) {
openSheet()
} else {
closeSheet()
payBillViewModel.notifyDuplicatePaymentHandler.showDuplicatePaymentBottomSheet
.collectLatest { showDuplicatePaymentBottomSheet ->
if (showDuplicatePaymentBottomSheet) {
openSheet()
} else {
closeSheet()
}
}
}
}
LaunchedEffect(bottomSheetState.isVisible) {