NTP-65849 | pay bill view model modularization (#16378)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user