NTP-61439 | Anmol A | Navi Upi Payment Processor (#16068)

This commit is contained in:
Anmol Agrawal
2025-05-30 11:55:22 +05:30
committed by GitHub
parent ed0c43435f
commit ed9347e887
15 changed files with 1458 additions and 1081 deletions

View File

@@ -18,6 +18,7 @@ import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PMT_
import com.navi.common.network.models.ErrorMessage
import com.navi.common.network.models.GenericErrorResponse
import com.navi.pay.common.model.view.CheckBalanceAnalyticsEventData
import com.navi.pay.common.model.view.NaviPayErrorConfig
import com.navi.pay.management.common.utils.PspEvaluationResult
import com.navi.payment.nativepayment.dataprovider.PinAction
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
@@ -28,6 +29,7 @@ import com.navi.payment.nativepayment.presentation.reducer.ImageSource
import com.navi.payment.nativepayment.utils.NaviPaymentErrorConfig
import com.navi.payment.nativepayment.viewmodel.ScanCardResult
import com.navi.payment.paymentscreen.model.ErrorReason
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.utils.putIfNotNullAndNotEmpty
import com.navi.payment.utils.usecases.CardValidator
@@ -1916,7 +1918,7 @@ class NaviPaymentAnalytics private constructor() {
}
}
inner class PspManager() {
inner class PspManager {
// PSP Manager Events
fun onAccountAdditionWithPspManager(enabledAccountTypes: String, screenName: String) {
val eventAttributes = buildMap { put("enabledAccountTypes", enabledAccountTypes) }
@@ -1973,12 +1975,6 @@ class NaviPaymentAnalytics private constructor() {
eventValues = eventAttributes,
)
}
fun onNaviPayOnboardingEntityNotInitialised(screenName: String) {
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_NaviPayOnboardingEntityNotInitialised"
)
}
}
inner class WebPaymentScreen {
@@ -2460,6 +2456,139 @@ class NaviPaymentAnalytics private constructor() {
}
}
inner class NaviUpiPaymentProcessor {
fun onUpiLitePayment(
screenName: String,
isDiscountApplied: String,
baseAttributes: Map<String, String>,
) {
val eventAttributes = buildMap {
put("is_discount_applied", isDiscountApplied)
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_UpiLitePayment",
eventValues = eventAttributes,
)
}
fun onNonUpiLitePayment(
screenName: String,
isDiscountApplied: String,
baseAttributes: Map<String, String>,
accountId: String,
) {
val eventAttributes = buildMap {
put("is_discount_applied", isDiscountApplied)
put("account_id", accountId)
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_NonUpiLitePayment",
eventValues = eventAttributes,
)
}
fun onPinPageOpened(
screenName: String,
naviPayUpiUriKey: String,
transactionReferenceId: String,
baseAttributes: Map<String, String>,
) {
val eventAttributes = buildMap {
put("navi_pay_upi_uri_key", naviPayUpiUriKey)
put("transaction_reference_id", transactionReferenceId)
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_PinPageOpened",
eventValues = eventAttributes,
)
}
fun onPinPageCancelled(
screenName: String,
naviPayUpiUriKey: String,
transactionReferenceId: String,
baseAttributes: Map<String, String>,
) {
val eventAttributes = buildMap {
put("navi_pay_upi_uri_key", naviPayUpiUriKey)
put("transaction_reference_id", transactionReferenceId)
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_PinPageCancelled",
eventValues = eventAttributes,
)
}
fun onPinPageError(
screenName: String,
naviPayUpiUriKey: String,
transactionReferenceId: String,
baseAttributes: Map<String, String>,
errorCode: NaviPayErrorConfig,
) {
val eventAttributes = buildMap {
put("navi_pay_upi_uri_key", naviPayUpiUriKey)
put("transaction_reference_id", transactionReferenceId)
put("navi_pay_error_config", errorCode.toString())
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_PinPageError",
eventValues = eventAttributes,
)
}
fun onPinPageSuccess(
screenName: String,
naviPayUpiUriKey: String,
transactionReferenceId: String,
baseAttributes: Map<String, String>,
) {
val eventAttributes = buildMap {
put("navi_pay_upi_uri_key", naviPayUpiUriKey)
put("transaction_reference_id", transactionReferenceId)
putAll(baseAttributes)
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_PinPageSuccess",
eventValues = eventAttributes,
)
}
fun onEmptyPaymentToken(screenName: String, baseAttributes: Map<String, String>) {
val eventAttributes = buildMap { putAll(baseAttributes) }
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_EmptyPaymentToken",
eventValues = eventAttributes,
)
}
fun onPayNowChecksFailed(
screenName: String,
transactionReferenceId: String,
paymentOrderReferenceId: String,
intentUri: String,
internalPayNowResponse: InternalPayNowResponse?,
genericErrorResponse: GenericErrorResponse,
) {
val eventAttributes = buildMap {
put("intentUri", intentUri)
put("transaction_Reference_id", transactionReferenceId)
put("payment_Order_ReferenceId", paymentOrderReferenceId)
put("internalPayNowResponse", internalPayNowResponse.toString())
put("genericErrorResponse", genericErrorResponse.toString())
}
NaviTrackEvent.trackEventOnClickStream(
eventName = "NaviPMT_${screenName}_PayNowChecksFailed",
eventValues = eventAttributes,
)
}
}
private fun addScreenName(
baseAttributes: Map<String, String>,
screenName: String,

View File

@@ -13,7 +13,7 @@ import com.navi.pay.onboarding.binding.model.view.NaviPayCustomerOnboardingEntit
sealed class PMSSendMoneyStatus {
data class Success(
val upiRequestId: String,
val credBlock: String? = null,
val credBlock: String,
val onboardingDataEntity: NaviPayCustomerOnboardingEntity,
) : PMSSendMoneyStatus()

View File

@@ -0,0 +1,14 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.payment.nativepayment.presentation.reducer
import com.ramcosta.composedestinations.spec.Direction
sealed interface MPSScreenEffect {
data class Navigation(val direction: Direction) : MPSScreenEffect
}

View File

@@ -0,0 +1,20 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.payment.nativepayment.presentation.reducer
import com.navi.payment.nativepayment.utils.PaymentCancelSource
import com.ramcosta.composedestinations.spec.Direction
sealed interface OneClickCheckoutScreenEffect {
data class SetResultAndFinish(
val cancelSource: PaymentCancelSource,
val shouldPromptAmountEdit: Boolean,
) : OneClickCheckoutScreenEffect
data class Navigation(val direction: Direction) : OneClickCheckoutScreenEffect
}

View File

@@ -34,18 +34,13 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.gson.Gson
import com.navi.analytics.utils.AlfredFacade
import com.navi.base.utils.isNotNull
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.isNull
import com.navi.base.utils.orElse
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PMT_API_CONNECT_TIMEOUT
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PMT_RETRY_POLICY_ENABLED
import com.navi.common.model.RequestConfig
import com.navi.common.utils.TemporaryStorageHelper
import com.navi.naviwidgets.R
import com.navi.pay.common.arc.ui.ArcNudgeBottomSheetContent
@@ -66,20 +61,17 @@ import com.navi.payment.nativepayment.components.MPSNonOnboardedView
import com.navi.payment.nativepayment.components.MPSOnboardedView
import com.navi.payment.nativepayment.dataprovider.PinAction
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatusRequest
import com.navi.payment.nativepayment.presentation.reducer.MPSScreenEffect
import com.navi.payment.nativepayment.screens.destinations.NPSScreenRootDestination
import com.navi.payment.nativepayment.screens.destinations.TransactionPollingScreenDestination
import com.navi.payment.nativepayment.utils.MPSScreenUtils
import com.navi.payment.nativepayment.utils.PaymentCancelSource
import com.navi.payment.nativepayment.utils.handleResultAndFinish
import com.navi.payment.nativepayment.viewmodel.MPSViewModel
import com.navi.payment.nativepayment.viewmodel.NaviPaymentViewModel
import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.utils.Constants.MINI_PAYMENT_SCREEN
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@Destination
@@ -117,7 +109,6 @@ fun MPSScreen(
val showDiscountedAmount by mpsViewModel.showDiscountedAmount.collectAsStateWithLifecycle()
val checkBalanceStateMap by mpsViewModel.checkBalanceStateMap.collectAsStateWithLifecycle()
val checkBalanceAccountId by mpsViewModel.checkBalanceAccountId.collectAsStateWithLifecycle()
val sendMoneyStatus by mpsViewModel.pmsSendMoneyStatus.collectAsStateWithLifecycle()
val rewardsInfoV2 by mpsViewModel.rewardsInfoV2.collectAsStateWithLifecycle()
val isDiscountPreApplied by mpsViewModel.isDiscountPreApplied.collectAsStateWithLifecycle()
@@ -145,52 +136,7 @@ fun MPSScreen(
}
}
LaunchedEffect(key1 = sendMoneyStatus) {
if (sendMoneyStatus is PMSSendMoneyStatus.Success) {
val pmsSendMoneyStatus = sendMoneyStatus as PMSSendMoneyStatus.Success
val payNowResponse = mpsViewModel.payNowResponse
val payload = (payNowResponse?.providerPayload) as? NaviPayProcessPayload
if (
mpsViewModel.paymentSdkInitParams?.token.isNotNullAndNotEmpty() &&
payNowResponse?.transactionReferenceId.isNotNullAndNotEmpty()
) {
navigator.navigate(
TransactionPollingScreenDestination(
token = mpsViewModel.paymentSdkInitParams?.token.orEmpty(),
paymentAmount = paymentAmount,
redeemedCoinsValue =
if (isDiscountApplied)
if (rewardsInfoV2.isNull())
coinBurnDetails?.redeemableCoinsValue?.value.orZero()
else rewardsInfoV2?.burnDetails?.coinsValue?.value.orZero()
else 0.0,
source = mpsViewModel.paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = payload?.tstoreOrderReferenceId.orEmpty(),
sourceDestination = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
pollingConfiguration =
payNowResponse?.pollingConfiguration ?: RequestConfig(),
request =
TransactionStatusRequest(
transactionReferenceId = payNowResponse?.transactionReferenceId,
screenType = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
),
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
naviPayUpiUriKey = payload?.naviPayUpiUriKey,
upiRequestId = pmsSendMoneyStatus.upiRequestId,
selectedBankAccountId = selectedBankAccount?.accountId.orEmpty(),
credBlock = pmsSendMoneyStatus.credBlock.orEmpty(),
metaData = Gson().toJson(payload?.metadata),
txnTimeStamp = mpsViewModel.txnTimeStamp,
isArcProtected = selectedBankAccount?.isArcProtected.orFalse(),
onboardingDataEntity = pmsSendMoneyStatus.onboardingDataEntity,
),
)
)
}
}
mpsViewModel.onNaviPayCallBack()
}
EffectsHandler(mpsScreenEffect = mpsViewModel.effect, navigator = navigator)
LaunchedEffect(key1 = Unit) {
naviPaymentAnalytics.onMPSLanded(baseAttributes = mpsViewModel.getAnalyticsParams())
@@ -598,6 +544,17 @@ fun MPSScreen(
}
}
@Composable
fun EffectsHandler(mpsScreenEffect: SharedFlow<MPSScreenEffect>, navigator: DestinationsNavigator) {
LaunchedEffect(Unit) {
mpsScreenEffect.collectLatest { effect ->
when (effect) {
is MPSScreenEffect.Navigation -> navigator.navigate(effect.direction)
}
}
}
}
@Composable
private fun HandleLifecycleEventsForAlfred(naviPaymentActivity: NaviPaymentActivity) {
val lifecycleOwner = LocalLifecycleOwner.current

View File

@@ -26,11 +26,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.gson.Gson
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.common.model.RequestConfig
import com.navi.common.upi.CANCEL
import com.navi.common.upi.NAVI_PAY_RESPONSE
import com.navi.common.utils.stringToJsonObject
import com.navi.pay.common.theme.color.NaviPayColor
@@ -43,14 +41,13 @@ import com.navi.payment.nativepayment.activity.NaviPaymentActivity
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.OneClickScreenBottomSheetUiState
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatusRequest
import com.navi.payment.nativepayment.presentation.reducer.OneClickCheckoutScreenEffect
import com.navi.payment.nativepayment.screens.destinations.NPSScreenRootDestination
import com.navi.payment.nativepayment.screens.destinations.TransactionPollingScreenDestination
import com.navi.payment.nativepayment.utils.NaviPaymentBottomSheetButtonAction
import com.navi.payment.nativepayment.utils.PaymentCancelSource
import com.navi.payment.nativepayment.utils.handleResultAndFinish
import com.navi.payment.nativepayment.utils.toNaviPaymentErrorConfig
import com.navi.payment.nativepayment.viewmodel.OneClickCheckoutViewModel
import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
@@ -58,6 +55,7 @@ import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.utils.validateUpiProcessPayload
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -72,8 +70,6 @@ fun OneClickCheckoutScreen(
NaviPaymentAnalytics.INSTANCE.SendMoneyErrorBottomSheet(),
) {
val sendMoneyStatus by
oneClickCheckoutViewModel.pmsSendMoneyStatus.collectAsStateWithLifecycle()
val bottomSheetStateHolder by
oneClickCheckoutViewModel.bottomSheetStateHolder.collectAsStateWithLifecycle()
val checkBalanceStateMap by
@@ -86,6 +82,12 @@ fun OneClickCheckoutScreen(
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val coroutineScope = rememberCoroutineScope()
EffectsHandler(
screenEffect = oneClickCheckoutViewModel.effect,
activity = naviPaymentActivity,
navigator = navigator,
)
val finishCallback:
(cancelSource: PaymentCancelSource, shouldPromptAmountEdit: Boolean) -> Unit =
{ cancelSource, shouldPromptAmountEdit ->
@@ -102,7 +104,7 @@ fun OneClickCheckoutScreen(
LaunchedEffect(Unit) {
systemUiController.setStatusBarColor(Color.White)
oneClickCheckoutViewModel.startPayment(bundle = naviPaymentActivity.intent.extras)
oneClickCheckoutViewModel.startPaymentProcess(bundle = naviPaymentActivity.intent.extras)
}
BackHandler {
@@ -132,50 +134,6 @@ fun OneClickCheckoutScreen(
}
}
LaunchedEffect(key1 = sendMoneyStatus) {
when (val pmsSendMoneyStatus = sendMoneyStatus) {
is PMSSendMoneyStatus.Success -> {
val payNowResponse = oneClickCheckoutViewModel.payNowResponse
val payload = (payNowResponse?.providerPayload) as? NaviPayProcessPayload
if (
oneClickCheckoutViewModel.paymentSdkInitParams?.token.isNotNullAndNotEmpty() &&
payNowResponse?.transactionReferenceId.isNotNullAndNotEmpty()
) {
oneClickCheckoutViewModel.updateTransactionStatusToPending(
transactionReferenceId = payNowResponse?.transactionReferenceId
)
navigateToTransactionPollingScreen(
navigator = navigator,
oneClickCheckoutViewModel = oneClickCheckoutViewModel,
payload = payload,
payNowResponse = payNowResponse,
pmsSendMoneyStatus = pmsSendMoneyStatus,
)
}
}
is PMSSendMoneyStatus.Error -> { // do this in VM instead
var transactionStatus = TransactionStatus.ERROR.name
if (pmsSendMoneyStatus.errorConfig.code == CANCEL) {
transactionStatus = TransactionStatus.USER_CANCELLED_TRANSACTION.name
oneClickCheckoutViewModel.handleBackPress(
cancelSource = PaymentCancelSource.SYSTEM_BACK_CLICKED,
finishCallback = finishCallback,
)
} else {
oneClickCheckoutViewModel.notifyError(
errorConfig = pmsSendMoneyStatus.errorConfig.toNaviPaymentErrorConfig()
)
}
oneClickCheckoutViewModel.notifyNaviUpiExitWithError(
status = transactionStatus,
error = pmsSendMoneyStatus.errorConfig,
)
}
null -> {}
}
oneClickCheckoutViewModel.onNaviPayCallBack()
}
Column { Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) {} }
ObserveBottomSheetEvents(activity = naviPaymentActivity, finishCallback = finishCallback)
if (bottomSheetStateHolder.showBottomSheet) {
@@ -343,3 +301,31 @@ private fun navigateToTransactionPollingScreen(
)
)
}
@Composable
private fun EffectsHandler(
screenEffect: SharedFlow<OneClickCheckoutScreenEffect>,
activity: NaviPaymentActivity,
navigator: DestinationsNavigator,
) {
LaunchedEffect(Unit) {
screenEffect.collectLatest { effect ->
when (effect) {
is OneClickCheckoutScreenEffect.SetResultAndFinish -> {
handleResultAndFinish(
activity = activity,
resultCode = 0,
screenName = NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN.name,
cancelSource = effect.cancelSource,
slideDirection = AnimatedContentTransitionScope.SlideDirection.Down,
shouldPromptAmountEdit = effect.shouldPromptAmountEdit,
)
}
is OneClickCheckoutScreenEffect.Navigation -> {
navigator.navigate(effect.direction)
}
}
}
}
}

View File

@@ -31,7 +31,6 @@ import com.navi.base.utils.FAILURE
import com.navi.base.utils.SUCCESS
import com.navi.base.utils.isNotNull
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.uitron.model.action.UpiIntent
import com.navi.common.upi.CANCEL
@@ -255,22 +254,7 @@ fun WebPaymentMainScreen(
val onPayButtonClick: () -> Unit = {
try {
finishBackgroundTask(activity)
if (selectedBankAccount?.isMPinSet.orFalse().not()) {
naviPaymentAnalytics.onSetUpiPinBtnClick(
webPaymentAction = webPaymentAction,
webPaymentData = webPaymentData.toJsonObject(),
baseAttributes = webPaymentViewModel.getAnalyticsParams(),
)
webPaymentViewModel.updateLastTriggeredActionBeforeOnboarding(webPaymentAction)
webPaymentViewModel.handleMPinSetup(activity, upiResultLauncher)
} else {
naviPaymentAnalytics.onPayNowClicked(webPaymentViewModel.getAnalyticsParams())
webPaymentViewModel.startPayAmount(
selectedBankAccount?.accountId.orEmpty(),
selectedBankAccount?.accountType.orEmpty(),
)
}
webPaymentViewModel.onPayButtonClick(activity, upiResultLauncher)
} catch (exception: Exception) {
exception.log()
}
@@ -301,17 +285,7 @@ fun WebPaymentMainScreen(
}
}
val onBackClick: () -> Unit = {
naviPaymentAnalytics.onBackPressed(webPaymentViewModel.getAnalyticsParams())
webPaymentViewModel.finishWithResult {
handleResultAndFinish(
activity = activity,
webPaymentAction = webPaymentAction,
webPaymentData = webPaymentData,
status = TransactionStatus.USER_CANCELLED_TRANSACTION.name,
)
}
}
val onBackClick: () -> Unit = { webPaymentViewModel.handleBackClick() }
BackHandler { onBackClick.invoke() }
@@ -324,7 +298,7 @@ fun WebPaymentMainScreen(
bottomSheetStateHolder.showBottomSheet &&
webPaymentViewModel.isDismissAllowed
) {
onBackClick.invoke()
webPaymentViewModel.handleBackClick()
}
}
}
@@ -405,7 +379,7 @@ fun WebPaymentMainScreen(
NaviPayModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetContent = {
when (bottomSheetStateHolder.bottomSheetUIState) {
when (val bottomSheetUIState = bottomSheetStateHolder.bottomSheetUIState) {
is WebPaymentScreenState.Loading -> {
MPSLoadingShimmer()
}
@@ -427,19 +401,10 @@ fun WebPaymentMainScreen(
)
}
is WebPaymentScreenState.Error -> {
webPaymentViewModel.finishWithResult {
handleResultAndFinish(
activity = activity,
webPaymentAction = webPaymentAction,
webPaymentData = webPaymentData,
status = FAILURE,
error =
(bottomSheetStateHolder.bottomSheetUIState
as? WebPaymentScreenState.Error)
?.errorData
?.error,
)
}
webPaymentViewModel.finishWithResult(
status = FAILURE,
error = bottomSheetUIState.errorData?.error,
)
}
else -> {}
}

View File

@@ -0,0 +1,615 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.payment.nativepayment.usecase
import com.google.gson.Gson
import com.navi.base.utils.EMPTY
import com.navi.base.utils.ResourceProvider
import com.navi.common.checkmate.core.CheckMateManager
import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.upi.CANCEL
import com.navi.common.utils.safeLaunch
import com.navi.common.utils.toJsonObject
import com.navi.pay.common.model.view.NaviPayErrorConfig
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.management.common.utils.NaviPayPspManager
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboarding.binding.model.view.NaviPayCustomerOnboardingEntity
import com.navi.payment.R
import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.common.usecase.TransactionStatusUseCase
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.PAYMENT_ORDER_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.TRANSACTION_REFERENCE_ID
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.UpiLinkedAccountPaymentInstrument
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatusRequest
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.utils.toGenericError
import com.navi.payment.nativepayment.utils.toGenericErrorResponse
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.turbocheckout.model.PayAmountRequest
import com.navi.payment.utils.getPMSMetricInfo
import com.navi.payment.utils.isUpiLiteAccount
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.joda.time.DateTime
/**
* NaviUpiPaymentProcessor
*
* A centralized, injectable processor that manages the complete UPI payment lifecycle (for both
* Lite and Non-Lite accounts) within the Navi Payments SDK. This class abstracts away the
* complexities of onboarding, payment initiation, analytics, and error management, providing a
* single, unified interface for all UPI payment flows.
*
* ## Purpose & Architectural Role
* - Serves as the canonical entry point for all UPI payment operations in the payments module.
* - Eliminates code duplication and branching logic that previously existed across multiple
* ViewModels (e.g., NPSViewModel, MPSViewModel).
* - Ensures a consistent, maintainable, and extensible payment flow for all UPI transactions,
* regardless of account type.
*
* ## Usage
* ViewModels and other payment orchestration components should delegate payment initiation to this
* processor via the [makePayment] function, providing all necessary parameters and callbacks for
* onboarding, payment, and error handling. This guarantees that any changes to payment flow,
* analytics, or error handling are managed in a single location.
*
* ## Benefits to the Payments System
* - **Maintainability:** Updates to payment flow, analytics, or error handling are made in one
* place and instantly propagate.
* - **Consistency:** All UPI payment flows (Lite/Non-Lite) follow the same process, reducing the
* risk of bugs and divergent behavior.
* - **Extensibility:** New features, account types, or analytics hooks can be added centrally
* without modifying multiple ViewModels.
* - **Code Quality:** Reduces surface area for errors, simplifies onboarding for new developers,
* and enforces best practices across the payments module.
*/
class NaviUpiPaymentProcessor
@Inject
constructor(
private val naviPayPspManager: NaviPayPspManager,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
private val paymentDataProvider: PaymentDataProvider,
private val paymentRepository: PaymentRepository,
private val transactionStatusUseCase: TransactionStatusUseCase,
private val resourceProvider: ResourceProvider,
) {
// arguments
private lateinit var paymentToken: String
private lateinit var account: LinkedAccountEntity
private lateinit var naviPaymentScreen: NaviPaymentScreenType
// analytics
private lateinit var analyticScreenName: String
private lateinit var baseAttributes: Map<String, String>
private val pspManagerAnalytics: NaviPaymentAnalytics.PspManager =
NaviPaymentAnalytics.INSTANCE.PspManager()
private val paymentProcessorAnalytics: NaviPaymentAnalytics.NaviUpiPaymentProcessor =
NaviPaymentAnalytics.INSTANCE.NaviUpiPaymentProcessor()
/**
* Initiates a UPI payment (Lite or Non-Lite) for the provided account, centralizing all payment
* logic.
*
* This function is designed to be the single entry point for all UPI payment flows, eliminating
* code duplication across multiple ViewModels (such as NPSViewModel, MPSViewModel, etc). It
* encapsulates the entire payment lifecycle: onboarding, payment initiation, PIN handling,
* analytics, and error management, via a unified interface.
*
* Usage:
* - ViewModels should delegate payment initiation to this function, passing all required
* parameters and callbacks.
* - This ensures that any updates to payment flow, analytics, or error handling are managed in
* one place.
*
* @param account The UPI-linked account to make the payment from. Must have MPIN set.
* @param isDiscountApplied Whether a discount (e.g., Navi Coins) is being applied in this
* payment.
* @param discountInfo List of applied discount info (if any).
* @param analyticScreenName Screen name for analytics and event tracking.
* @param naviPaymentScreen Enum representing which payment screen is invoking the flow (e.g.,
* FULL_PAYMENT_SCREEN).
* @param paymentToken Token received from the payment backend for this transaction.
* @param baseAttributes Analytics attributes for tracking and reporting.
* @param onInitialChecksFailure Callback for initial validation failure.
* @param onOnboardingTriggered Callback when onboarding is triggered for the account.
* @param onOnboardingCancelled Callback when onboarding is cancelled by the user.
* @param onOnboardingCompleted Callback when onboarding is completed successfully.
* @param onBeforePayNowApiCall Callback before making the PayNow API call (e.g., show loader).
* @param onPayNowSuccess Callback on successful PayNow API response.
* @param onPayNowFailure Callback on failed PayNow API response.
* @param onBeforeOpeningPinPage Callback before navigating to the PIN page.
* @param onPinPageBack Callback when the PIN page is dismissed/backed out.
* @param onPinPageError Callback on error during PIN entry/validation.
* @param onPinPageSuccess Callback on successful PIN entry and payment completion.
*
* Example usage in a ViewModel:
* ```kotlin
* naviUpiPaymentProcessor.makePayment(
* account = selectedBankAccount,
* isDiscountApplied = npsScreenState.naviCoinState.isDiscountApplied,
* discountInfo = createDiscountInfo(),
* analyticScreenName = screenName,
* naviPaymentScreen = NaviPaymentScreenType.FULL_PAYMENT_SCREEN,
* paymentToken = paymentSdkInitParams?.token.orEmpty(),
* baseAttributes = baseAnalyticsParams,
* ... // pass all required callbacks
* )
* ```
*
* This approach ensures all UPI payment flows are consistent, maintainable, and easily
* extensible.
*/
suspend fun makePayment(
account: LinkedAccountEntity,
isDiscountApplied: Boolean,
discountInfo: List<PayAmountRequest.DiscountInfo>?,
analyticScreenName: String,
naviPaymentScreen: NaviPaymentScreenType,
paymentToken: String,
baseAttributes: Map<String, String>,
onInitialChecksFailure: (GenericErrorResponse) -> Unit,
onOnboardingTriggered: () -> Unit,
onOnboardingCancelled: () -> Unit,
onOnboardingCompleted: () -> Unit,
onBeforePayNowApiCall: () -> Unit,
onPayNowSuccess: (PayNowResponse) -> Unit,
onPayNowFailure: (GenericErrorResponse) -> Unit,
onBeforeOpeningPinPage: () -> Unit,
onPinPageBack: () -> Unit,
onPinPageError: (GenericErrorResponse) -> Unit,
onPinPageSuccess: suspend (UpiNavigationResponse) -> Unit,
) {
if (initialChecksFailed(account, paymentToken, analyticScreenName, onInitialChecksFailure))
return
initialise(analyticScreenName, naviPaymentScreen, baseAttributes, paymentToken, account)
when {
account.isUpiLiteAccount() ->
processUpiLitePayment(
isDiscountApplied = isDiscountApplied,
discountInfo = discountInfo,
onInitialChecksFailure = onInitialChecksFailure,
onOnboardingTriggered = onOnboardingTriggered,
onOnboardingCancelled = onOnboardingCancelled,
onOnboardingCompleted = onOnboardingCompleted,
onBeforePayNowApiCall = onBeforePayNowApiCall,
onPayNowFailure = onPayNowFailure,
onPayNowSuccess = onPayNowSuccess,
onBeforeOpeningPinPage = onBeforeOpeningPinPage,
onPinPageBack = onPinPageBack,
onPinPageError = onPinPageError,
onPinPageSuccess = onPinPageSuccess,
)
else ->
processNonUpiLitePayment(
isDiscountApplied = isDiscountApplied,
discountInfo = discountInfo,
onOnboardingTriggered = onOnboardingTriggered,
onOnboardingCancelled = onOnboardingCancelled,
onOnboardingCompleted = onOnboardingCompleted,
onBeforePayNowApiCall = onBeforePayNowApiCall,
onPayNowFailure = onPayNowFailure,
onPayNowSuccess = onPayNowSuccess,
onBeforeOpeningPinPage = onBeforeOpeningPinPage,
onPinPageBack = onPinPageBack,
onPinPageError = onPinPageError,
onPinPageSuccess = onPinPageSuccess,
)
}
}
private fun initialChecksFailed(
account: LinkedAccountEntity,
paymentToken: String,
analyticScreenName: String,
onInitialChecksFailure: (GenericErrorResponse) -> Unit,
): Boolean {
if (account.isMPinSet.not()) return true
if (paymentToken.isEmpty()) {
CheckMateManager.logAppErrorEvent(
metricInfo = MetricInfo.PMSMetric<Any>(screen = analyticScreenName),
errorCode = resourceProvider.getString(R.string.empty_payment_token),
errorTitle = resourceProvider.getString(R.string.something_went_wrong),
errorDes = resourceProvider.getString(R.string.technical_issue),
)
onInitialChecksFailure(
GenericErrorResponse(
title = resourceProvider.getString(R.string.something_went_wrong),
message = resourceProvider.getString(R.string.technical_issue),
code = resourceProvider.getString(R.string.empty_payment_token),
logMessage = resourceProvider.getString(R.string.empty_payment_token),
)
)
paymentProcessorAnalytics.onEmptyPaymentToken(
screenName = analyticScreenName,
baseAttributes = baseAttributes,
)
return true
}
return false
}
private fun initialise(
analyticScreenName: String,
naviPaymentScreen: NaviPaymentScreenType,
baseAttributes: Map<String, String>,
paymentToken: String,
account: LinkedAccountEntity,
) {
this.analyticScreenName = analyticScreenName
this.naviPaymentScreen = naviPaymentScreen
this.baseAttributes = baseAttributes
this.paymentToken = paymentToken
this.account = account
}
private suspend fun processUpiLitePayment(
isDiscountApplied: Boolean,
discountInfo: List<PayAmountRequest.DiscountInfo>?,
onInitialChecksFailure: (GenericErrorResponse) -> Unit,
onOnboardingTriggered: () -> Unit,
onOnboardingCancelled: () -> Unit,
onOnboardingCompleted: () -> Unit,
onBeforePayNowApiCall: () -> Unit,
onPayNowFailure: (GenericErrorResponse) -> Unit,
onPayNowSuccess: (PayNowResponse) -> Unit,
onBeforeOpeningPinPage: () -> Unit,
onPinPageBack: () -> Unit,
onPinPageError: (GenericErrorResponse) -> Unit,
onPinPageSuccess: suspend (UpiNavigationResponse) -> Unit,
) {
if (account.activeLiteAccountPsp == null) {
CheckMateManager.logAppErrorEvent(
metricInfo = MetricInfo.PMSMetric<Any>(screen = analyticScreenName),
errorCode = resourceProvider.getString(R.string.no_active_lite_account_psp),
errorTitle = resourceProvider.getString(R.string.something_went_wrong),
errorDes = resourceProvider.getString(R.string.technical_issue),
)
onInitialChecksFailure(getUpiLiteChecksFailureErrorConfig())
return
}
val vpaEntityForLiteAccount =
account.getVpaEntityByPsp(account.activeLiteAccountPsp ?: return)
paymentProcessorAnalytics.onUpiLitePayment(
screenName = analyticScreenName,
isDiscountApplied = isDiscountApplied.toString(),
baseAttributes = baseAttributes,
)
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = account.accountId,
isPaymentFromLiteAccount = true,
screenName = analyticScreenName,
)
naviPayPspManager.evaluateAndOnboardPspForVpa(
vpaEntity = vpaEntityForLiteAccount,
screenName = analyticScreenName,
onOnboardingTriggered = { onOnboardingTriggered() },
naviPayFlowType = NaviPayFlowType.UPI_LITE,
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, analyticScreenName)
if (pspEvaluationResult.isOnboardingTriggered) {
if (pspEvaluationResult.onboardingDataEntity == null) {
onOnboardingCancelled()
return@evaluateAndOnboardPspForVpa
} else {
onOnboardingCompleted()
}
}
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
callPayNowApi(
discountInfo = discountInfo,
onboardingDataEntity = onboardingEntity,
onBeforePayNowApiCall = onBeforePayNowApiCall,
onPayNowSuccess = onPayNowSuccess,
onPayNowFailure = onPayNowFailure,
onBeforeOpeningPinPage = onBeforeOpeningPinPage,
onPinPageBack = onPinPageBack,
onPinPageError = onPinPageError,
onPinPageSuccess = onPinPageSuccess,
)
}
},
)
}
private suspend fun processNonUpiLitePayment(
isDiscountApplied: Boolean,
discountInfo: List<PayAmountRequest.DiscountInfo>?,
onOnboardingTriggered: () -> Unit,
onOnboardingCancelled: () -> Unit,
onOnboardingCompleted: () -> Unit,
onBeforePayNowApiCall: () -> Unit,
onPayNowFailure: (GenericErrorResponse) -> Unit,
onPayNowSuccess: (PayNowResponse) -> Unit,
onBeforeOpeningPinPage: () -> Unit,
onPinPageBack: () -> Unit,
onPinPageError: (GenericErrorResponse) -> Unit,
onPinPageSuccess: suspend (UpiNavigationResponse) -> Unit,
) {
paymentProcessorAnalytics.onNonUpiLitePayment(
screenName = analyticScreenName,
isDiscountApplied = isDiscountApplied.toString(),
accountId = account.accountId,
baseAttributes = baseAttributes,
)
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = account.accountId,
isPaymentFromLiteAccount = false,
screenName = analyticScreenName,
)
naviPayPspManager.evaluateAndOnboardPspForFlow(
naviPayFlowType = NaviPayFlowType.getSendMoneyFlowForAccountType(account.accountType),
vpaEntityList = account.vpaEntityList,
screenName = analyticScreenName,
onOnboardingTriggered = { onOnboardingTriggered() },
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, analyticScreenName)
if (pspEvaluationResult.isOnboardingTriggered) {
if (pspEvaluationResult.onboardingDataEntity == null) {
onOnboardingCancelled()
return@evaluateAndOnboardPspForFlow
} else {
onOnboardingCompleted()
}
}
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
callPayNowApi(
discountInfo = discountInfo,
onboardingDataEntity = onboardingEntity,
onBeforePayNowApiCall = onBeforePayNowApiCall,
onPayNowSuccess = onPayNowSuccess,
onPayNowFailure = onPayNowFailure,
onBeforeOpeningPinPage = onBeforeOpeningPinPage,
onPinPageBack = onPinPageBack,
onPinPageError = onPinPageError,
onPinPageSuccess = onPinPageSuccess,
)
}
},
)
}
private suspend fun callPayNowApi(
discountInfo: List<PayAmountRequest.DiscountInfo>?,
onboardingDataEntity: NaviPayCustomerOnboardingEntity,
onBeforePayNowApiCall: () -> Unit,
onPayNowSuccess: (PayNowResponse) -> Unit,
onPayNowFailure: (GenericErrorResponse) -> Unit,
onBeforeOpeningPinPage: () -> Unit,
onPinPageBack: () -> Unit,
onPinPageError: (GenericErrorResponse) -> Unit,
onPinPageSuccess: suspend (UpiNavigationResponse) -> Unit,
) {
onBeforePayNowApiCall()
val payNowResponse =
paymentRepository.postPayNow(
token = paymentToken,
data =
PayAmountRequest(
methodName = UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
PayAmountRequest.SelectedMethodDetails(
payerBankAccountId = account.accountId,
accountType = account.accountType,
),
discountInfo = discountInfo,
),
metricInfo = getPMSMetricInfo(screenName = analyticScreenName),
)
val internalPayNowResponse = payNowResponse.data as? InternalPayNowResponse
val intentUri = internalPayNowResponse?.providerPayload?.naviPayUpiUriKey.orEmpty()
val transactionReferenceId = internalPayNowResponse?.transactionReferenceId.orEmpty()
val paymentOrderReferenceId = internalPayNowResponse?.paymentOrderReferenceId.orEmpty()
if (
payNowResponse.isSuccessWithData() &&
internalPayNowResponse != null &&
intentUri.isNotEmpty() &&
transactionReferenceId.isNotEmpty() &&
paymentOrderReferenceId.isNotEmpty()
) {
updateTransactionInfoInDataProvider(transactionReferenceId, paymentOrderReferenceId)
insertTransactionStatusRequest(transactionReferenceId)
onPayNowSuccess(internalPayNowResponse)
startNaviUpiPayment(
payNowResponse = internalPayNowResponse,
intentUri = intentUri,
transactionReferenceId = transactionReferenceId,
paymentOrderReferenceId = paymentOrderReferenceId,
onboardingDataEntity = onboardingDataEntity,
onBeforeOpeningPinPage = onBeforeOpeningPinPage,
onPinPageBack = onPinPageBack,
onPinPageError = onPinPageError,
onPinPageSuccess = onPinPageSuccess,
)
} else {
val genericErrorResponse = payNowResponse.toGenericErrorResponse()
paymentProcessorAnalytics.onPayNowChecksFailed(
analyticScreenName,
transactionReferenceId,
paymentOrderReferenceId,
intentUri,
internalPayNowResponse,
genericErrorResponse,
)
onPayNowFailure(genericErrorResponse)
return
}
}
private suspend fun startNaviUpiPayment(
payNowResponse: InternalPayNowResponse,
intentUri: String,
transactionReferenceId: String,
paymentOrderReferenceId: String,
onboardingDataEntity: NaviPayCustomerOnboardingEntity,
onBeforeOpeningPinPage: () -> Unit,
onPinPageBack: () -> Unit,
onPinPageError: (GenericErrorResponse) -> Unit,
onPinPageSuccess: suspend (UpiNavigationResponse) -> Unit,
) {
onBeforeOpeningPinPage()
val metaData = Gson().toJson(payNowResponse.providerPayload?.metadata)
paymentProcessorAnalytics.onPinPageOpened(
screenName = analyticScreenName,
naviPayUpiUriKey = intentUri,
transactionReferenceId = transactionReferenceId,
baseAttributes = baseAttributes,
)
val transactionTimeStamp = DateTime.now().toString()
val pmsSendMoneyStatus =
pmsSendMoneyUseCase.performChecksAndGenerateCredBlock(
naviPayUpiUriKey = intentUri,
screenName = naviPaymentScreen.name,
linkedAccountEntity = account,
baseAnalyticsParam = baseAttributes,
txnTimeStamp = transactionTimeStamp,
onboardingDataEntity = onboardingDataEntity,
)
when (pmsSendMoneyStatus) {
is PMSSendMoneyStatus.Error -> {
val naviPayErrorConfig = pmsSendMoneyStatus.errorConfig
var transactionStatus = TransactionStatus.ERROR.name
if (naviPayErrorConfig.code == CANCEL) {
paymentProcessorAnalytics.onPinPageCancelled(
screenName = analyticScreenName,
naviPayUpiUriKey = intentUri,
transactionReferenceId = transactionReferenceId,
baseAttributes = baseAttributes,
)
onPinPageBack()
transactionStatus = TransactionStatus.USER_CANCELLED_TRANSACTION.name
} else {
paymentProcessorAnalytics.onPinPageError(
screenName = analyticScreenName,
naviPayUpiUriKey = intentUri,
transactionReferenceId = transactionReferenceId,
errorCode = naviPayErrorConfig,
baseAttributes = baseAttributes,
)
onPinPageError(naviPayErrorConfig.toGenericError())
}
notifyNaviUpiExitWithError(
status = transactionStatus,
transactionReferenceId = transactionReferenceId,
error = naviPayErrorConfig,
)
}
is PMSSendMoneyStatus.Success -> {
paymentProcessorAnalytics.onPinPageSuccess(
screenName = analyticScreenName,
naviPayUpiUriKey = intentUri,
transactionReferenceId = transactionReferenceId,
baseAttributes = baseAttributes,
)
updateTransactionStatusToPending(transactionReferenceId)
val navigationResponse =
UpiNavigationResponse(
paymentToken = paymentToken,
transactionTimestamp = transactionTimeStamp,
pmsSendMoneyStatus = pmsSendMoneyStatus,
internalPayNowResponse = payNowResponse,
metaData = metaData,
intentUri = intentUri,
tstoreOrderReferenceId =
payNowResponse.providerPayload?.tstoreOrderReferenceId.orEmpty(),
transactionReferenceId = transactionReferenceId,
paymentOrderReferenceId = paymentOrderReferenceId,
)
onPinPageSuccess(navigationResponse)
}
}
}
private fun notifyNaviUpiExitWithError(
status: String,
transactionReferenceId: String,
error: NaviPayErrorConfig?,
) {
CoroutineScope(Dispatchers.IO).safeLaunch {
transactionStatusUseCase.updateAndPostTransactionStatus(
token = paymentToken,
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId = transactionReferenceId,
event = error.toJsonObject().toString(),
screenType = naviPaymentScreen.name,
status = status,
),
)
}
}
private fun updateTransactionInfoInDataProvider(
transactionReferenceId: String,
paymentOrderReferenceId: String,
) {
paymentDataProvider.add(TRANSACTION_REFERENCE_ID, transactionReferenceId)
paymentDataProvider.add(PAYMENT_ORDER_REFERENCE_ID, paymentOrderReferenceId)
}
private fun insertTransactionStatusRequest(transactionReferenceId: String) {
CoroutineScope(Dispatchers.IO).safeLaunch {
transactionStatusUseCase.insertTransactionStatusRequest(
TransactionStatusRequestEntity(
transactionReferenceId = transactionReferenceId,
token = paymentToken,
status = TransactionStatus.INITIATED.name,
event = EMPTY,
screenType = naviPaymentScreen.name,
)
)
}
}
private fun updateTransactionStatusToPending(transactionReferenceId: String) {
CoroutineScope(Dispatchers.IO).safeLaunch {
transactionStatusUseCase.updateTransactionStatusRequest(
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId = transactionReferenceId,
status = TransactionStatus.PENDING.name,
event = EMPTY,
)
)
}
}
private fun getUpiLiteChecksFailureErrorConfig(): GenericErrorResponse {
return GenericErrorResponse(
title = resourceProvider.getString(R.string.something_went_wrong),
message = resourceProvider.getString(R.string.technical_issue),
code = resourceProvider.getString(R.string.no_active_lite_account_psp),
logMessage = resourceProvider.getString(R.string.no_active_lite_account_psp),
)
}
}
data class UpiNavigationResponse(
val paymentToken: String,
val transactionTimestamp: String,
val pmsSendMoneyStatus: PMSSendMoneyStatus.Success,
val internalPayNowResponse: InternalPayNowResponse,
val intentUri: String,
val metaData: String,
val tstoreOrderReferenceId: String,
val transactionReferenceId: String,
val paymentOrderReferenceId: String,
)

