NTP-13380 | ANMOL AGRAWAL | Net Banking Implementation for AMC (#13943)

This commit is contained in:
Anmol Agrawal
2024-12-04 20:01:02 +05:30
committed by GitHub
parent 653968f742
commit e96dbc328f
21 changed files with 323 additions and 191 deletions

View File

@@ -89,7 +89,6 @@ import com.navi.common.utils.log
import com.navi.common.utils.stringToJsonObject
import com.navi.naviwidgets.models.response.amc.PaymentCardData.Companion.PAYMENT_CARD_ID
import com.navi.payment.listener.PaymentListener
import com.navi.payment.model.common.PaymentSdkTypes
import com.navi.payment.model.paymentmethod.PaymentMethodResponse
import com.navi.payment.nativepayment.router.NaviPaymentRouter
import com.navi.payment.nativepayment.sharedviewmodel.NaviCheckoutViewModel
@@ -117,11 +116,8 @@ class FundBuyActivity :
?.getString(
com.navi.payment.utils.Constants.STATUS.uppercase(Locale.getDefault())
)
when (status) {
PaymentSdkTypes.DISMISS_LOADER.name -> {
// NO-OP
}
PaymentSdkTypes.TRANSACTION_SUCCESS.name -> {
when (status?.contains(SUCCESS, ignoreCase = true)) {
true -> {
val keyData = result?.data?.extras?.getString(KEY_DATA)
val keyDataJsonObject = keyData?.stringToJsonObject()
val upiRequestId = getUpiRequestId(keyDataJsonObject)

View File

@@ -451,6 +451,13 @@ class NaviPaymentAnalytics private constructor() {
eventValues = eventAttributes
)
}
fun emptyBanksList() {
NaviTrackEvent.trackEventOnClickStream(
eventName =
"${NAVI_PMT}_${NaviPaymentAnalyticScreenName.NET_BANKING_SCREEN.screenName}_EmptyBanksList"
)
}
}
inner class CardScreen {

View File

@@ -26,7 +26,7 @@ fun NPSCardsWidget(
cardHeading: String
) {
Column(modifier = Modifier.fillMaxWidth()) {
NpsCardsHeader(header = cardHeading)
NpsPaymentInstrumentHeader(header = cardHeading)
Spacer(modifier = Modifier.height(16.dp))
NPSAddCard(
cardHeadline = stringResource(id = R.string.enter_card_details),

View File

@@ -142,7 +142,7 @@ fun NPSAddCard(
}
@Composable
fun NpsCardsHeader(
fun NpsPaymentInstrumentHeader(
header: String = EMPTY,
) {
Row(

View File

@@ -51,7 +51,7 @@ fun NPSNaviUpiWidget(
) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
NpsCardsHeader(header = stringResource(id = R.string.navi_upi_card_header))
NpsPaymentInstrumentHeader(header = stringResource(id = R.string.navi_upi_card_header))
if (naviCoinState.coinEarnBannerDetails.isNotNull()) {
NpsCoinEarnBanner(
amount = naviCoinState.coinEarnBannerDetails?.amount,

View File

@@ -7,31 +7,154 @@
package com.navi.payment.nativepayment.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.navi.common.utils.EMPTY
import com.navi.common.utils.clickableDebounce
import com.navi.design.font.FontWeightEnum
import com.navi.design.font.getFontWeight
import com.navi.design.font.naviFontFamily
import com.navi.naviwidgets.R as WidgetsR
import com.navi.naviwidgets.extensions.NaviText
import com.navi.pay.common.theme.color.NaviPayColor
import com.navi.pay.common.theme.color.NaviPayColor.inputFieldFilled
import com.navi.pay.common.ui.ImageWithCircularBackground
import com.navi.pay.utils.getMaskedAccountNumber
import com.navi.payment.R
import com.navi.payment.nativepayment.model.BankDetails
import com.navi.payment.nativepayment.presentation.reducer.NPSScreenContract
@Composable
fun NPSNetBankingWidget(
paymentOptionIconUrl: String,
onAction: (action: NPSScreenContract.NPSScreenEvent) -> Unit
onAction: (action: NPSScreenContract.NPSScreenEvent) -> Unit,
requireRedirection: Boolean,
bankDetailsList: List<BankDetails>,
selectedBankDetails: BankDetails?
) {
Column(modifier = Modifier.fillMaxWidth()) {
NpsCardsHeader(header = stringResource(id = R.string.net_banking_card_header))
NpsPaymentInstrumentHeader(header = stringResource(id = R.string.net_banking_card_header))
Spacer(modifier = Modifier.height(16.dp))
NPSAddCard(
cardHeadline = stringResource(id = R.string.net_banking_card_headline),
imageResourceId = WidgetsR.drawable.chevron_icon_black,
cardFooterImageUrl = paymentOptionIconUrl,
onClick = { onAction(NPSScreenContract.NPSScreenEvent.OnNetBankingClicked) }
)
if (requireRedirection) {
NPSAddCard(
cardHeadline = stringResource(id = R.string.net_banking_card_headline),
imageResourceId = WidgetsR.drawable.chevron_icon_black,
cardFooterImageUrl = paymentOptionIconUrl,
onClick = { onAction(NPSScreenContract.NPSScreenEvent.OnNetBankingClicked) }
)
} else {
NpsNetbankingList(
bankDetailsList = bankDetailsList,
selectedBankDetails = selectedBankDetails,
onClick = { bankDetail ->
onAction(NPSScreenContract.NPSScreenEvent.OnNetbankingBankSelected(bankDetail))
}
)
}
}
}
@Composable
fun NpsNetbankingList(
bankDetailsList: List<BankDetails>,
onClick: (bankDetail: BankDetails) -> Unit,
selectedBankDetails: BankDetails?
) {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
bankDetailsList.forEach { bankDetail ->
NpsNetbankingBankItem(
bankDetail = bankDetail,
isSelected = selectedBankDetails == bankDetail,
onClick = onClick
)
Spacer(modifier = Modifier.height(16.dp))
}
}
}
@Composable
fun NpsNetbankingBankItem(
bankDetail: BankDetails,
isSelected: Boolean,
onClick: (bankDetail: BankDetails) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier =
Modifier.fillMaxWidth()
.shadow(
elevation = 16.dp,
spotColor = NaviPayColor.mutedSteelBlue,
ambientColor = NaviPayColor.mutedSteelBlue
)
.background(shape = RoundedCornerShape(4.dp), color = NaviPayColor.bgDefault)
.border(
border = BorderStroke(width = 1.dp, color = NaviPayColor.borderDefault),
shape = RoundedCornerShape(4.dp)
)
.clickableDebounce { onClick.invoke(bankDetail) }
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {
ImageWithCircularBackground(
boxSize = 40.dp,
imageUrl = bankDetail.bankIcon,
imageSize = 24.dp
)
Spacer(modifier = Modifier.width(12.dp))
NaviText(
modifier = Modifier.weight(1f, fill = false),
text = bankDetail.bankTitle,
fontSize = 16.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = inputFieldFilled,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
if (bankDetail.bankAccountNumber.isNotEmpty()) {
NaviText(
text = " - ${bankDetail.bankAccountNumber.getMaskedAccountNumber()}",
fontSize = 16.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = inputFieldFilled,
maxLines = 1,
)
}
}
if (isSelected) {
ButtonLoaderLottie()
} else {
Image(
painter = painterResource(id = com.navi.naviwidgets.R.drawable.chevron_icon_black),
contentDescription = EMPTY,
modifier = Modifier.size(24.dp)
)
}
}
}

View File

@@ -36,7 +36,7 @@ fun NPSUpiIntentWidget(
onAction: (action: NPSBaseContract.Event) -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
NpsCardsHeader(header = stringResource(id = titleResId))
NpsPaymentInstrumentHeader(header = stringResource(id = titleResId))
if (upiIntentOptionCollapsed) {
Spacer(modifier = Modifier.height(16.dp))
NPSAddCard(

View File

@@ -227,7 +227,7 @@ fun PopularBanksGridItem(
)
Spacer(modifier = Modifier.weight(1f))
if (selectedBank?.bankCode == bankEntity.bankCode && isSelectedFromPopularBanks) {
ShowNetBankingSelectLottie()
ButtonLoaderLottie()
} else {
NaviText(
text = bankEntity.bankTitle,
@@ -285,13 +285,13 @@ fun RegularBanksListItem(
)
if (selectedBank?.bankCode == bankEntity.bankCode && !isSelectedFromPopularBanks) {
Spacer(modifier = Modifier.weight(1f))
ShowNetBankingSelectLottie()
ButtonLoaderLottie()
}
}
}
@Composable
fun ShowNetBankingSelectLottie() {
fun ButtonLoaderLottie() {
val composition by
rememberLottieComposition(LottieCompositionSpec.Asset(NAVI_PAY_PURPLE_CTA_LOADER_LOTTIE))
LottieAnimation(

View File

@@ -8,8 +8,8 @@
package com.navi.payment.nativepayment.dataprovider
import com.navi.common.utils.SoftRefLruCache
import com.navi.payment.nativepayment.model.BankDetails
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.nativepayment.model.NetBankingPaymentInstrument
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,8 +26,8 @@ class PaymentDataProvider @Inject constructor() {
private val _paymentMethodResponse = MutableStateFlow<BasePaymentMethodResponse?>(null)
val paymentMethodResponse = _paymentMethodResponse.asStateFlow()
private val _fetchBanksResponse = MutableStateFlow<NetBankingPaymentInstrument?>(null)
val fetchBanksResponse = _fetchBanksResponse.asStateFlow()
private val _netBankingBankDetails = MutableStateFlow<List<BankDetails>>(value = emptyList())
val netBankingBankDetails = _netBankingBankDetails.asStateFlow()
private val _isDiscountPreApplied = MutableStateFlow(false)
val isDiscountPreApplied = _isDiscountPreApplied.asStateFlow()
@@ -60,7 +60,7 @@ class PaymentDataProvider @Inject constructor() {
analyticsEventParams.clear()
sourceEventProperties.clear()
_paymentMethodResponse.update { null }
_fetchBanksResponse.update { null }
_netBankingBankDetails.update { emptyList() }
_isDiscountPreApplied.update { false }
}
@@ -87,14 +87,14 @@ class PaymentDataProvider @Inject constructor() {
_paymentMethodResponse.update { response }
}
fun updateFetchBanksResponse(response: NetBankingPaymentInstrument?) {
_fetchBanksResponse.update { response }
}
fun updateDiscountAppliedStatus(isDiscountApplied: Boolean) {
_isDiscountPreApplied.update { isDiscountApplied }
}
fun updateNetBankingBanksResponse(banks: List<BankDetails>) {
_netBankingBankDetails.update { banks }
}
companion object {
private const val MAX_CONSUMPTION_LIMIT = 100
private const val MAX_TTL = 60 * 60 * 1000L

View File

@@ -8,14 +8,13 @@
package com.navi.payment.nativepayment.model
import com.google.gson.annotations.SerializedName
import com.navi.base.utils.EMPTY
data class NetBankingPaymentInstrument(
@SerializedName("instrumentName") override val instrumentName: String?,
@SerializedName("instrumentType")
override val instrumentType: String? = PAYMENT_INSTRUMENT_TYPE,
@SerializedName("instrument") val instrument: NetBankingPaymentInstumentDetails?,
@SerializedName("popularBanks") val popularBanksList: List<BankDetails>?,
@SerializedName("otherBanks") val otherBanksList: List<BankDetails>?
) : BasePaymentInstrument() {
companion object {
const val PAYMENT_INSTRUMENT_TYPE = "NETBANKING"
@@ -23,11 +22,15 @@ data class NetBankingPaymentInstrument(
}
data class NetBankingPaymentInstumentDetails(
@SerializedName("optionsVisible") val requireRedirection: Boolean = true,
@SerializedName("availableBanks") val availableBanks: List<BankDetails>,
@SerializedName("paymentOptionIconUrl") val paymentOptionIconUrl: String
)
data class BankDetails(
@SerializedName("bankName") val bankCode: String,
@SerializedName("bankIcon") val bankIcon: String,
@SerializedName("bankTitle") val bankTitle: String
@SerializedName("bankTitle") val bankTitle: String,
@SerializedName("isBankPopular") val isBankPopular: Boolean = false,
@SerializedName("bankAccountNumber") val bankAccountNumber: String = EMPTY
)

View File

@@ -13,6 +13,7 @@ import com.navi.pay.management.common.sendmoney.model.view.EligibilityState
import com.navi.pay.management.common.sendmoney.model.view.PMSSendMoneyStatus
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.utils.SAVINGS_ONLY_ENABLED_ACCOUNTS
import com.navi.payment.nativepayment.model.BankDetails
import com.navi.payment.nativepayment.model.CoinDetails
import com.navi.payment.nativepayment.model.CoinRewards
import com.navi.payment.nativepayment.model.FomoBottomSheetCtaAction
@@ -35,7 +36,8 @@ interface NPSScreenContract : NPSBaseContract {
val naviCoinState: NaviCoinState,
val npsBottomSheetHolder: NPSBottomSheetStateHolder,
val cardsHeading: String,
val iconDetails: InstrumentIconDetails
val iconDetails: InstrumentIconDetails,
val netBankingDetails: NetbankingDetails
) : NPSBaseContract.State
interface NPSScreenEvent : NPSBaseContract.Event {
@@ -53,9 +55,11 @@ interface NPSScreenContract : NPSBaseContract {
data object OnCardsClicked : NPSScreenEvent
data class OnNaviUpiResultRecieved(val result: ActivityResult) : NPSScreenEvent
data class OnNaviUpiResultReceived(val result: ActivityResult) : NPSScreenEvent
data class OnFomoSheetCtaClicked(val action: FomoBottomSheetCtaAction) : NPSScreenEvent
data class OnNetbankingBankSelected(val bankDetails: BankDetails) : NPSScreenEvent
}
interface NPSScreenEffect : NPSBaseContract.Effect {
@@ -128,3 +132,9 @@ data class InstrumentIconDetails(
val upiAppsIconUrl: String = NPS_DEFAULT_UPI_APPS_CARD_WIDGET,
val cardIconUrl: String = NPS_DEFAULT_CARD_WIDGET_ICON
)
data class NetbankingDetails(
val requireRedirection: Boolean,
val availableBanks: List<BankDetails>,
val selectedBank: BankDetails?
)

View File

@@ -14,7 +14,6 @@ import com.navi.common.network.retrofit.ResponseCallback
import com.navi.payment.model.common.SignalPaymentData
import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.nativepayment.model.NetBankingPaymentInstrument
import com.navi.payment.network.retrofit.RetrofitService
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.PayNowResponse
@@ -131,14 +130,4 @@ constructor(@PaymentsSdkRetrofit private val retrofitService: RetrofitService) :
metricInfo = metricInfo
)
}
suspend fun fetchBanks(
token: String,
metricInfo: MetricInfo<RepoResult<NetBankingPaymentInstrument>>
): RepoResult<NetBankingPaymentInstrument> {
return apiResponseCallback(
response = retrofitService.fetchBanksCompose(token, PAYMENT_GATEWAY_MODULE),
metricInfo = metricInfo
)
}
}

View File

@@ -79,7 +79,7 @@ import com.navi.payment.nativepayment.NaviPaymentAnalyticScreenName
import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.activity.NaviPaymentActivity
import com.navi.payment.nativepayment.components.NPSHeader
import com.navi.payment.nativepayment.components.NpsCardsHeader
import com.navi.payment.nativepayment.components.NpsPaymentInstrumentHeader
import com.navi.payment.nativepayment.components.OutlinedTextFieldWithLabelAndError
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.presentation.reducer.CardDetailScreenEffect
@@ -195,7 +195,7 @@ private fun cardDetailScreenMainContent(
): @Composable() (ColumnScope.() -> Unit) = {
val coroutineScope = rememberCoroutineScope()
Spacer(modifier = Modifier.height(25.dp))
NpsCardsHeader(state.paymentInstrumentHeading)
NpsPaymentInstrumentHeader(state.paymentInstrumentHeading)
Spacer(modifier = Modifier.height(24.dp))
OutlinedTextFieldForCards(
title = stringResource(R.string.card_number),

View File

@@ -70,6 +70,7 @@ import com.navi.base.utils.orZero
import com.navi.common.model.RequestConfig
import com.navi.common.uitron.model.action.UpiIntent
import com.navi.common.utils.EMPTY
import com.navi.common.utils.clickableDebounce
import com.navi.common.utils.log
import com.navi.common.utils.navigateUp
import com.navi.design.font.FontWeightEnum
@@ -88,7 +89,6 @@ import com.navi.pay.management.common.sendmoney.model.view.UpiTransactionType
import com.navi.pay.management.common.sendmoney.viewmodel.SendMoneyViewModel
import com.navi.pay.management.moneytransfer.scanpay.util.getPayeeEntity
import com.navi.pay.onboarding.common.NaviPayOnboardingActionsType
import com.navi.pay.utils.clickableDebounce
import com.navi.pay.utils.popBackStackUpTo
import com.navi.payment.R
import com.navi.payment.juspay.HyperServicesHolder
@@ -434,7 +434,13 @@ private fun NpsMainScreen(
NPSNetBankingWidget(
paymentOptionIconUrl =
screenState.iconDetails.netBankingIconUrl,
onAction = onEvent
onAction = onEvent,
requireRedirection =
screenState.netBankingDetails.requireRedirection,
bankDetailsList =
screenState.netBankingDetails.availableBanks,
selectedBankDetails =
screenState.netBankingDetails.selectedBank
)
Spacer(modifier = Modifier.height(32.dp))
}
@@ -642,7 +648,7 @@ private fun getUpiResultLauncher(
entryPoint = result.data?.extras?.getString("type").orEmpty(),
baseAttributes = npsViewModel.getAnalyticsParams()
)
npsViewModel.onEvent(NPSScreenContract.NPSScreenEvent.OnNaviUpiResultRecieved(result))
npsViewModel.onEvent(NPSScreenContract.NPSScreenEvent.OnNaviUpiResultReceived(result))
}
}

View File

@@ -52,7 +52,6 @@ import com.navi.payment.R
import com.navi.payment.juspay.JusPayUtil
import com.navi.payment.juspay.JuspayCallbackWrapper
import com.navi.payment.model.initiatesdk.PaymentSDKProvider
import com.navi.payment.nativepayment.NaviPaymentAnalyticScreenName
import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.activity.NaviPaymentActivity
import com.navi.payment.nativepayment.components.BankSearchField
@@ -93,9 +92,6 @@ fun NetBankingScreenRoot(
val isKeyboardVisible = WindowInsets.isImeVisible
LaunchedEffect(Unit) {
naviPaymentAnalytics.onScreenLanded(netBankingViewModel.getBaseAnalyticsParams())
netBankingViewModel.recordScreenLatency(
screenName = NaviPaymentAnalyticScreenName.NET_BANKING_SCREEN.screenName
)
}
ObserveBottomSheetEvents(activity = naviPaymentActivity, onEvent = netBankingViewModel::onEvent)
@@ -177,9 +173,7 @@ fun NetBankingScreen(
Modifier.height(if (screenState.showPopularBanks) 24.dp else 16.dp)
)
}
if (
screenState.allBanksList?.isNotEmpty().orFalse() || screenState.showPopularBanks
) {
if (screenState.allBanksList?.isNotEmpty().orFalse()) {
NetBankingScreenListSection(
popularBanks = screenState.popularBanksList ?: emptyList(),
allBanks = screenState.allBanksList ?: emptyList(),

View File

@@ -193,19 +193,15 @@ abstract class NPSBaseViewModel(
)
}
private suspend fun handleJuspayEventReceived(payload: JSONObject) {
protected open suspend fun handleJuspayEventReceived(payload: JSONObject) {
if (PaymentScreenUtil.isValidJuspayResponse(payload)) {
showLoader(false)
val npsBaseState = state.value.npsBaseState
updateNpsBaseState(npsBaseState.copy(isCtaLoaderEnabled = false))
navigateToLoaderScreen(payload)
}
}
private suspend fun handleUpiAppResultReceived() {
showLoader(false)
val npsBaseState = state.value.npsBaseState
updateNpsBaseState(npsBaseState.copy(isCtaLoaderEnabled = false))
navigateToLoaderScreen()
}

View File

@@ -56,6 +56,7 @@ import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion
import com.navi.payment.nativepayment.dataprovider.getMpinSetAction
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
import com.navi.payment.nativepayment.model.AvailableCardTypes
import com.navi.payment.nativepayment.model.BankDetails
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.CardPaymentInstrument
import com.navi.payment.nativepayment.model.CardPaymentInstrumentDetails
@@ -80,6 +81,7 @@ import com.navi.payment.nativepayment.presentation.reducer.NPSBottomSheetType
import com.navi.payment.nativepayment.presentation.reducer.NPSScreenContract
import com.navi.payment.nativepayment.presentation.reducer.NaviCoinState
import com.navi.payment.nativepayment.presentation.reducer.NaviUpiPaymentState
import com.navi.payment.nativepayment.presentation.reducer.NetbankingDetails
import com.navi.payment.nativepayment.presentation.reducer.UpiCollectOptionState
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.utils.getDiscountAdjustedAmount
@@ -98,6 +100,7 @@ import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.TransactionStatus
import com.navi.payment.paymentscreen.model.TransactionStatusRequest
import com.navi.payment.turbocheckout.model.NetBankingPayNowRequest
import com.navi.payment.turbocheckout.model.PayAmountRequest
import com.navi.payment.turbocheckout.model.PayNowRequest
import com.navi.payment.turbocheckout.model.SelectedMethodDetails
@@ -193,7 +196,13 @@ constructor(
bottomSheetUIState = NPSBottomSheetType.Init
),
cardsHeading = resourceProvider.getString(R.string.debit_credit_card_heading),
iconDetails = InstrumentIconDetails()
iconDetails = InstrumentIconDetails(),
netBankingDetails =
NetbankingDetails(
requireRedirection = true,
availableBanks = emptyList(),
selectedBank = null
)
)
}
@@ -343,11 +352,24 @@ constructor(
instrumentDetails: NetBankingPaymentInstumentDetails
): NPSScreenContract.NPSScreenState {
val iconUrl = instrumentDetails.paymentOptionIconUrl
val requireRedirection = instrumentDetails.requireRedirection
val preferredBankList =
if (requireRedirection.not()) {
instrumentDetails.availableBanks
} else {
emptyList()
}
paymentDataProvider.updateNetBankingBanksResponse(instrumentDetails.availableBanks)
return if (iconUrl.isNotNullAndNotEmpty())
updatedState.copy(
iconDetails =
updatedState.iconDetails.copy(
netBankingIconUrl = instrumentDetails.paymentOptionIconUrl
),
netBankingDetails =
updatedState.netBankingDetails.copy(
requireRedirection = instrumentDetails.requireRedirection,
availableBanks = preferredBankList
)
)
else updatedState
@@ -587,7 +609,7 @@ constructor(
is NPSScreenContract.NPSScreenEvent.OnDiscountClicked -> {
handleDiscountApplied(event.isDiscountApplied)
}
is NPSScreenContract.NPSScreenEvent.OnNaviUpiResultRecieved -> {
is NPSScreenContract.NPSScreenEvent.OnNaviUpiResultReceived -> {
handleNaviUpiResultReceived(event.result)
}
is NPSScreenContract.NPSScreenEvent.OnNaviPayCallback -> {
@@ -618,6 +640,11 @@ constructor(
is NPSScreenContract.NPSScreenEvent.OnFomoSheetCtaClicked -> {
handleFomoBottomSheetCtaClicked(event.action)
}
is NPSScreenContract.NPSScreenEvent.OnNetbankingBankSelected -> {
if (isInteractionAllowed) {
handleOnNetBankingSelected(event.bankDetails)
}
}
else -> {
super.onEvent(event)
}
@@ -625,6 +652,25 @@ constructor(
}
}
private suspend fun handleOnNetBankingSelected(bankDetails: BankDetails) {
_state.update {
npsScreenState.copy(
netBankingDetails =
npsScreenState.netBankingDetails.copy(selectedBank = bankDetails)
)
}
handlePayNowClicked()
}
override suspend fun handleJuspayEventReceived(payload: JSONObject) {
_state.update {
npsScreenState.copy(
netBankingDetails = npsScreenState.netBankingDetails.copy(selectedBank = null),
)
}
super.handleJuspayEventReceived(payload)
}
private suspend fun handleFomoBottomSheetCtaClicked(action: FomoBottomSheetCtaAction) {
when (action) {
is FomoBottomSheetCtaAction.ScrollToTop -> {
@@ -840,7 +886,16 @@ constructor(
)
var payNowRequest: PayNowRequest? = null
val selectedBankAccount = npsScreenState.naviUpiPaymentState.selectedBankAccount
if (npsScreenState.npsBaseState.selectedUpiApp != null) {
if (npsScreenState.netBankingDetails.selectedBank != null) {
payNowRequest =
NetBankingPayNowRequest(
methodName = NetBankingPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
SelectedMethodDetails(
bankName = npsScreenState.netBankingDetails.selectedBank?.bankCode
)
)
} else if (npsScreenState.npsBaseState.selectedUpiApp != null) {
showLoader(true)
payNowRequest =
UpiIntentPayNowRequest(

View File

@@ -10,7 +10,7 @@ package com.navi.payment.nativepayment.viewmodel
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.navi.base.utils.isNull
import com.navi.base.utils.EMPTY
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.payment.model.common.PaymentSdkInitParams
@@ -70,6 +70,9 @@ constructor(
override val state: StateFlow<NetBankingScreenState>
get() = _state
private val currentState: NetBankingScreenState
get() = state.value
private val _effect = MutableSharedFlow<NetBankingScreenEffect>()
override val effect: SharedFlow<NetBankingScreenEffect>
get() = _effect
@@ -78,9 +81,9 @@ constructor(
var paymentSdkInitParams: PaymentSdkInitParams? = null
private val baseAnalyticsParams = mutableMapOf<String, String>()
private val naviPaymentAnalytics = NaviPaymentAnalytics.INSTANCE.NetBankingScreen()
private val netBankingAnalytics = NaviPaymentAnalytics.INSTANCE.NetBankingScreen()
private var sortedAllBanksList: List<BankDetails>? = null
private var sortedAllBanksList: List<BankDetails> = emptyList()
init {
recordScreenLandTime(screenName)
@@ -98,7 +101,7 @@ constructor(
private fun setInitialState(): NetBankingScreenState {
return NetBankingScreenState(
searchQuery = TextFieldValue(""),
searchQuery = TextFieldValue(EMPTY),
allBanksList = emptyList(),
popularBanksList = emptyList(),
showPopularBanks = true,
@@ -110,50 +113,36 @@ constructor(
}
private fun fetchBanksList() {
_state.update { it.copy(isLoading = true) }
viewModelScope.safeLaunch {
paymentDataProvider.fetchBanksResponse.collectLatest { fetchBanksResponse ->
if (fetchBanksResponse.isNull()) {
updateFetchBankResponse()
paymentDataProvider.netBankingBankDetails.collectLatest { bankDetails ->
if (bankDetails.isEmpty()) {
handleEmptyBanksList()
} else {
handleFetchBanksResponse(fetchBanksResponse!!)
handleBankDetails(bankDetails)
}
}
}
}
private fun updateFetchBankResponse() {
viewModelScope.safeLaunch {
val response =
paymentRepository.fetchBanks(
paymentSdkInitParams?.token.orEmpty(),
metricInfo =
getPMSMetricInfo(
screenName = NaviPaymentAnalyticScreenName.NET_BANKING_SCREEN.screenName
)
)
if (response.isSuccessWithData()) {
paymentDataProvider.updateFetchBanksResponse(response.data)
} else {
handleError(response)
}
}
private fun handleEmptyBanksList() {
netBankingAnalytics.emptyBanksList()
}
private fun handleFetchBanksResponse(response: NetBankingPaymentInstrument) {
private fun handleBankDetails(bankList: List<BankDetails>) {
val popularBanksList = bankList.filter { it.isBankPopular }
sortedAllBanksList = bankList.sortedBy { it.bankTitle }
val paymentMethodResponse =
paymentDataProvider.paymentMethodResponse.value as? S2sPaymentMethodResponse?
val allBanksList =
(response.otherBanksList ?: emptyList()) + (response.popularBanksList ?: emptyList())
sortedAllBanksList = allBanksList.sortedBy { it.bankTitle }
_state.update {
it.copy(
updateState(
currentState.copy(
isLoading = false,
allBanksList = sortedAllBanksList,
popularBanksList = response.popularBanksList,
popularBanksList = popularBanksList,
paymentAmount = paymentMethodResponse?.methodDetails?.amount?.value ?: 0.0
)
}
)
recordScreenLatency(NaviPaymentAnalyticScreenName.NET_BANKING_SCREEN.screenName)
}
private fun getSDKInitParams(): PaymentSdkInitParams? {
@@ -166,7 +155,7 @@ constructor(
_state.update { it.copy(searchQuery = event.query) }
}
is NetBankingScreenEvent.OnBankSelected -> {
naviPaymentAnalytics.onBankSelected(
netBankingAnalytics.onBankSelected(
selectedBank = event.bankDetails.bankTitle,
isSelectedFromPopularBanks = event.isPopularBank,
isSelectedFromFilteredList = state.value.searchQuery.text.isNotEmpty(),
@@ -187,7 +176,7 @@ constructor(
is NetBankingScreenEvent.OnClearSearchQueryClicked -> {
_state.update {
it.copy(
searchQuery = TextFieldValue(""),
searchQuery = TextFieldValue(EMPTY),
allBanksList = sortedAllBanksList,
showPopularBanks = true
)
@@ -223,7 +212,7 @@ constructor(
_effect.emit(NetBankingScreenEffect.DismissKeyboard)
delay(DELAY_FOR_HIDING_KEYBOARD)
}
naviPaymentAnalytics.onBackPressed(baseAnalyticsParams)
netBankingAnalytics.onBackPressed(baseAnalyticsParams)
_effect.emit(NetBankingScreenEffect.BackPress)
}
}
@@ -256,16 +245,16 @@ constructor(
it.copy(allBanksList = sortedAllBanksList, showPopularBanks = true)
}
} else {
naviPaymentAnalytics.onSearchBankQuery(
netBankingAnalytics.onSearchBankQuery(
query = query,
baseAttributes = getBaseAnalyticsParams()
)
val filteredBanksList =
sortedAllBanksList?.filter {
sortedAllBanksList.filter {
it.bankTitle.contains(query, ignoreCase = true)
}
if (filteredBanksList.isNullOrEmpty()) {
naviPaymentAnalytics.onNoBankFound(
if (filteredBanksList.isEmpty()) {
netBankingAnalytics.onNoBankFound(
query = query,
baseAttributes = getBaseAnalyticsParams()
)
@@ -314,8 +303,7 @@ constructor(
payNowResponseData.providerPayload?.let {
_effect.emit(
NetBankingScreenEffect.StartPayment(
provider =
(response.data as ExternalPayNowResponse).provider.orEmpty(),
provider = payNowResponseData.provider.orEmpty(),
payLoad = JSONObject(deserializer.toJson(it))
)
)
@@ -334,4 +322,8 @@ constructor(
fun getBaseAnalyticsParams(): Map<String, String> {
return baseAnalyticsParams
}
private fun updateState(newState: NetBankingScreenState) {
_state.update { newState }
}
}

View File

@@ -19,27 +19,10 @@ import com.navi.common.model.ModuleNameV2
import com.navi.common.model.NetworkInfo
import com.navi.common.network.converter.EmptyBodyHandlingConverterFactory
import com.navi.common.utils.registerUiTronDeSerializers
import com.navi.payment.model.initiatesdk.InitiatePaymentSDKConfig
import com.navi.payment.model.paymentmethod.GenericPaymentMethodResponse
import com.navi.payment.model.paymentresult.PaymentResultData
import com.navi.payment.model.paymentresult.PaymentSDKActionConfig
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.network.deserializers.PayNowResponseDeserializer
import com.navi.payment.network.deserializers.PaymentInstrumentDeserializer
import com.navi.payment.network.deserializers.PaymentMethodResponseDeserializer
import com.navi.payment.network.deserializers.PaymentSDKActionDeserializer
import com.navi.payment.network.deserializers.PaymentSDKConfigDeserializer
import com.navi.payment.network.deserializers.PaymentSDKMethodDetailsDeserializer
import com.navi.payment.network.deserializers.PaymentSdkPayloadDeserializer
import com.navi.payment.network.deserializers.TransactionResponseDataDeserializer
import com.navi.payment.network.retrofit.NaviPaymentsHttpClient
import com.navi.payment.network.retrofit.RetrofitService
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.TransactionResponseMetaData
import com.navi.paymentclients.deserializers.ThirdPartyPayloadDeserializer
import com.navi.paymentclients.model.thirdparty.ThirdPartyResultData
import com.navi.payment.utils.registerPaymentDeserializers
import com.navi.paymentclients.network.ClientRetrofitService
import dagger.Module
import dagger.Provides
@@ -102,30 +85,7 @@ object PaymentNetworkModule {
@Provides
@PaymentsSdkRetrofit
fun providesPaymentDeserializer(): Gson {
return GsonBuilder()
.registerTypeAdapter(
InitiatePaymentSDKConfig::class.java,
PaymentSDKConfigDeserializer()
)
.registerTypeAdapter(PaymentSDKActionConfig::class.java, PaymentSDKActionDeserializer())
.registerTypeAdapter(
GenericPaymentMethodResponse::class.java,
PaymentSDKMethodDetailsDeserializer()
)
.registerTypeAdapter(PaymentResultData::class.java, PaymentSdkPayloadDeserializer())
.registerTypeAdapter(ThirdPartyResultData::class.java, ThirdPartyPayloadDeserializer())
.registerTypeAdapter(
TransactionResponseMetaData::class.java,
TransactionResponseDataDeserializer()
)
.registerTypeAdapter(
BasePaymentMethodResponse::class.java,
PaymentMethodResponseDeserializer()
)
.registerTypeAdapter(BasePaymentInstrument::class.java, PaymentInstrumentDeserializer())
.registerTypeAdapter(PayNowResponse::class.java, PayNowResponseDeserializer())
.registerUiTronDeSerializers()
.create()
return GsonBuilder().registerPaymentDeserializers().registerUiTronDeSerializers().create()
}
@Singleton

View File

@@ -10,6 +10,7 @@ package com.navi.payment.utils
import android.content.Intent
import com.cashfree.pg.ui.api.CFPaymentComponent.CFPaymentModes
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.navi.base.utils.FAILURE
import com.navi.base.utils.SUCCESS
@@ -17,16 +18,34 @@ import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.utils.log
import com.navi.payment.cashfree.CashFreeConfig
import com.navi.payment.model.digio.DigioResponseData
import com.navi.payment.model.initiatesdk.InitiatePaymentSDKConfig
import com.navi.payment.model.paymentmethod.GenericPaymentMethodResponse
import com.navi.payment.model.paymentmethod.PaymentMethodResponse
import com.navi.payment.model.paymentmethod.PaymentMethods
import com.navi.payment.model.paymentmethod.RazorPayPaymentMethods
import com.navi.payment.model.paymentresult.PaymentResultData
import com.navi.payment.model.paymentresult.PaymentSDKActionConfig
import com.navi.payment.model.razorpay.Prefill
import com.navi.payment.model.razorpay.RazorpayPaymentData
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.nativepayment.model.SdkPaymentMethodResponse
import com.navi.payment.nativepayment.model.WebPaymentAction
import com.navi.payment.nativepayment.model.WebPaymentData
import com.navi.payment.network.deserializers.PayNowResponseDeserializer
import com.navi.payment.network.deserializers.PaymentInstrumentDeserializer
import com.navi.payment.network.deserializers.PaymentMethodResponseDeserializer
import com.navi.payment.network.deserializers.PaymentSDKActionDeserializer
import com.navi.payment.network.deserializers.PaymentSDKConfigDeserializer
import com.navi.payment.network.deserializers.PaymentSDKMethodDetailsDeserializer
import com.navi.payment.network.deserializers.PaymentSdkPayloadDeserializer
import com.navi.payment.network.deserializers.TransactionResponseDataDeserializer
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.TransactionResponseMetaData
import com.navi.payment.paymentscreen.model.TransactionStatus
import com.navi.payment.utils.PaymentErrorConstants.TOKEN_INIT_FAILED
import com.navi.paymentclients.deserializers.ThirdPartyPayloadDeserializer
import com.navi.paymentclients.model.thirdparty.ThirdPartyResultData
import com.navi.paymentclients.model.thirdparty.UpiResultErrorResponse
import java.math.BigDecimal
import java.math.RoundingMode
@@ -255,3 +274,26 @@ fun <T> Set<T>.toggle(element: T): Set<T> {
return this.plus(element)
}
}
fun GsonBuilder.registerPaymentDeserializers(): GsonBuilder {
return apply {
registerTypeAdapter(InitiatePaymentSDKConfig::class.java, PaymentSDKConfigDeserializer())
registerTypeAdapter(PaymentSDKActionConfig::class.java, PaymentSDKActionDeserializer())
registerTypeAdapter(
GenericPaymentMethodResponse::class.java,
PaymentSDKMethodDetailsDeserializer()
)
registerTypeAdapter(PaymentResultData::class.java, PaymentSdkPayloadDeserializer())
registerTypeAdapter(ThirdPartyResultData::class.java, ThirdPartyPayloadDeserializer())
registerTypeAdapter(
TransactionResponseMetaData::class.java,
TransactionResponseDataDeserializer()
)
registerTypeAdapter(
BasePaymentMethodResponse::class.java,
PaymentMethodResponseDeserializer()
)
registerTypeAdapter(BasePaymentInstrument::class.java, PaymentInstrumentDeserializer())
registerTypeAdapter(PayNowResponse::class.java, PayNowResponseDeserializer())
}
}

View File

@@ -16,24 +16,6 @@ import com.navi.base.AppServiceManager
import com.navi.common.network.models.RepoResult
import com.navi.common.utils.registerUiTronDeSerializers
import com.navi.payment.R
import com.navi.payment.model.initiatesdk.InitiatePaymentSDKConfig
import com.navi.payment.model.paymentmethod.GenericPaymentMethodResponse
import com.navi.payment.model.paymentresult.PaymentResultData
import com.navi.payment.model.paymentresult.PaymentSDKActionConfig
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.network.deserializers.PayNowResponseDeserializer
import com.navi.payment.network.deserializers.PaymentInstrumentDeserializer
import com.navi.payment.network.deserializers.PaymentMethodResponseDeserializer
import com.navi.payment.network.deserializers.PaymentSDKActionDeserializer
import com.navi.payment.network.deserializers.PaymentSDKConfigDeserializer
import com.navi.payment.network.deserializers.PaymentSDKMethodDetailsDeserializer
import com.navi.payment.network.deserializers.PaymentSdkPayloadDeserializer
import com.navi.payment.network.deserializers.TransactionResponseDataDeserializer
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.TransactionResponseMetaData
import com.navi.paymentclients.deserializers.ThirdPartyPayloadDeserializer
import com.navi.paymentclients.model.thirdparty.ThirdPartyResultData
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
@@ -46,30 +28,7 @@ fun <T> mockApiResponse(
val dataString = String(inputStream.readBytes(), StandardCharsets.UTF_8)
val jsonElement = (JsonParser.parseString(dataString) as? JsonObject)?.get(jsonKey)
val customGson =
GsonBuilder()
.registerTypeAdapter(
GenericPaymentMethodResponse::class.java,
PaymentSDKMethodDetailsDeserializer()
)
.registerTypeAdapter(PaymentResultData::class.java, PaymentSdkPayloadDeserializer())
.registerTypeAdapter(PaymentSDKActionConfig::class.java, PaymentSDKActionDeserializer())
.registerTypeAdapter(
InitiatePaymentSDKConfig::class.java,
PaymentSDKConfigDeserializer()
)
.registerTypeAdapter(ThirdPartyResultData::class.java, ThirdPartyPayloadDeserializer())
.registerTypeAdapter(
BasePaymentMethodResponse::class.java,
PaymentMethodResponseDeserializer()
)
.registerTypeAdapter(BasePaymentInstrument::class.java, PaymentInstrumentDeserializer())
.registerTypeAdapter(PayNowResponse::class.java, PayNowResponseDeserializer())
.registerTypeAdapter(
TransactionResponseMetaData::class.java,
TransactionResponseDataDeserializer()
)
.registerUiTronDeSerializers()
.create()
GsonBuilder().registerPaymentDeserializers().registerUiTronDeSerializers().create()
return RepoResult(data = customGson.fromJson(jsonElement, type))
}