View File

@@ -21,10 +21,10 @@ import com.navi.base.utils.orZero
import com.navi.common.checkmate.core.CheckMateManager
import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.model.ModuleNameV2
import com.navi.common.model.RequestConfig
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.upi.CANCEL
import com.navi.common.upi.NAVI_PAY_RESPONSE
import com.navi.common.upi.NaviPayAction
import com.navi.common.upi.TYPE
@@ -32,9 +32,7 @@ import com.navi.common.upi.UpiDataType
import com.navi.common.usecase.LitmusExperimentsUseCase
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.stringToJsonObject
import com.navi.common.utils.toJsonObject
import com.navi.pay.common.model.view.CheckBalanceState
import com.navi.pay.common.model.view.NaviPayErrorConfig
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.common.setup.NaviPayManager
import com.navi.pay.common.usecase.AccountListCheckBalanceUseCase
@@ -60,32 +58,30 @@ import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.activity.NaviPaymentActivity
import com.navi.payment.nativepayment.common.usecase.TransactionStatusUseCase
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.PAYMENT_ORDER_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.TRANSACTION_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.UPI_LITE_MAX_PAYABLE_AMOUNT_PER_TRANSACTION
import com.navi.payment.nativepayment.dataprovider.PinAction
import com.navi.payment.nativepayment.dataprovider.getMpinResetAction
import com.navi.payment.nativepayment.dataprovider.getMpinSetAction
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.BasePaymentMethodResponse
import com.navi.payment.nativepayment.model.CoinDetails
import com.navi.payment.nativepayment.model.CoinRewards
import com.navi.payment.nativepayment.model.LoaderConfig
import com.navi.payment.nativepayment.model.MPSBottomSheetStateHolder
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.OnboardingDetails
import com.navi.payment.nativepayment.model.PMSLinkedAccountEntity
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.PaymentsMainCtaState
import com.navi.payment.nativepayment.model.RewardsInfoV2
import com.navi.payment.nativepayment.model.S2sPaymentMethodResponse
import com.navi.payment.nativepayment.model.UpiLinkedAccountPaymentInstrument
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatusRequest
import com.navi.payment.nativepayment.presentation.reducer.MPSScreenEffect
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.screens.destinations.TransactionPollingScreenDestination
import com.navi.payment.nativepayment.usecase.NaviUpiPaymentProcessor
import com.navi.payment.nativepayment.usecase.PmsLinkedAccountUseCase
import com.navi.payment.nativepayment.usecase.PmsSendMoneyUseCase
import com.navi.payment.nativepayment.usecase.UpiNavigationResponse
import com.navi.payment.nativepayment.utils.MPSScreenUtils
import com.navi.payment.nativepayment.utils.NaviPaymentErrorConfig
import com.navi.payment.nativepayment.utils.NaviPaymentEventHandler
@@ -99,11 +95,11 @@ import com.navi.payment.nativepayment.utils.getLastSelectedAccountBeforeAccounts
import com.navi.payment.nativepayment.utils.getPayloadBasedOnType
import com.navi.payment.nativepayment.utils.mapToLinkedAccountEntity
import com.navi.payment.nativepayment.utils.rankUpiAccountsByEligibility
import com.navi.payment.nativepayment.utils.toGenericError
import com.navi.payment.nativepayment.utils.toGenericErrorResponse
import com.navi.payment.nativepayment.utils.toPaymentNaviPaymentErrorConfig
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.paymentscreen.model.PMSErrorReason
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.PaymentErrorData
@@ -121,7 +117,6 @@ import com.navi.payment.utils.getArcNudgeBottomSheetSubtitle
import com.navi.payment.utils.getArcNudgeBottomSheetTitle
import com.navi.payment.utils.getInstalledUpiApps
import com.navi.payment.utils.getPMSMetricInfo
import com.navi.payment.utils.isUpiLiteAccount
import com.navi.payment.utils.requireTpvSupport
import com.navi.payment.utils.roundTo
import com.navi.payment.utils.validateUpiProcessPayload
@@ -131,13 +126,15 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.joda.time.DateTime
@HiltViewModel
class MPSViewModel
@@ -155,8 +152,8 @@ constructor(
private val transactionStatusUseCase: TransactionStatusUseCase,
private val accountListCheckBalanceUseCase: AccountListCheckBalanceUseCase,
private val arcNudgeUseCase: ArcNudgeUseCase,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
private val naviPayPspManager: NaviPayPspManager,
private val naviUpiPaymentProcessor: NaviUpiPaymentProcessor,
@PaymentsSdkRetrofit private val deserializer: Gson,
) :
NaviPaymentBaseVM(
@@ -230,11 +227,9 @@ constructor(
var paymentSdkInitParams: PaymentSdkInitParams? = null
var addAccountType: List<String>? = null
private val _pmsSendMoneyStatus = MutableStateFlow<PMSSendMoneyStatus?>(null)
val pmsSendMoneyStatus = _pmsSendMoneyStatus.asStateFlow()
var loaderConfig: LoaderConfig? = null
var txnTimeStamp: String = EMPTY
private val _effect = MutableSharedFlow<MPSScreenEffect>()
val effect: SharedFlow<MPSScreenEffect>
get() = _effect.asSharedFlow()
private val _bottomSheetStateHolder =
MutableStateFlow(
@@ -328,7 +323,6 @@ constructor(
methodDetails.paymentOrderRefId.orEmpty(),
)
updatePaymentAmount(methodDetails.amount?.value.orZero())
updateLottieConfig(methodDetails.loaderConfig)
if (paymentSdkInitParams?.rewardsInfo?.isEnabled.orFalse()) {
getRewardInfoV2()
} else {
@@ -343,10 +337,6 @@ constructor(
_paymentAmount.update { amount }
}
private fun updateLottieConfig(loaderConfig: LoaderConfig?) {
this.loaderConfig = loaderConfig
}
private suspend fun processAvailablePaymentMethods(
availablePaymentMethods: List<BasePaymentInstrument>
) {
@@ -532,102 +522,6 @@ constructor(
_mainCtaState.update { mainCtaState }
}
private fun startPayAmount(onboardingDataEntity: NaviPayCustomerOnboardingEntity) =
viewModelScope.launch {
if (showPayNowLoader.value) return@launch
_showPayNowLoader.update { true }
val response = fetchPayNowResponse()
if (response.isSuccessWithData()) {
paymentDataProvider.add(
TRANSACTION_REFERENCE_ID,
response.data?.transactionReferenceId.orEmpty(),
)
paymentDataProvider.add(
PAYMENT_ORDER_REFERENCE_ID,
response.data?.paymentOrderReferenceId.orEmpty(),
)
response.data?.transactionReferenceId?.let { transactionReferenceId ->
insertTransactionStatusRequest(
transactionReferenceId = transactionReferenceId,
token = paymentSdkInitParams?.token.orEmpty(),
screenType = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
event = EMPTY,
status = TransactionStatus.INITIATED.name,
)
}
payNowResponse = response.data
(response.data as? InternalPayNowResponse)?.let {
startNaviUpiPayment(payNowResponse = it, onboardingDataEntity)
}
} else {
val errorResponse = response.toGenericErrorResponse()
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState =
MPSScreenUtils.MPSScreenType.Error(
errorData =
PaymentErrorData(
error = errorResponse,
errorReason = PMSErrorReason.PayNowError,
)
),
bottomSheetStateChange = true,
)
}
_showPayNowLoader.update { false }
}
private suspend fun fetchPayNowResponse() =
paymentRepository.postPayNow(
token = paymentSdkInitParams?.token,
data =
PayAmountRequest(
methodName = UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
PayAmountRequest.SelectedMethodDetails(
payerBankAccountId = selectedBankAccount.value?.accountId,
accountType = selectedBankAccount.value?.accountType,
),
discountInfo =
if (isDiscountApplied.value) {
listOf(
PayAmountRequest.DiscountInfo(
type = PayAmountRequest.DiscountType.COIN.name,
amount =
if (rewardsInfoV2.value.isNull())
coinBurnDetails.value?.redeemableCoinsValue
else rewardsInfoV2.value?.burnDetails?.coinsValue,
discountMetadata =
PayAmountRequest.DiscountMetadata(
coinsUsed =
if (rewardsInfoV2.value.isNull())
coinBurnDetails.value?.redeemableCoins
else {
val coins =
rewardsInfoV2.value?.burnDetails?.coins
val coinValue =
coins
?.replace(",", "")
?.toIntOrNull()
.orZero()
coinValue
}
),
)
)
} else null,
),
metricInfo =
getPMSMetricInfo(
screenName = NaviPaymentAnalyticScreenName.MINI_PAYMENT_SCREEN.screenName
),
)
fun onNaviPayCallBack() {
payNowResponse = null
_pmsSendMoneyStatus.update { null }
}
private suspend fun updateSDKInitParams() {
paymentSdkInitParams = paymentDataProvider.getSdkInitParams()
paymentDataProvider.updateAnalyticsParams(
@@ -829,44 +723,6 @@ constructor(
}
}
private fun notifyNaviUpiExitWithError(status: String, error: NaviPayErrorConfig?) {
coroutineScope.safeLaunch(Dispatchers.IO) {
paymentSdkInitParams?.token?.let {
transactionStatusUseCase.updateAndPostTransactionStatus(
token = it,
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId =
paymentDataProvider.get(TRANSACTION_REFERENCE_ID).toString(),
event = error.toJsonObject().toString(),
screenType = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
status = status,
),
)
}
}
}
private fun insertTransactionStatusRequest(
transactionReferenceId: String,
token: String,
event: String,
screenType: String,
status: String,
) {
coroutineScope.safeLaunch(Dispatchers.IO) {
transactionStatusUseCase.insertTransactionStatusRequest(
TransactionStatusRequestEntity(
transactionReferenceId = transactionReferenceId,
token = token,
status = status,
event = event,
screenType = screenType,
)
)
}
}
fun updateFeedbackOptions(option: String) {
if (feedBackOptions.value.contains(option)) {
_feedBackOptions.update { it.minus(option) }
@@ -902,10 +758,6 @@ constructor(
}
}
suspend fun isUserOnboardedForAnyPsp(): Boolean {
return naviPayManager.isUserOnboardedForAnyPsp()
}
fun onDiscountClicked(isDiscountApplied: Boolean) {
viewModelScope.launch(Dispatchers.Default) {
_isDiscountApplied.update { isDiscountApplied }
@@ -991,12 +843,16 @@ constructor(
}
}
fun updateCheckBalanceAccountId(accountId: String) {
private fun updateCheckBalanceAccountId(accountId: String) {
_checkBalanceAccountId.update { accountId }
}
fun updateShowPayNowLoader(show: Boolean) {
_showPayNowLoader.update { show }
private fun showPayNowLoader() {
_showPayNowLoader.update { true }
}
private fun stopPayNowLoader() {
_showPayNowLoader.update { false }
}
fun onArcNudgeClicked() {
@@ -1056,69 +912,6 @@ constructor(
}
}
private suspend fun startNaviUpiPayment(
payNowResponse: InternalPayNowResponse,
onboardingDataEntity: NaviPayCustomerOnboardingEntity,
) {
val payLoad = payNowResponse.providerPayload
naviPaymentAnalytics.onRedirectionToPinPage(
naviPayUpiUriKey = payLoad?.naviPayUpiUriKey.orEmpty(),
transactionReferenceId = payNowResponse.transactionReferenceId.orEmpty(),
baseAttributes = getAnalyticsParams(),
)
txnTimeStamp = DateTime.now().toString()
val pmsSendMoneyStatus =
pmsSendMoneyUseCase.performChecksAndGenerateCredBlock(
naviPayUpiUriKey = payLoad?.naviPayUpiUriKey.orEmpty(),
linkedAccountEntity = selectedBankAccount.value,
screenName = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
baseAnalyticsParam = getAnalyticsParams(),
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = onboardingDataEntity,
)
when (pmsSendMoneyStatus) {
is PMSSendMoneyStatus.Error -> {
var transactionStatus = TransactionStatus.ERROR.name
val errorConfig = pmsSendMoneyStatus.errorConfig
if (errorConfig.code == CANCEL) {
transactionStatus = TransactionStatus.USER_CANCELLED_TRANSACTION.name
updateShowPayNowLoader(false)
updateBottomSheetUIState(true)
} else {
updateBottomSheetUIState(
showBottomSheet = true,
bottomSheetUIState =
MPSScreenUtils.MPSScreenType.Error(
PaymentErrorData(
error = errorConfig.toGenericError(),
errorReason = PMSErrorReason.UPIError,
)
),
bottomSheetStateChange = true,
)
}
notifyNaviUpiExitWithError(
status = transactionStatus,
error = pmsSendMoneyStatus.errorConfig,
)
}
is PMSSendMoneyStatus.Success -> {
bottomSheetPreSendMoneyState = bottomSheetStateHolder.value
transactionStatusUseCase.updateTransactionStatusRequest(
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId =
payNowResponse.transactionReferenceId.orEmpty(),
event = EMPTY,
screenType = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
status = TransactionStatus.PENDING.name,
)
)
}
}
_pmsSendMoneyStatus.update { pmsSendMoneyStatus }
}
private fun getRewardInfoV2() {
naviPaymentAnalytics.onOfferExperienceBannerShown(
rewardsInfoV2 = paymentSdkInitParams?.rewardsInfo,
@@ -1179,15 +972,54 @@ constructor(
upiResultLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
) {
val selectedBankAccountValue = selectedBankAccount.value ?: return
if (showPayNowLoader.value) return
when {
selectedBankAccountValue.isUpiLiteAccount() ->
processUpiLitePayment(selectedBankAccountValue)
selectedBankAccountValue.isMPinSet.not() ->
handleMPinSetup(selectedBankAccountValue, naviPaymentActivity, upiResultLauncher)
else -> processNonUpiLitePayment(selectedBankAccountValue)
else -> makePayment(selectedBankAccountValue)
}
}
private suspend fun makePayment(selectedBankAccountValue: LinkedAccountEntity) {
showPayNowLoader()
naviUpiPaymentProcessor.makePayment(
account = selectedBankAccountValue,
isDiscountApplied = isDiscountApplied.value,
discountInfo = createDiscountInfo(),
analyticScreenName = NaviPaymentAnalyticScreenName.MINI_PAYMENT_SCREEN.screenName,
naviPaymentScreen = NaviPaymentScreenType.MINI_PAYMENT_SCREEN,
paymentToken = paymentSdkInitParams?.token.orEmpty(),
baseAttributes = getAnalyticsParams(),
onInitialChecksFailure = { showErrorState(PaymentErrorData(error = it)) },
onOnboardingTriggered = { updateBottomSheetUIState(showBottomSheet = false) },
onOnboardingCancelled = {
stopPayNowLoader()
updateBottomSheetUIState(showBottomSheet = true)
},
onOnboardingCompleted = { updateBottomSheetUIState(showBottomSheet = true) },
onBeforePayNowApiCall = {},
onPayNowSuccess = { payNowResponse = it },
onPayNowFailure = {
stopPayNowLoader()
showErrorState(
PaymentErrorData(error = it, errorReason = PMSErrorReason.PayNowError)
)
},
onBeforeOpeningPinPage = {},
onPinPageBack = {
stopPayNowLoader()
updateBottomSheetUIState(true)
},
onPinPageError = {
stopPayNowLoader()
showErrorState(
PaymentErrorData(error = it, errorReason = PMSErrorReason.PayNowError)
)
},
onPinPageSuccess = { onPinPageSuccess(selectedBankAccountValue, it) },
)
}
fun handleMPinSetup(
selectedBankAccountValue: LinkedAccountEntity,
naviPaymentActivity: NaviPaymentActivity,
@@ -1250,72 +1082,6 @@ constructor(
}
}
private suspend fun processNonUpiLitePayment(selectedBankAccountValue: LinkedAccountEntity) {
naviPaymentAnalytics.onNonUpiLitePayment(
isDiscountApplied = isDiscountApplied.value.toString(),
accountId = selectedBankAccountValue.accountId,
baseAttributes = getAnalyticsParams(),
)
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = selectedBankAccountValue.accountId,
isPaymentFromLiteAccount = false,
screenName = screenName,
)
naviPayPspManager.evaluateAndOnboardPspForFlow(
naviPayFlowType =
NaviPayFlowType.getSendMoneyFlowForAccountType(
selectedBankAccountValue.accountType
),
vpaEntityList = selectedBankAccountValue.vpaEntityList,
screenName = screenName,
onOnboardingTriggered = { updateBottomSheetUIState(showBottomSheet = false) },
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName = screenName)
if (pspEvaluationResult.isOnboardingTriggered) {
updateBottomSheetUIState(showBottomSheet = true)
}
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForPayment(onboardingEntity = onboardingEntity)
}
},
)
}
private suspend fun processUpiLitePayment(selectedBankAccountValue: LinkedAccountEntity) {
if (selectedBankAccountValue.activeLiteAccountPsp == null) {
showErrorState(PaymentErrorData())
CheckMateManager.logAppErrorEvent(
metricInfo = MetricInfo.PMSMetric<Any>(screen = screenName),
errorCode = resourceProvider.getString(R.string.no_active_lite_account_psp),
errorTitle = resourceProvider.getString(R.string.something_went_wrong),
errorDes = resourceProvider.getString(R.string.technical_issue),
)
return
}
val vpaEntityForLiteAccount =
selectedBankAccountValue.getVpaEntityByPsp(
selectedBankAccountValue.activeLiteAccountPsp ?: return
)
naviPaymentAnalytics.onUpiLitePayment(
isDiscountApplied = isDiscountApplied.value.toString(),
baseAttributes = getAnalyticsParams(),
)
naviPayPspManager.evaluateAndOnboardPspForVpa(
vpaEntity = vpaEntityForLiteAccount,
screenName = screenName,
onOnboardingTriggered = { updateBottomSheetUIState(showBottomSheet = false) },
naviPayFlowType = NaviPayFlowType.UPI_LITE,
onPspEvaluated = { pspEvaluationResult ->
if (pspEvaluationResult.isOnboardingTriggered) {
updateBottomSheetUIState(showBottomSheet = true)
}
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForPayment(onboardingEntity = onboardingEntity)
}
},
)
}
fun handleAccountAdditionFlow(addAccountButtonClicked: Boolean = false) {
viewModelScope.launch {
val enabledAccountTypes = getEnabledAccountTypes()
@@ -1437,10 +1203,6 @@ constructor(
private fun isCheckBalanceInProgress(): Boolean = checkBalanceAccountId.value.isNotEmpty()
private fun onPspEvaluatedForPayment(onboardingEntity: NaviPayCustomerOnboardingEntity) {
startPayAmount(onboardingEntity)
}
private suspend fun onPspEvaluatedForCheckBalance(
accountId: String,
linkedAccount: LinkedAccountEntity,
@@ -1579,4 +1341,77 @@ constructor(
bottomSheetStateChange = true,
)
}
private suspend fun onPinPageSuccess(
account: LinkedAccountEntity,
upiNavigationResponse: UpiNavigationResponse,
) {
bottomSheetPreSendMoneyState = bottomSheetStateHolder.value
val payload = (payNowResponse?.providerPayload) as? NaviPayProcessPayload
_effect.emit(
MPSScreenEffect.Navigation(
direction =
TransactionPollingScreenDestination(
token = upiNavigationResponse.paymentToken,
paymentAmount = paymentAmount.value,
redeemedCoinsValue =
if (isDiscountApplied.value)
if (rewardsInfoV2.isNull())
coinBurnDetails.value?.redeemableCoinsValue?.value.orZero()
else rewardsInfoV2.value?.burnDetails?.coinsValue?.value.orZero()
else 0.0,
source = paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = upiNavigationResponse.tstoreOrderReferenceId,
sourceDestination = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
pollingConfiguration =
payNowResponse?.pollingConfiguration ?: RequestConfig(),
request =
TransactionStatusRequest(
transactionReferenceId =
upiNavigationResponse.transactionReferenceId,
screenType = NaviPaymentScreenType.MINI_PAYMENT_SCREEN.name,
),
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
naviPayUpiUriKey = upiNavigationResponse.intentUri,
upiRequestId =
upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
selectedBankAccountId = account.accountId,
credBlock = upiNavigationResponse.pmsSendMoneyStatus.credBlock,
metaData = Gson().toJson(payload?.metadata),
txnTimeStamp = upiNavigationResponse.transactionTimestamp,
isArcProtected = account.isArcProtected.orFalse(),
onboardingDataEntity =
upiNavigationResponse.pmsSendMoneyStatus.onboardingDataEntity,
),
)
)
)
payNowResponse = null
}
private fun createDiscountInfo(): List<PayAmountRequest.DiscountInfo>? {
return if (isDiscountApplied.value) {
listOf(
PayAmountRequest.DiscountInfo(
type = PayAmountRequest.DiscountType.COIN.name,
amount =
if (rewardsInfoV2.value.isNull())
coinBurnDetails.value?.redeemableCoinsValue
else rewardsInfoV2.value?.burnDetails?.coinsValue,
discountMetadata =
PayAmountRequest.DiscountMetadata(
coinsUsed =
if (rewardsInfoV2.value.isNull())
coinBurnDetails.value?.redeemableCoins
else {
val coins = rewardsInfoV2.value?.burnDetails?.coins
val coinValue = coins?.replace(",", "")?.toIntOrNull().orZero()
coinValue
}
),
)
)
} else null
}
}

View File

@@ -23,9 +23,9 @@ import com.navi.base.utils.orElse
import com.navi.base.utils.orFalse
import com.navi.base.utils.orTrue
import com.navi.base.utils.orZero
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.upi.CANCEL
import com.navi.common.upi.NAVI_PAY_RESPONSE
import com.navi.common.upi.NaviPayAction
import com.navi.common.upi.TYPE
@@ -34,7 +34,6 @@ import com.navi.common.usecase.LitmusExperimentsUseCase
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.Constants.DEFAULT
import com.navi.common.utils.stringToJsonObject
import com.navi.common.utils.toJsonObject
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.common.setup.NaviPayManager
import com.navi.pay.common.usecase.AccountListCheckBalanceUseCase
@@ -44,7 +43,6 @@ import com.navi.pay.common.usecase.NaviPayConfigUseCase
import com.navi.pay.management.common.sendmoney.model.view.BankAccountsState
import com.navi.pay.management.common.utils.NaviPayPspManager
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboarding.binding.model.view.NaviPayCustomerOnboardingEntity
import com.navi.pay.utils.ALL_ENABLED_ACCOUNTS
import com.navi.pay.utils.LITE_MAX_SEND_MONEY
import com.navi.pay.utils.NAVI_PAY_PURPLE_CTA_LOADER_LOTTIE
@@ -73,7 +71,6 @@ import com.navi.payment.nativepayment.model.FomoBottomSheetCtaAction
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.NetBankingPaymentInstrument
import com.navi.payment.nativepayment.model.NetBankingPaymentInstumentDetails
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.PaymentsMainCtaState
import com.navi.payment.nativepayment.model.RewardsInfoV2
import com.navi.payment.nativepayment.model.S2sPaymentMethodResponse
@@ -96,9 +93,10 @@ 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.usecase.NaviUpiPaymentProcessor
import com.navi.payment.nativepayment.usecase.NetBankingUseCase
import com.navi.payment.nativepayment.usecase.PmsLinkedAccountUseCase
import com.navi.payment.nativepayment.usecase.PmsSendMoneyUseCase
import com.navi.payment.nativepayment.usecase.UpiNavigationResponse
import com.navi.payment.nativepayment.utils.NaviPaymentEventHandler
import com.navi.payment.nativepayment.utils.NaviPaymentRewardsEventBus
import com.navi.payment.nativepayment.utils.PaymentRewardsEvent
@@ -109,13 +107,11 @@ import com.navi.payment.nativepayment.utils.getLastSelectedAccountBeforeAccounts
import com.navi.payment.nativepayment.utils.getPayloadBasedOnType
import com.navi.payment.nativepayment.utils.mapToLinkedAccountEntity
import com.navi.payment.nativepayment.utils.rankUpiAccountsByEligibility
import com.navi.payment.nativepayment.utils.toGenericError
import com.navi.payment.nativepayment.utils.toGenericErrorResponse
import com.navi.payment.nativepayment.utils.toPaymentNaviPaymentErrorConfig
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.ExternalPayNowResponse
import com.navi.payment.paymentscreen.model.IntentUriPayNowResponse
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.paymentscreen.model.PMSErrorReason
import com.navi.payment.paymentscreen.model.PayNowResponse
@@ -125,7 +121,6 @@ import com.navi.payment.turbocheckout.model.PayNowRequest
import com.navi.payment.turbocheckout.model.SelectedMethodDetails
import com.navi.payment.turbocheckout.model.UpiCollectPayNowRequest
import com.navi.payment.turbocheckout.model.UpiIntentPayNowRequest
import com.navi.payment.turbocheckout.model.UpiPayNowRequest
import com.navi.payment.utils.Constants
import com.navi.payment.utils.Constants.COIN_DISCOUNT_APPLY_DELAY
import com.navi.payment.utils.Constants.SCREEN_TRANSITION_DURATION_IN_MILLIS
@@ -153,7 +148,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.joda.time.DateTime
import org.json.JSONObject
@HiltViewModel
@@ -170,12 +164,12 @@ constructor(
private val naviPayManager: NaviPayManager,
@PaymentsSdkRetrofit private val deserializer: Gson,
private val paymentEventHandler: NaviPaymentEventHandler,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
private val arcNudgeUseCase: ArcNudgeUseCase,
private val naviPaymentRewardsEventBus: NaviPaymentRewardsEventBus,
private val netBankingUseCase: NetBankingUseCase,
private val accountListCheckBalanceUseCase: AccountListCheckBalanceUseCase,
private val naviPayPspManager: NaviPayPspManager,
private val naviUpiPaymentProcessor: NaviUpiPaymentProcessor,
) :
NPSBaseViewModel(
paymentRepository = paymentRepository,
@@ -1020,10 +1014,7 @@ constructor(
submitPayNowRequest(payNowRequest)
}
private fun handlePayNowResponse(
response: RepoResult<PayNowResponse>,
onboardingEntity: NaviPayCustomerOnboardingEntity? = null,
) {
private fun handlePayNowResponse(response: RepoResult<PayNowResponse>) {
viewModelScope.launch {
if (response.isSuccessWithData()) {
paymentDataProvider.add(
@@ -1040,7 +1031,6 @@ constructor(
transactionReferenceId = it,
token = paymentSdkInitParams?.token.orEmpty(),
screenType = NaviPaymentScreenType.FULL_PAYMENT_SCREEN.name,
event = EMPTY,
status = TransactionStatus.INITIATED.name,
)
}
@@ -1060,9 +1050,6 @@ constructor(
)
}
}
is InternalPayNowResponse -> {
startNaviUpiPayment(responseData, onboardingEntity)
}
}
} else {
showLoader(false)
@@ -1282,7 +1269,6 @@ constructor(
private fun insertTransactionStatusRequest(
transactionReferenceId: String,
token: String,
event: String,
screenType: String,
status: String,
) {
@@ -1292,7 +1278,7 @@ constructor(
transactionReferenceId = transactionReferenceId,
token = token,
status = status,
event = event,
event = EMPTY,
screenType = screenType,
)
)
@@ -1445,79 +1431,6 @@ constructor(
}
}
private suspend fun startNaviUpiPayment(
payNowResponse: InternalPayNowResponse,
onboardingEntity: NaviPayCustomerOnboardingEntity? = null,
) {
updateBottomSheetUIState(false)
if (onboardingEntity == null) {
pspManagerAnalytics.onNaviPayOnboardingEntityNotInitialised(screenName)
return
}
val payload = (payNowResponse.providerPayload)
val txnTimeStamp = DateTime.now().toString()
val pmsSendMoneyStatus =
pmsSendMoneyUseCase.performChecksAndGenerateCredBlock(
naviPayUpiUriKey = payload?.naviPayUpiUriKey.orEmpty(),
screenName = NaviPaymentScreenType.FULL_PAYMENT_SCREEN.name,
linkedAccountEntity = npsScreenState.naviUpiPaymentState.selectedBankAccount,
baseAnalyticsParam = baseAnalyticsParams,
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = onboardingEntity,
)
when (pmsSendMoneyStatus) {
is PMSSendMoneyStatus.Error -> {
val errorConfig = (pmsSendMoneyStatus as? PMSSendMoneyStatus.Error)?.errorConfig
var transactionStatus = TransactionStatus.ERROR.name
if (errorConfig?.code == CANCEL) {
transactionStatus = TransactionStatus.USER_CANCELLED_TRANSACTION.name
} else {
handleError(
genericErrorResponse = errorConfig?.toGenericError(),
errorReason = PMSErrorReason.UPIError,
)
}
notifySdkExitWithError(transactionStatus, errorConfig.toJsonObject().toString())
}
is PMSSendMoneyStatus.Success -> {
if (
paymentSdkInitParams?.token.isNotNullAndNotEmpty() &&
payNowResponse.transactionReferenceId.isNotNullAndNotEmpty()
) {
updateTransactionStatusToPending(payNowResponse.transactionReferenceId)
_effect.emit(
NPSBaseContract.Effect.NavigateToLoaderScreen(
token = paymentSdkInitParams?.token.orEmpty(),
paymentAmount = npsScreenState.npsBaseState.paymentAmount,
source = paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = payload?.tstoreOrderReferenceId.orEmpty(),
payNowResponse = payNowResponse,
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
selectedBankAccountId =
npsScreenState.naviUpiPaymentState.selectedBankAccount
?.accountId
.orEmpty(),
credBlock = pmsSendMoneyStatus.credBlock.orEmpty(),
upiRequestId = pmsSendMoneyStatus.upiRequestId,
naviPayUpiUriKey = payload?.naviPayUpiUriKey,
metaData = Gson().toJson(payload?.metadata),
txnTimeStamp = txnTimeStamp,
isArcProtected =
npsScreenState.naviUpiPaymentState.selectedBankAccount
?.isArcProtected
.orFalse(),
onboardingDataEntity = onboardingEntity,
),
)
)
}
}
}
showLoader(false)
}
private fun handleArcNudgeClicked() {
viewModelScope.safeLaunch {
naviPaymentAnalytics.onArcNudgeClicked()
@@ -1629,18 +1542,6 @@ constructor(
npsScreenState.naviUpiPaymentState.showOnboardingCard
}
private fun createUpiRequest(selectedBankAccount: LinkedAccountEntity): UpiPayNowRequest {
return UpiPayNowRequest(
methodName = UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
SelectedMethodDetails(
payerBankAccountId = selectedBankAccount.accountId,
accountType = selectedBankAccount.accountType,
),
discountInfo = createDiscountInfo(),
)
}
private fun createDiscountInfo(): List<PayAmountRequest.DiscountInfo>? {
val discountState = npsScreenState.naviCoinState
@@ -1701,43 +1602,41 @@ constructor(
selectedBankAccount: LinkedAccountEntity
) {
when {
isPaymentFromLiteAccount() -> handleLiteAccountPayment(selectedBankAccount)
selectedBankAccount.isMPinSet.not() -> handleMPinSetup(selectedBankAccount)
else -> handleNonLiteAccountPayment(selectedBankAccount)
else -> makePayment(selectedBankAccount)
}
}
private suspend fun handleNonLiteAccountPayment(selectedBankAccount: LinkedAccountEntity) {
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = selectedBankAccount.accountId,
isPaymentFromLiteAccount = false,
screenName = screenName,
)
naviPayPspManager.evaluateAndOnboardPspForFlow(
naviPayFlowType =
NaviPayFlowType.getSendMoneyFlowForAccountType(selectedBankAccount.accountType),
vpaEntityList = selectedBankAccount.vpaEntityList,
screenName = screenName,
onOnboardingTriggered = {},
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
if (pspEvaluationResult.isOnboardingTriggered) {
showRedirectingToMakePaymentBottomSheet()
}
onPspEvaluatedForSendMoney(onboardingEntity, selectedBankAccount)
}
},
)
}
private suspend fun onPspEvaluatedForSendMoney(
onboardingEntity: NaviPayCustomerOnboardingEntity,
selectedBankAccount: LinkedAccountEntity,
) {
private suspend fun makePayment(account: LinkedAccountEntity) {
showLoader(true)
val upiPayNowRequest = createUpiRequest(selectedBankAccount)
submitPayNowRequest(upiPayNowRequest, onboardingEntity)
naviUpiPaymentProcessor.makePayment(
account = account,
isDiscountApplied = npsScreenState.naviCoinState.isDiscountApplied,
discountInfo = createDiscountInfo(),
analyticScreenName = screenName,
naviPaymentScreen = NaviPaymentScreenType.FULL_PAYMENT_SCREEN,
paymentToken = paymentSdkInitParams?.token.orEmpty(),
baseAttributes = baseAnalyticsParams,
onInitialChecksFailure = {
handleError(
genericErrorResponse = it,
errorReason = PMSErrorReason.NoActiveLiteAccountPSP,
)
},
onOnboardingTriggered = {},
onOnboardingCancelled = { showLoader(false) },
onOnboardingCompleted = { showRedirectingToMakePaymentBottomSheet() },
onBeforePayNowApiCall = {},
onPayNowSuccess = { payNowResponse = it },
onPayNowFailure = onPayNowFailure(),
onBeforeOpeningPinPage = { updateBottomSheetUIState(false) },
onPinPageBack = { showLoader(false) },
onPinPageError = {
showLoader(false)
handleError(genericErrorResponse = it, errorReason = PMSErrorReason.UPIError)
},
onPinPageSuccess = navigateToTransactionPollingScreen(account),
)
}
private suspend fun handleAccountAdditionFlow(hasClickedAddAccount: Boolean = false) {
@@ -1814,41 +1713,6 @@ constructor(
)
}
private suspend fun handleLiteAccountPayment(selectedBankAccount: LinkedAccountEntity) {
if (selectedBankAccount.activeLiteAccountPsp == null) {
notifyError()
return
}
val vpaEntityForLiteAccount =
selectedBankAccount.getVpaEntityByPsp(
selectedBankAccount.activeLiteAccountPsp ?: return
)
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = selectedBankAccount.accountId,
isPaymentFromLiteAccount = true,
screenName,
)
naviPayPspManager.evaluateAndOnboardPspForVpa(
vpaEntity = vpaEntityForLiteAccount,
screenName = screenName,
onOnboardingTriggered = {},
naviPayFlowType = NaviPayFlowType.UPI_LITE,
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
if (pspEvaluationResult.isOnboardingTriggered) {
return@evaluateAndOnboardPspForVpa // don't make upi lite payment automatically
// after onboarding
}
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForSendMoney(
onboardingEntity = onboardingEntity,
selectedBankAccount = selectedBankAccount,
)
}
},
)
}
private fun isPaymentFromLiteAccount(): Boolean {
return npsScreenState.naviUpiPaymentState.selectedBankAccount
?.accountId
@@ -1893,10 +1757,7 @@ constructor(
)
}
private suspend fun submitPayNowRequest(
payNowRequest: PayNowRequest,
onboardingEntity: NaviPayCustomerOnboardingEntity? = null,
) {
private suspend fun submitPayNowRequest(payNowRequest: PayNowRequest) {
val payNowResponse =
paymentRepository.postPayNow(
token = paymentSdkInitParams?.token,
@@ -1906,7 +1767,7 @@ constructor(
screenName = NaviPaymentAnalyticScreenName.FULL_PAYMENT_SCREEN.screenName
),
)
handlePayNowResponse(payNowResponse, onboardingEntity)
handlePayNowResponse(payNowResponse)
}
private fun isUpiCollectSelected() =
@@ -1946,4 +1807,42 @@ constructor(
?.contains(UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE)
.orFalse()
}
private fun onPayNowFailure(): (GenericErrorResponse) -> Unit = {
showLoader(false)
_state.update {
npsScreenState.copy(
netBankingDetails = npsScreenState.netBankingDetails.copy(selectedBank = null)
)
}
updateBottomSheetUIState(false)
handleError(it, errorReason = PMSErrorReason.PayNowError)
}
private fun navigateToTransactionPollingScreen(
account: LinkedAccountEntity
): suspend (UpiNavigationResponse) -> Unit = { upiNavigationResponse ->
val payload = upiNavigationResponse.internalPayNowResponse.providerPayload
_effect.emit(
NPSBaseContract.Effect.NavigateToLoaderScreen(
token = upiNavigationResponse.paymentToken,
paymentAmount = npsScreenState.npsBaseState.paymentAmount,
source = paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = upiNavigationResponse.tstoreOrderReferenceId,
payNowResponse = payNowResponse,
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
selectedBankAccountId = account.accountId,
credBlock = upiNavigationResponse.pmsSendMoneyStatus.credBlock,
upiRequestId = upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
naviPayUpiUriKey = upiNavigationResponse.intentUri,
metaData = Gson().toJson(payload?.metadata),
txnTimeStamp = upiNavigationResponse.transactionTimestamp,
isArcProtected = account.isArcProtected,
onboardingDataEntity =
upiNavigationResponse.pmsSendMoneyStatus.onboardingDataEntity,
),
)
)
}
}

View File

@@ -17,12 +17,9 @@ import com.navi.base.utils.EMPTY
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.model.RequestConfig
import com.navi.common.usecase.LitmusExperimentsUseCase
import com.navi.common.utils.TemporaryStorageHelper
import com.navi.common.utils.toJsonObject
import com.navi.pay.common.model.view.CheckBalanceState
import com.navi.pay.common.model.view.NaviPayErrorConfig
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.common.model.view.PspType
import com.navi.pay.common.setup.NaviPayManager
@@ -33,36 +30,29 @@ import com.navi.pay.common.usecase.NaviPayConfigUseCase
import com.navi.pay.management.common.sendmoney.util.PaymentRetryErrorBucket
import com.navi.pay.management.common.utils.NaviPayPspManager
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboarding.binding.model.view.NaviPayCustomerOnboardingEntity
import com.navi.pay.utils.isAccountIdOfTypeUpiLite
import com.navi.payment.model.common.PaymentSdkInitParams
import com.navi.payment.model.common.SignalPaymentData
import com.navi.payment.model.paymentmethod.Amount
import com.navi.payment.nativepayment.NaviPaymentAnalyticScreenName
import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.common.usecase.TransactionStatusUseCase
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.PAYMENT_ORDER_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.TRANSACTION_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PinAction
import com.navi.payment.nativepayment.dataprovider.getMpinResetAction
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.OneClickScreenBottomSheetStateHolder
import com.navi.payment.nativepayment.model.OneClickScreenBottomSheetUiState
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.S2sPaymentMethodResponse
import com.navi.payment.nativepayment.model.UpiLinkedAccountPaymentInstrument
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatus
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatusRequest
import com.navi.payment.nativepayment.presentation.reducer.OneClickCheckoutScreenEffect
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.usecase.PmsSendMoneyUseCase
import com.navi.payment.nativepayment.screens.destinations.TransactionPollingScreenDestination
import com.navi.payment.nativepayment.usecase.NaviUpiPaymentProcessor
import com.navi.payment.nativepayment.usecase.UpiNavigationResponse
import com.navi.payment.nativepayment.utils.NaviPaymentEventHandler
import com.navi.payment.nativepayment.utils.PaymentCancelSource
import com.navi.payment.nativepayment.utils.toGenericErrorResponse
import com.navi.payment.nativepayment.utils.toPaymentNaviPaymentErrorConfig
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.paymentscreen.model.PMSErrorReason
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.turbocheckout.model.PayAmountRequest
@@ -77,13 +67,15 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.joda.time.DateTime
import org.json.JSONObject
@HiltViewModel
@@ -96,12 +88,11 @@ constructor(
private val paymentDataProvider: PaymentDataProvider,
private val paymentRepository: PaymentRepository,
private val paymentEventHandler: NaviPaymentEventHandler,
private val transactionStatusUseCase: TransactionStatusUseCase,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
private val arcNudgeUseCase: ArcNudgeUseCase,
private val naviPayManager: NaviPayManager,
private val naviPayPspManager: NaviPayPspManager,
private val accountListCheckBalanceUseCase: AccountListCheckBalanceUseCase,
private val naviUpiPaymentProcessor: NaviUpiPaymentProcessor,
) :
NaviPaymentBaseVM(
screenName = NaviPaymentAnalyticScreenName.ONE_CLICK_CHECKOUT_SCREEN.screenName,
@@ -119,9 +110,6 @@ constructor(
var paymentSdkInitParams: PaymentSdkInitParams? = null
var isAmountReadOnly: Boolean = true
private val _pmsSendMoneyStatus = MutableStateFlow<PMSSendMoneyStatus?>(null)
val pmsSendMoneyStatus = _pmsSendMoneyStatus.asStateFlow()
private val _bottomSheetStateHolder =
MutableStateFlow(
OneClickScreenBottomSheetStateHolder(
@@ -145,6 +133,10 @@ constructor(
private val pspManagerAnalytics = NaviPaymentAnalytics.INSTANCE.PspManager()
private val sendMoneyErrorAnalytics = NaviPaymentAnalytics.INSTANCE.SendMoneyErrorBottomSheet()
private val _effect = MutableSharedFlow<OneClickCheckoutScreenEffect>()
val effect: SharedFlow<OneClickCheckoutScreenEffect>
get() = _effect.asSharedFlow()
init {
viewModelScope.safeLaunch(Dispatchers.IO) { sendMoneyErrorListener() }
}
@@ -199,7 +191,7 @@ constructor(
}
}
fun startPayment(bundle: Bundle? = Bundle()) {
fun startPaymentProcess(bundle: Bundle? = Bundle()) {
viewModelScope.safeLaunch(Dispatchers.IO) {
if (selectedAccountId.isNotNullAndNotEmpty()) return@safeLaunch
bundle?.let {
@@ -221,185 +213,68 @@ constructor(
paymentDataProvider.paymentMethodResponse.collectLatest { it ->
(it as? S2sPaymentMethodResponse)?.let {
amount = it.methodDetails?.amount?.value.orZero()
callPayNowApiAndStartPayment()
getSelectedAccountAndStartPayment()
}
}
}
private suspend fun callPayNowApiAndStartPayment() {
private suspend fun getSelectedAccountAndStartPayment() {
val accounts = linkedAccountUseCase.execute(includeAllDetails = true).first()
linkedAccountEntity = accounts.firstOrNull { it.accountId == selectedAccountId }
linkedAccountEntity?.let {
if (paymentSdkInitParams?.isArcProtected.orFalse()) {
arcNudgeUseCase.updateAccountArcStatusForLinkedAccount(it)
}
accountListCheckBalanceUseCase.updateCheckBalanceStateForLinkedAccount(
linkedAccountEntity = it,
isAccountEligible = it.eligibilityState.isAccountEligible,
)
_checkBalanceStateMap.update { accountListCheckBalanceUseCase.checkBalanceStateMap }
}
val response = fetchPayNowResponse()
if (response.isSuccessWithData()) {
paymentDataProvider.add(
TRANSACTION_REFERENCE_ID,
response.data?.transactionReferenceId.orEmpty(),
)
paymentDataProvider.add(
PAYMENT_ORDER_REFERENCE_ID,
response.data?.paymentOrderReferenceId.orEmpty(),
)
response.data?.transactionReferenceId?.let { transactionReferenceId ->
insertTransactionStatusRequest(transactionReferenceId = transactionReferenceId)
}
payNowResponse = response.data
(response.data as? InternalPayNowResponse)?.let {
startNaviUpiPayment(payNowResponse = it)
}
} else {
onBottomSheetCloseClicked()
handleError(response = response, errorReason = PMSErrorReason.PayNowError)
}
linkedAccountEntity?.let { makePayment(it) }
}
private suspend fun fetchPayNowResponse() =
paymentRepository.postPayNow(
token = paymentSdkInitParams?.token,
data =
PayAmountRequest(
methodName = UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
PayAmountRequest.SelectedMethodDetails(
payerBankAccountId = linkedAccountEntity?.accountId,
accountType = linkedAccountEntity?.accountType,
),
discountInfo =
if (coinsValue > 0) {
listOf(
PayAmountRequest.DiscountInfo(
type = PayAmountRequest.DiscountType.COIN.name,
amount = coinsAmount,
discountMetadata =
PayAmountRequest.DiscountMetadata(coinsUsed = coinsValue),
)
)
} else null,
),
metricInfo =
getPMSMetricInfo(
screenName = NaviPaymentAnalyticScreenName.ONE_CLICK_CHECKOUT_SCREEN.screenName
),
)
private suspend fun startNaviUpiPayment(payNowResponse: InternalPayNowResponse) {
naviPaymentAnalytics.onNavigatingToPinPage(
selectedBankAccountId = selectedAccountId,
oneClickPayToPinTime =
(System.currentTimeMillis() - TemporaryStorageHelper.payNowClickTime).toString(),
baseAttributes = paymentDataProvider.getAnalyticsParams(),
)
val selectedBankAccount = linkedAccountEntity ?: return
val payload = payNowResponse.providerPayload
txnTimeStamp = DateTime.now().toString()
when {
isPaymentFromLiteAccount() -> handleLiteAccountPayment(selectedBankAccount, payload)
else -> handleNonLiteAccountPayment(selectedBankAccount, payload)
}
}
private suspend fun handleNonLiteAccountPayment(
selectedBankAccount: LinkedAccountEntity,
payload: NaviPayProcessPayload?,
) {
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = selectedBankAccount.accountId,
isPaymentFromLiteAccount = false,
screenName = screenName,
)
naviPayPspManager.evaluateAndOnboardPspForFlow(
naviPayFlowType =
NaviPayFlowType.getSendMoneyFlowForAccountType(selectedBankAccount.accountType),
vpaEntityList = selectedBankAccount.vpaEntityList,
screenName = screenName,
private suspend fun makePayment(account: LinkedAccountEntity) {
naviUpiPaymentProcessor.makePayment(
account = account,
isDiscountApplied = coinsValue > 0,
discountInfo = getDiscountInfo(),
analyticScreenName = NaviPaymentAnalyticScreenName.ONE_CLICK_CHECKOUT_SCREEN.screenName,
naviPaymentScreen = NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN,
paymentToken = paymentSdkInitParams?.token.orEmpty(),
baseAttributes = getAnalyticsParams(),
onInitialChecksFailure = {
handleError(
genericErrorResponse = it,
errorReason = PMSErrorReason.NoActiveLiteAccountPSP,
)
},
onOnboardingTriggered = {},
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForSendMoney(onboardingEntity, payload, selectedBankAccount)
}
onOnboardingCancelled = {},
onOnboardingCompleted = {},
onBeforePayNowApiCall = { onBeforePayNowApiCall(account) },
onPayNowSuccess = { payNowResponse = it },
onPayNowFailure = {
onBottomSheetCloseClicked()
handleError(it, errorReason = PMSErrorReason.PayNowError)
},
onBeforeOpeningPinPage = {},
onPinPageBack = {
postSdkExitSignal()
finish()
payNowResponse = null
},
onPinPageError = {
handleError(it)
payNowResponse = null // check if needed
},
onPinPageSuccess = {
navigateToTransactionPollingScreen(it, account = account)
payNowResponse = null
},
)
}
private suspend fun handleLiteAccountPayment(
selectedBankAccount: LinkedAccountEntity,
payload: NaviPayProcessPayload?,
) {
if (selectedBankAccount.activeLiteAccountPsp == null) {
notifyError()
return
}
val vpaEntityForLiteAccount =
selectedBankAccount.getVpaEntityByPsp(
selectedBankAccount.activeLiteAccountPsp ?: return
)
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = selectedBankAccount.accountId,
isPaymentFromLiteAccount = true,
screenName,
)
naviPayPspManager.evaluateAndOnboardPspForVpa(
vpaEntity = vpaEntityForLiteAccount,
screenName = screenName,
onOnboardingTriggered = {},
naviPayFlowType = NaviPayFlowType.UPI_LITE,
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForSendMoney(
onboardingEntity = onboardingEntity,
payload = payload,
selectedBankAccount = selectedBankAccount,
)
}
},
)
}
private suspend fun onPspEvaluatedForSendMoney(
onboardingEntity: NaviPayCustomerOnboardingEntity,
payload: NaviPayProcessPayload?,
selectedBankAccount: LinkedAccountEntity,
) {
_pmsSendMoneyStatus.update {
pmsSendMoneyUseCase.performChecksAndGenerateCredBlock(
naviPayUpiUriKey = payload?.naviPayUpiUriKey.orEmpty(),
linkedAccountEntity = selectedBankAccount,
screenName = screenName,
baseAnalyticsParam = paymentDataProvider.getAnalyticsParams(),
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = onboardingEntity,
)
}
}
private fun isPaymentFromLiteAccount(): Boolean {
return linkedAccountEntity?.accountId?.isAccountIdOfTypeUpiLite().orFalse()
}
private fun insertTransactionStatusRequest(transactionReferenceId: String) {
coroutineScope.safeLaunch(Dispatchers.IO) {
transactionStatusUseCase.insertTransactionStatusRequest(
TransactionStatusRequestEntity(
transactionReferenceId = transactionReferenceId,
token = paymentSdkInitParams?.token,
screenType = NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN.name,
event = EMPTY,
status = TransactionStatus.INITIATED.name,
private fun getDiscountInfo() =
if (coinsValue > 0) {
listOf(
PayAmountRequest.DiscountInfo(
type = PayAmountRequest.DiscountType.COIN.name,
amount = coinsAmount,
discountMetadata = PayAmountRequest.DiscountMetadata(coinsUsed = coinsValue),
)
)
}
}
} else null
private suspend fun updateSDKInitParams() {
paymentSdkInitParams = paymentDataProvider.getSdkInitParams()
@@ -430,47 +305,11 @@ constructor(
baseAttributes = getAnalyticsParams(),
)
_isBottomSheetRetryCtaLoaderEnabled.update { true }
callPayNowApiAndStartPayment()
getSelectedAccountAndStartPayment()
onBottomSheetCloseClicked()
}
}
fun notifyNaviUpiExitWithError(status: String, error: NaviPayErrorConfig?) {
coroutineScope.safeLaunch(Dispatchers.IO) {
paymentSdkInitParams?.token?.let {
transactionStatusUseCase.updateAndPostTransactionStatus(
token = it,
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId =
paymentDataProvider.get(TRANSACTION_REFERENCE_ID).toString(),
event = error.toJsonObject().toString(),
screenType = NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN.name,
status = status,
),
)
}
}
}
fun updateTransactionStatusToPending(transactionReferenceId: String?) {
coroutineScope.safeLaunch(Dispatchers.IO) {
transactionStatusUseCase.updateTransactionStatusRequest(
transactionStatusRequest =
TransactionStatusRequest(
transactionReferenceId = transactionReferenceId,
status = TransactionStatus.PENDING.name,
event = EMPTY,
)
)
}
}
fun onNaviPayCallBack() {
payNowResponse = null
_pmsSendMoneyStatus.update { null }
}
fun postSdkExitSignal() {
coroutineScope.safeLaunch(Dispatchers.IO) {
paymentSdkInitParams?.token?.let {
@@ -601,4 +440,76 @@ constructor(
)
}
}
private fun finish() {
viewModelScope.safeLaunch {
_effect.emit(
OneClickCheckoutScreenEffect.SetResultAndFinish(
cancelSource = PaymentCancelSource.SYSTEM_BACK_CLICKED,
shouldPromptAmountEdit = false,
)
)
}
}
private fun onBeforePayNowApiCall(account: LinkedAccountEntity) {
viewModelScope.safeLaunch(Dispatchers.IO) {
if (paymentSdkInitParams?.isArcProtected.orFalse()) {
arcNudgeUseCase.updateAccountArcStatusForLinkedAccount(account)
}
accountListCheckBalanceUseCase.updateCheckBalanceStateForLinkedAccount(
linkedAccountEntity = account,
isAccountEligible = account.eligibilityState.isAccountEligible,
)
_checkBalanceStateMap.update { accountListCheckBalanceUseCase.checkBalanceStateMap }
}
}
private suspend fun navigateToTransactionPollingScreen(
upiNavigationResponse: UpiNavigationResponse,
account: LinkedAccountEntity,
) {
if (
upiNavigationResponse.paymentToken.isNotEmpty() &&
upiNavigationResponse.transactionReferenceId.isNotEmpty()
) {
_effect.emit(
OneClickCheckoutScreenEffect.Navigation(
direction =
TransactionPollingScreenDestination(
token = upiNavigationResponse.paymentToken,
paymentAmount = amount,
redeemedCoinsValue = coinsAmount?.value.orZero(),
source = paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = upiNavigationResponse.tstoreOrderReferenceId,
sourceDestination =
NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN.name,
pollingConfiguration =
payNowResponse?.pollingConfiguration ?: RequestConfig(),
request =
TransactionStatusRequest(
transactionReferenceId =
upiNavigationResponse.transactionReferenceId,
screenType =
NaviPaymentScreenType.ONE_CLICK_CHECKOUT_SCREEN.name,
),
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
naviPayUpiUriKey = upiNavigationResponse.intentUri,
upiRequestId =
upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
selectedBankAccountId = account.accountId,
credBlock = upiNavigationResponse.pmsSendMoneyStatus.credBlock,
metaData = upiNavigationResponse.metaData,
txnTimeStamp = upiNavigationResponse.transactionTimestamp,
isArcProtected = account.isArcProtected.orFalse(),
onboardingDataEntity =
upiNavigationResponse.pmsSendMoneyStatus
.onboardingDataEntity,
),
)
)
)
}
}
}

View File

@@ -14,16 +14,13 @@ import androidx.activity.result.ActivityResult
import com.google.gson.Gson
import com.navi.base.utils.EMPTY
import com.navi.base.utils.ResourceProvider
import com.navi.base.utils.isNotNullAndNotEmpty
import com.navi.base.utils.orFalse
import com.navi.base.utils.orZero
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.uitron.model.action.UpiIntent
import com.navi.common.upi.CANCEL
import com.navi.common.usecase.LitmusExperimentsUseCase
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.toJsonObject
import com.navi.pay.common.model.view.NaviPayFlowType
import com.navi.pay.common.setup.NaviPayManager
import com.navi.pay.common.usecase.NaviPayConfigUseCase
@@ -40,7 +37,6 @@ import com.navi.payment.nativepayment.dataprovider.getMpinSetAction
import com.navi.payment.nativepayment.db.model.TransactionStatusRequestEntity
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.PaymentsMainCtaState
import com.navi.payment.nativepayment.model.S2sPaymentMethodResponse
import com.navi.payment.nativepayment.model.UpiCollectPaymentInstrument
@@ -53,15 +49,13 @@ import com.navi.payment.nativepayment.presentation.reducer.NPSBaseState
import com.navi.payment.nativepayment.presentation.reducer.UpiCollectOptionState
import com.navi.payment.nativepayment.presentation.reducer.UpiIntentScreenContract
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.usecase.NaviUpiPaymentProcessor
import com.navi.payment.nativepayment.usecase.PmsLinkedAccountUseCase
import com.navi.payment.nativepayment.usecase.PmsSendMoneyUseCase
import com.navi.payment.nativepayment.usecase.UpiNavigationResponse
import com.navi.payment.nativepayment.utils.mapToLinkedAccountEntity
import com.navi.payment.nativepayment.utils.toGenericError
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.ExternalPayNowResponse
import com.navi.payment.paymentscreen.model.IntentUriPayNowResponse
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviPayProcessPayload
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.paymentscreen.model.PMSErrorReason
import com.navi.payment.paymentscreen.model.PayNowResponse
@@ -69,7 +63,6 @@ import com.navi.payment.turbocheckout.model.PayNowRequest
import com.navi.payment.turbocheckout.model.SelectedMethodDetails
import com.navi.payment.turbocheckout.model.UpiCollectPayNowRequest
import com.navi.payment.turbocheckout.model.UpiIntentPayNowRequest
import com.navi.payment.turbocheckout.model.UpiPayNowRequest
import com.navi.payment.utils.getPMSMetricInfo
import com.navi.payment.utils.roundTo
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -83,7 +76,6 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import org.joda.time.DateTime
import org.json.JSONObject
@HiltViewModel
@@ -94,13 +86,13 @@ constructor(
@PaymentsSdkRetrofit private val deserializer: Gson,
paymentDataProvider: PaymentDataProvider,
pmsLinkedAccountUseCase: PmsLinkedAccountUseCase,
transactionStatusUseCase: TransactionStatusUseCase,
transactionStatusUseCase: TransactionStatusUseCase, // TODO Remove?
paymentRepository: PaymentRepository,
resourceProvider: ResourceProvider,
litmusExperimentsUseCase: LitmusExperimentsUseCase,
naviPayConfigUseCase: NaviPayConfigUseCase,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
private val naviPayPspManager: NaviPayPspManager,
private val naviUpiPaymentProcessor: NaviUpiPaymentProcessor,
) :
NPSBaseViewModel(
paymentRepository = paymentRepository,
@@ -357,53 +349,50 @@ constructor(
private suspend fun handleNaviUpiFlow(vpaAccount: LinkedAccountEntity) {
when {
vpaAccount.isMPinSet.not() -> handleMPinSetup(vpaAccount)
else -> handleNaviUpiPayment(vpaAccount)
else -> makePayment(vpaAccount)
}
}
private suspend fun handleNaviUpiPayment(vpaAccount: LinkedAccountEntity) {
pspManagerAnalytics.onSendMoneyWithPspManager(
accountId = vpaAccount.accountId,
isPaymentFromLiteAccount = false,
screenName = screenName,
)
naviPayPspManager.evaluateAndOnboardPspForFlow(
naviPayFlowType =
NaviPayFlowType.getSendMoneyFlowForAccountType(vpaAccount.accountType),
vpaEntityList = vpaAccount.vpaEntityList,
screenName = screenName,
onOnboardingTriggered = {},
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(
pspEvaluationResult = pspEvaluationResult,
screenName = screenName,
private suspend fun makePayment(vpaAccount: LinkedAccountEntity) {
showLoader(true)
naviUpiPaymentProcessor.makePayment(
account = vpaAccount,
isDiscountApplied = false,
discountInfo = null,
analyticScreenName = screenName,
naviPaymentScreen = NaviPaymentScreenType.UPI_INTENT,
paymentToken = paymentSdkInitParams?.token.orEmpty(),
baseAttributes = getAnalyticsParams(),
onInitialChecksFailure = {
handleError(
genericErrorResponse = it,
errorReason = PMSErrorReason.NoActiveLiteAccountPSP,
)
},
onOnboardingTriggered = {},
onOnboardingCancelled = { showLoader(false) },
onOnboardingCompleted = {},
onBeforePayNowApiCall = {},
onPayNowSuccess = { payNowResponse = it },
onPayNowFailure = { error ->
showLoader(false)
handleError(genericErrorResponse = error, errorReason = PMSErrorReason.UPIError)
},
onBeforeOpeningPinPage = {},
onPinPageBack = { showLoader(false) },
onPinPageError = { error ->
showLoader(false)
handleError(genericErrorResponse = error, errorReason = PMSErrorReason.UPIError)
},
onPinPageSuccess = {
navigateToTransactionPollingScreen(
upiNavigationResponse = it,
accountId = vpaAccount.accountId,
)
pspEvaluationResult.onboardingDataEntity?.let { onboardingEntity ->
onPspEvaluatedForSendMoney(onboardingEntity, vpaAccount)
}
},
)
}
private suspend fun onPspEvaluatedForSendMoney(
onboardingEntity: NaviPayCustomerOnboardingEntity,
vpaAccount: LinkedAccountEntity,
) {
val upiPayNowRequest = createUpiPayNowRequest(vpaAccount)
callPayNowApiAndHandleResponse(upiPayNowRequest, onboardingEntity)
}
private fun createUpiPayNowRequest(vpaAccount: LinkedAccountEntity) =
UpiPayNowRequest(
methodName = UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
selectedMethodDetails =
SelectedMethodDetails(
payerBankAccountId = vpaAccount.accountId,
accountType = vpaAccount.accountType,
),
)
private fun createCollectPayNowRequest(vpa: String): UpiCollectPayNowRequest {
return UpiCollectPayNowRequest(
selectedMethodDetails = SelectedMethodDetails(customerVpa = vpa)
@@ -460,12 +449,6 @@ constructor(
)
}
}
is InternalPayNowResponse -> {
startNaviUpiPayment(
payNowResponse = payNowResponseData,
onboardingDataEntity = onboardingDataEntity,
)
}
}
} else {
showLoader(false)
@@ -526,92 +509,27 @@ constructor(
return upiScreenState.connectedAccountVpaMap?.get(key = vpa)
}
private suspend fun startNaviUpiPayment(
payNowResponse: InternalPayNowResponse,
onboardingDataEntity: NaviPayCustomerOnboardingEntity? = null,
) {
if (onboardingDataEntity == null) {
pspManagerAnalytics.onNaviPayOnboardingEntityNotInitialised(screenName = screenName)
return
}
val payload = payNowResponse.providerPayload
val txnTimeStamp = DateTime.now().toString()
val pmsSendMoneyStatus =
pmsSendMoneyUseCase.performChecksAndGenerateCredBlock(
naviPayUpiUriKey = payload?.naviPayUpiUriKey.orEmpty(),
screenName = NaviPaymentScreenType.UPI_INTENT.name,
linkedAccountEntity =
getLinkedAccountEntityFromVpaIfAvailable(
vpa =
upiScreenState.npsBaseState.upiCollectOptionState.upiCollectVpa.text
.trimEnd()
),
baseAnalyticsParam = baseAnalyticsParams,
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = onboardingDataEntity,
)
when (pmsSendMoneyStatus) {
is PMSSendMoneyStatus.Error -> {
val errorConfig = pmsSendMoneyStatus.errorConfig
var transactionStatus = TransactionStatus.ERROR.name
if (errorConfig.code == CANCEL) {
transactionStatus = TransactionStatus.USER_CANCELLED_TRANSACTION.name
} else {
handleError(
genericErrorResponse = errorConfig.toGenericError(),
errorReason = PMSErrorReason.UPIError,
)
}
notifySdkExitWithError(transactionStatus, errorConfig.toJsonObject().toString())
}
is PMSSendMoneyStatus.Success -> {
if (
paymentSdkInitParams?.token.isNotNullAndNotEmpty() &&
payNowResponse.transactionReferenceId.isNotNullAndNotEmpty()
) {
updateTransactionStatusToPending(payNowResponse.transactionReferenceId)
navigateToTransactionLoaderScreen(
payload,
payNowResponse,
pmsSendMoneyStatus,
txnTimeStamp,
)
}
}
}
showLoader(false)
}
private suspend fun navigateToTransactionLoaderScreen(
payload: NaviPayProcessPayload?,
payNowResponse: InternalPayNowResponse,
pmsSendMoneyStatus: PMSSendMoneyStatus.Success,
txnTimeStamp: String,
private suspend fun navigateToTransactionPollingScreen(
accountId: String,
upiNavigationResponse: UpiNavigationResponse,
) {
_effect.emit(
NPSBaseContract.Effect.NavigateToLoaderScreen(
token = paymentSdkInitParams?.token.orEmpty(),
token = upiNavigationResponse.paymentToken,
paymentAmount = state.value.npsBaseState.paymentAmount,
source = paymentSdkInitParams?.paymentSource.orEmpty(),
tstoreOrderReferenceId = payload?.tstoreOrderReferenceId.orEmpty(),
payNowResponse = payNowResponse,
tstoreOrderReferenceId = upiNavigationResponse.tstoreOrderReferenceId,
payNowResponse = upiNavigationResponse.internalPayNowResponse,
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
selectedBankAccountId =
getLinkedAccountEntityFromVpaIfAvailable(
vpa =
upiScreenState.npsBaseState.upiCollectOptionState
.upiCollectVpa
.text
)
?.accountId
.orEmpty(),
credBlock = pmsSendMoneyStatus.credBlock.orEmpty(),
upiRequestId = pmsSendMoneyStatus.upiRequestId,
naviPayUpiUriKey = payload?.naviPayUpiUriKey,
metaData = Gson().toJson(payload?.metadata),
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = pmsSendMoneyStatus.onboardingDataEntity,
selectedBankAccountId = accountId,
credBlock = upiNavigationResponse.pmsSendMoneyStatus.credBlock,
upiRequestId = upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
naviPayUpiUriKey = upiNavigationResponse.intentUri,
metaData = upiNavigationResponse.metaData,
txnTimeStamp = upiNavigationResponse.transactionTimestamp,
onboardingDataEntity =
upiNavigationResponse.pmsSendMoneyStatus.onboardingDataEntity,
),
)
)

View File

@@ -29,6 +29,7 @@ import com.navi.common.checkmate.model.MetricInfo
import com.navi.common.network.models.GenericErrorResponse
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.uitron.model.action.UpiIntent
import com.navi.common.upi.UPI_REQUEST_ID
import com.navi.common.upi.UpiDataType
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.toJsonObject
@@ -51,7 +52,6 @@ import com.navi.payment.model.initiatesdk.PaymentPrefetchMethodRequest
import com.navi.payment.nativepayment.NaviPaymentAnalyticScreenName
import com.navi.payment.nativepayment.NaviPaymentAnalytics
import com.navi.payment.nativepayment.activity.WebPaymentActivity
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.PAYMENT_ORDER_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.TRANSACTION_REFERENCE_ID
import com.navi.payment.nativepayment.dataprovider.PaymentDataProvider.Companion.UPI_LITE_MAX_PAYABLE_AMOUNT_PER_TRANSACTION
import com.navi.payment.nativepayment.dataprovider.WebPaymentDataProvider
@@ -60,6 +60,7 @@ import com.navi.payment.nativepayment.dataprovider.WebPaymentDataProvider.Compan
import com.navi.payment.nativepayment.dataprovider.getMpinSetAction
import com.navi.payment.nativepayment.model.BasePaymentInstrument
import com.navi.payment.nativepayment.model.CoinRewards
import com.navi.payment.nativepayment.model.NaviPaymentScreenType
import com.navi.payment.nativepayment.model.PMSLinkedAccountEntity
import com.navi.payment.nativepayment.model.PMSSendMoneyStatus
import com.navi.payment.nativepayment.model.PaymentsMainCtaState
@@ -71,24 +72,27 @@ import com.navi.payment.nativepayment.model.WebPaymentData
import com.navi.payment.nativepayment.model.WebPaymentScreenEffect
import com.navi.payment.nativepayment.model.WebPaymentScreenState
import com.navi.payment.nativepayment.model.WebPaymentSource
import com.navi.payment.nativepayment.model.transactionStatusRequest.TransactionStatus
import com.navi.payment.nativepayment.repository.PaymentRepository
import com.navi.payment.nativepayment.usecase.NaviUpiPaymentProcessor
import com.navi.payment.nativepayment.usecase.PmsSendMoneyUseCase
import com.navi.payment.nativepayment.usecase.UpiNavigationResponse
import com.navi.payment.nativepayment.utils.getEligibilityStateForUpiLiteAccount
import com.navi.payment.nativepayment.utils.getFirstEligibleAccount
import com.navi.payment.nativepayment.utils.getPayloadBasedOnType
import com.navi.payment.nativepayment.utils.mapToLinkedAccountEntity
import com.navi.payment.nativepayment.utils.rankUpiAccountsByEligibility
import com.navi.payment.nativepayment.utils.toGenericError
import com.navi.payment.nativepayment.utils.toGenericErrorResponse
import com.navi.payment.nativepayment.utils.updateAccountEligibilityStatusForNonUpiLiteAccounts
import com.navi.payment.network.util.PaymentsSdkRetrofit
import com.navi.payment.paymentscreen.model.InternalPayNowResponse
import com.navi.payment.paymentscreen.model.NaviUpiTransactionInfo
import com.navi.payment.paymentscreen.model.PMSErrorReason
import com.navi.payment.paymentscreen.model.PayNowResponse
import com.navi.payment.paymentscreen.model.PaymentErrorData
import com.navi.payment.turbocheckout.model.PayAmountRequest
import com.navi.payment.utils.Constants
import com.navi.payment.utils.Constants.EMPTY_TOKEN_ERROR
import com.navi.payment.utils.Constants.PAYMENT_ORDER_REFERNCE_ID
import com.navi.payment.utils.Constants.PSP_MANAGER_RESPONSE
import com.navi.payment.utils.getErrorData
import com.navi.payment.utils.getPMSMetricInfo
@@ -120,8 +124,9 @@ constructor(
private val naviPayManager: NaviPayManager,
private val linkedAccountsUseCase: LinkedAccountsUseCase,
private val pmsSendMoneyUseCase: PmsSendMoneyUseCase,
@PaymentsSdkRetrofit private val deserializer: Gson,
private val naviUpiPaymentProcessor: NaviUpiPaymentProcessor,
private val naviPayPspManager: NaviPayPspManager,
@PaymentsSdkRetrofit private val deserializer: Gson,
) : NaviPaymentBaseVM(screenName = NaviPaymentAnalyticScreenName.WEB_PAYMENT_SCREEN.screenName) {
private val webPaymentData = savedStateHandle.get<WebPaymentData>("webPaymentData")
@@ -485,7 +490,7 @@ constructor(
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
if (pspEvaluationResult.isOnboardingTriggered) {
if (webPayAction == WebPaymentAction.WEB_VIEW_SEND_MONEY) finishWithError()
_showPayNowLoader.update { false }
stopPayNowLoader()
return@evaluateAndOnboardPspForVpa // don't make upi lite payment automatically
// after onboarding for web view payments.
}
@@ -525,7 +530,7 @@ constructor(
onPspEvaluated = { pspEvaluationResult ->
pspManagerAnalytics.onPspManagerResult(pspEvaluationResult, screenName)
if (pspEvaluationResult.onboardingDataEntity == null) {
_showPayNowLoader.update { false }
stopPayNowLoader()
if (webPayAction == WebPaymentAction.WEB_VIEW_SEND_MONEY) finishWithError()
return@evaluateAndOnboardPspForFlow
}
@@ -559,7 +564,7 @@ constructor(
txnTimeStamp = txnTimeStamp,
onboardingDataEntity = onboardingEntity,
)
_showPayNowLoader.update { false }
stopPayNowLoader()
handlePinPageResult(
sendMoneyStatus = sendMoneyStatus,
naviPayUpiUriKey = naviPayUpiUriKey,
@@ -590,7 +595,7 @@ constructor(
NaviUpiTransactionInfo(
naviPayUpiUriKey = naviPayUpiUriKey,
txnTimeStamp = txnTimeStamp,
credBlock = sendMoneyStatus.credBlock!!,
credBlock = sendMoneyStatus.credBlock,
upiRequestId = sendMoneyStatus.upiRequestId,
selectedBankAccountId = linkedAccountEntity.accountId,
metaData = metaData,
@@ -633,69 +638,11 @@ constructor(
_paymentAmount.update { amount }
}
fun startPayAmount(accountId: String, linkedAccountType: String) =
viewModelScope.launch {
if (showPayNowLoader.value) return@launch
_showPayNowLoader.update { true }
val response = fetchPayNowResponse(accountId, linkedAccountType)
if (response.isSuccessWithData()) {
paymentDataProvider.add(
TRANSACTION_REFERENCE_ID,
response.data?.transactionReferenceId.orEmpty(),
)
paymentDataProvider.add(
PAYMENT_ORDER_REFERENCE_ID,
response.data?.paymentOrderReferenceId.orEmpty(),
)
payNowResponse = response.data
(response.data as? InternalPayNowResponse)?.let {
val payLoad = it.providerPayload
startNaviUpiPayment(
naviPayUpiUriKey = payLoad?.naviPayUpiUriKey,
tstoreOrderId = payLoad?.tstoreOrderReferenceId.orEmpty(),
metaData = Gson().toJson(payLoad?.metadata),
bankAccountId = selectedBankAccount.value?.accountId.orEmpty(),
)
}
} else {
_showPayNowLoader.update { false }
updateBottomSheetUIState(
true,
WebPaymentScreenState.Error(
PaymentErrorData(error = response.toGenericErrorResponse())
),
)
}
}
private suspend fun fetchPayNowResponse(accountId: String, linkedAccountType: String) =
paymentRepository.postPayNow(
token = token,
PayAmountRequest(
UpiLinkedAccountPaymentInstrument.PAYMENT_INSTRUMENT_TYPE,
PayAmountRequest.SelectedMethodDetails(
payerBankAccountId = accountId,
accountType = linkedAccountType,
),
),
getPMSMetricInfo(
screenName = NaviPaymentAnalyticScreenName.WEB_PAYMENT_SCREEN.screenName
),
)
fun onBankAccountSelected(linkedAccountEntity: LinkedAccountEntity) {
_selectedBankAccount.update { linkedAccountEntity }
updateMainCtaState()
}
fun finishWithResult(finishCallback: () -> Unit) {
updateBottomSheetUIState(showBottomSheet = false)
viewModelScope.launch(Dispatchers.Main) {
delay(150)
finishCallback.invoke()
}
}
fun startAction(
activity: Activity,
launcher: ActivityResultLauncher<Intent>,
@@ -822,7 +769,7 @@ constructor(
WebPaymentScreenEffect.SetResultAndFinish(status = FAILURE)
)
} else {
updateBottomSheetUIState(true)
showBottomSheet()
}
}
},
@@ -830,23 +777,25 @@ constructor(
}
}
private suspend fun finishWithError() {
_effect.emit(
WebPaymentScreenEffect.SetResultAndFinish(
status = FAILURE,
error =
GenericErrorResponse(
title =
AppServiceManager.application.resources.getString(
CommonR.string.something_went_wrong
),
message =
AppServiceManager.application.resources.getString(
R.string.generic_error_description
),
),
private fun finishWithError() {
viewModelScope.safeLaunch {
_effect.emit(
WebPaymentScreenEffect.SetResultAndFinish(
status = FAILURE,
error =
GenericErrorResponse(
title =
AppServiceManager.application.resources.getString(
CommonR.string.something_went_wrong
),
message =
AppServiceManager.application.resources.getString(
R.string.generic_error_description
),
),
)
)
)
}
}
fun handleWebSendMoney() {
@@ -865,4 +814,180 @@ constructor(
linkedAccountsUseCase.execute(screenName = screenName, includeAllDetails = true).first()
return accounts.any { it.accountType.contains("CREDIT").not() && it.isDisabled.not() }
}
fun onPayButtonClick(
activity: WebPaymentActivity,
upiResultLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
) {
val account = selectedBankAccount.value ?: return
if (showPayNowLoader.value) return
naviPaymentAnalytics.onPayNowClicked(baseAttributes = getAnalyticsParams())
if (account.isMPinSet.not()) {
naviPaymentAnalytics.onSetUpiPinBtnClick(
webPaymentAction = webPayAction ?: WebPaymentAction.WEB_SEND_MONEY,
webPaymentData = webPaymentData.toJsonObject(),
baseAttributes = getAnalyticsParams(),
)
updateLastTriggeredActionBeforeOnboarding(
webPayAction ?: WebPaymentAction.WEB_SEND_MONEY
)
handleMPinSetup(activity, upiResultLauncher)
} else {
makePayment(account)
}
}
fun handleBackClick() {
naviPaymentAnalytics.onBackPressed(getAnalyticsParams())
finishWithResult(status = TransactionStatus.USER_CANCELLED_TRANSACTION.name)
}
fun finishWithResult(
status: String,
error: GenericErrorResponse? = null,
additionalParams: Map<String, String> = emptyMap(),
) {
viewModelScope.safeLaunch(Dispatchers.IO) {
hideBottomSheet()
delay(150)
_effect.emit(
WebPaymentScreenEffect.SetResultAndFinish(
status = status,
error = error,
additionalParams = additionalParams,
)
)
}
}
private fun makePayment(account: LinkedAccountEntity) {
showPayNowLoader()
viewModelScope.safeLaunch(Dispatchers.IO) {
naviUpiPaymentProcessor.makePayment(
account = account,
isDiscountApplied = false,
discountInfo = null,
analyticScreenName = screenName,
naviPaymentScreen =
NaviPaymentScreenType
.MINI_PAYMENT_SCREEN, // Enum not present for web in backend.
paymentToken = token,
baseAttributes = getAnalyticsParams(),
onInitialChecksFailure = {
showErrorBottomSheet(
error = it,
errorReason = PMSErrorReason.NoActiveLiteAccountPSP,
)
},
onOnboardingTriggered = { hideBottomSheet() },
onOnboardingCancelled = {
showBottomSheet()
stopPayNowLoader()
},
onOnboardingCompleted = { showBottomSheet() },
onBeforePayNowApiCall = {},
onPayNowSuccess = { payNowResponse = it },
onPayNowFailure = {
stopPayNowLoader()
showErrorBottomSheet(it, PMSErrorReason.PayNowError)
},
onBeforeOpeningPinPage = { updateBottomSheetUIState(false) },
onPinPageBack = {
finishWithResult(
status = TransactionStatus.USER_CANCELLED_TRANSACTION.name,
error = null,
additionalParams =
mapOf(
TRANSACTION_REFERENCE_ID to
payNowResponse?.transactionReferenceId.orEmpty(),
PAYMENT_ORDER_REFERNCE_ID to
payNowResponse?.paymentOrderReferenceId.orEmpty(),
),
)
},
onPinPageError = {
finishWithResult(
status = FAILURE,
error = null,
additionalParams =
mapOf(
TRANSACTION_REFERENCE_ID to
payNowResponse?.transactionReferenceId.orEmpty(),
PAYMENT_ORDER_REFERNCE_ID to
payNowResponse?.paymentOrderReferenceId.orEmpty(),
),
)
},
onPinPageSuccess = { upiNavigationResponse ->
executeSendMoney(upiNavigationResponse, account)
stopPayNowLoader()
finishWithResult(
status = SUCCESS,
error = null,
additionalParams =
mapOf(
TRANSACTION_REFERENCE_ID to
payNowResponse?.transactionReferenceId.orEmpty(),
PAYMENT_ORDER_REFERNCE_ID to
payNowResponse?.paymentOrderReferenceId.orEmpty(),
UPI_REQUEST_ID to
upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
),
)
},
)
}
}
private fun showPayNowLoader() {
_showPayNowLoader.update { true }
}
private fun executeSendMoney(
upiNavigationResponse: UpiNavigationResponse,
account: LinkedAccountEntity,
) {
CoroutineScope(Dispatchers.IO).launch {
pmsSendMoneyUseCase.performChecksAndExecuteSendMoney(
onInitialChecksFailure = {
showErrorBottomSheet(it.toGenericError(), PMSErrorReason.UPIError)
},
naviUpiTransactionInfo =
NaviUpiTransactionInfo(
naviPayUpiUriKey = upiNavigationResponse.intentUri,
txnTimeStamp = upiNavigationResponse.transactionTimestamp,
credBlock = upiNavigationResponse.pmsSendMoneyStatus.credBlock,
upiRequestId = upiNavigationResponse.pmsSendMoneyStatus.upiRequestId,
selectedBankAccountId = account.accountId,
metaData = upiNavigationResponse.metaData,
onboardingDataEntity =
upiNavigationResponse.pmsSendMoneyStatus.onboardingDataEntity,
),
tstoreOrderId = upiNavigationResponse.tstoreOrderReferenceId,
screenName = NaviPaymentAnalyticScreenName.WEB_PAYMENT_SCREEN.screenName,
paymentAmount = paymentAmount.value.toString(),
onPaymentSuccess = {},
onPaymentFailure = {},
)
}
}
private fun stopPayNowLoader() {
_showPayNowLoader.update { false }
}
private fun showErrorBottomSheet(error: GenericErrorResponse?, errorReason: PMSErrorReason) {
updateBottomSheetUIState(
true,
WebPaymentScreenState.Error(PaymentErrorData(error = error, errorReason = errorReason)),
)
}
private fun showBottomSheet() {
updateBottomSheetUIState(true)
}
private fun hideBottomSheet() {
updateBottomSheetUIState(false)
}
}

View File

@@ -29,4 +29,6 @@ sealed class PMSErrorReason : Parcelable, ErrorReason {
data object Unknown : PMSErrorReason()
data object PaymentsServiceDown : PMSErrorReason()
data object NoActiveLiteAccountPSP : PMSErrorReason()
}

View File

@@ -163,4 +163,5 @@
<string name="other_apps_platform_fee_value">₹2 to ₹5</string>
<string name="platform_fee_value">₹0</string>
<string name="applicable_on_all_bills_and_recharges">Applicable on all bills and recharges.</string>
<string name="empty_payment_token">EMPTY_PAYMENT_TOKEN</string>
</resources>