NTP-17756 | Shiv Natani | implemented send money usecase for navi pay (#14116)

This commit is contained in:
Shiv Natani
2024-12-16 15:10:14 +05:30
committed by GitHub
parent bf04dfc1ed
commit 5612fe13c6
2 changed files with 289 additions and 55 deletions

View File

@@ -0,0 +1,221 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.common.usecase
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.pay.common.repository.SharedPreferenceRepository
import com.navi.pay.common.utils.DeviceInfoProvider
import com.navi.pay.common.utils.getMetricInfo
import com.navi.pay.management.common.sendmoney.model.network.GatewayPayeeInfo
import com.navi.pay.management.common.sendmoney.model.network.GatewayPayerInfo
import com.navi.pay.management.common.sendmoney.model.network.GatewayTxnInfo
import com.navi.pay.management.common.sendmoney.model.network.SendMoneyRequest
import com.navi.pay.management.common.sendmoney.model.network.TransactionInitiationType.Companion.getTransactionInitiationTypeFromSendMoneyScreenSource
import com.navi.pay.management.common.sendmoney.model.network.TransactionResponse
import com.navi.pay.management.common.sendmoney.model.view.PayeeEntity
import com.navi.pay.management.common.sendmoney.model.view.SendMoneyScreenSource
import com.navi.pay.management.common.sendmoney.model.view.UpiTransactionType
import com.navi.pay.management.common.sendmoney.repository.SendMoneyRepository
import com.navi.pay.management.common.transaction.model.network.TransactionInstrumentType
import com.navi.pay.management.lite.util.UPILitePurposeCodes
import com.navi.pay.npcicl.CredDataProvider
import com.navi.pay.npcicl.NpciRepository
import com.navi.pay.npcicl.NpciResult
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.utils.KEY_IS_FIRST_TRANSACTION_SUCCESSFUL
import com.navi.pay.utils.PAY_AGAIN
import com.navi.pay.utils.UPI_LITE_CREDBLOCK_INITIAL
import com.navi.pay.utils.UPI_LITE_CREDBLOCK_SIGNATURE
import javax.inject.Inject
import kotlin.time.measureTimedValue
class SendMoneyUseCase
@Inject
constructor(
private val npciRepository: NpciRepository,
private val deviceInfoProvider: DeviceInfoProvider,
private val credDataProvider: CredDataProvider,
private val upiLiteBalanceUseCase: UpiLiteBalanceUseCase,
private val sendMoneyRepository: SendMoneyRepository,
private val sharedPreferenceRepository: SharedPreferenceRepository,
) {
private fun modifyCredBlockForLiteTxn(credBlock: String): String {
return credBlock.replace(UPI_LITE_CREDBLOCK_SIGNATURE, UPI_LITE_CREDBLOCK_INITIAL)
}
suspend fun generateCredBlock(
selectedBankAccount: LinkedAccountEntity,
upiRequestId: String,
payeeEntity: PayeeEntity,
screenName: String,
txnTimeStamp: String,
isPaymentFromLiteAccount: Boolean = false,
onNpciResultSuccess: suspend (credBlock: String) -> Unit,
onNpciResultError: (isUserAborted: Boolean) -> Unit,
triggerEventForUpiLitePayment: () -> Unit = {},
onNpciCredentialsFetched: () -> Unit = {},
) {
val lrn = selectedBankAccount.activeLiteAccountLrn
if (isPaymentFromLiteAccount) triggerEventForUpiLitePayment()
val npciCredData =
if (isPaymentFromLiteAccount) {
credDataProvider.upiLiteSendMoneyCred(
linkedAccountEntity = selectedBankAccount,
payerLiteAccountNumber = lrn,
upiRequestId = upiRequestId,
payeeEntity = payeeEntity,
txnTimeStamp = txnTimeStamp
)
} else {
credDataProvider.payCred(
accountEntity = selectedBankAccount,
payeeEntity = payeeEntity,
upiRequestId = upiRequestId
)
}
val npciResult =
npciRepository.fetchCredentials(
npciCredData = npciCredData,
metricInfo = getMetricInfo(screenName = screenName),
)
onNpciCredentialsFetched()
when (npciResult) {
is NpciResult.Success -> {
val credBlock =
if (isPaymentFromLiteAccount) modifyCredBlockForLiteTxn(npciResult.data)
else npciResult.data
onNpciResultSuccess(credBlock)
}
is NpciResult.Error -> {
onNpciResultError(npciResult.isUserAborted)
}
else -> {}
}
}
suspend fun executeSendMoney(
payeeEntity: PayeeEntity,
upiRequestId: String,
selectedBankAccount: LinkedAccountEntity,
txnTimeStamp: String,
credBlock: String,
isPaymentFromLiteAccount: Boolean = false,
sendMoneyScreenSource: SendMoneyScreenSource,
transactionType: UpiTransactionType,
tstoreOrderId: String?,
metadata: String?,
screenName: String,
onPaymentSuccess:
suspend (
transactionCompletionTime: Long,
isTransactionEligibleForNpsComs: Boolean,
sendMoneyResponse: TransactionResponse,
) -> Unit,
onPaymentFailure:
suspend (
sendMoneyResponse: RepoResult<TransactionResponse>,
sendMoneyRequest: SendMoneyRequest,
) -> Unit,
saveDataInSavedBeneficiary: (sendMoneyRequest: SendMoneyRequest) -> Unit = {},
) {
val sendMoneyRequest =
SendMoneyRequest(
gatewayPayeeInfo =
GatewayPayeeInfo(
vpa = payeeEntity.vpa,
name = payeeEntity.name,
bankIfsc = payeeEntity.bankIfsc,
bankAccountUniqueId = payeeEntity.bankAccountUniqueId,
bankAccountNumber = payeeEntity.maskedAccountNumber,
bankName = payeeEntity.bankName,
bankCode = payeeEntity.bankCode,
mobileNumber = payeeEntity.phoneNumber,
upiNumber = payeeEntity.upiNumber,
mcc = payeeEntity.mcc,
),
gatewayPayerInfo =
GatewayPayerInfo(
merchantCustomerId = deviceInfoProvider.getMerchantCustomerId(),
vpa = selectedBankAccount.vpa,
name = selectedBankAccount.name,
bankAccountUniqueId = selectedBankAccount.accountId,
lrn =
if (isPaymentFromLiteAccount) selectedBankAccount.activeLiteAccountLrn
else null,
liteCurrBalance = upiLiteBalanceUseCase.execute(),
bankCode = selectedBankAccount.bankCode,
),
gatewayTxnInfo =
GatewayTxnInfo(
amount = payeeEntity.amount,
upiRequestId = upiRequestId,
currency = payeeEntity.currency,
initiationMode = payeeEntity.mode,
purpose =
if (isPaymentFromLiteAccount) UPILitePurposeCodes.SEND_MONEY.purposeCode
else payeeEntity.purpose,
remarks = payeeEntity.note,
timeStamp = txnTimeStamp,
credBlock =
if (isPaymentFromLiteAccount) modifyCredBlockForLiteTxn(credBlock)
else credBlock,
refCategory = payeeEntity.refCategory,
refUrl = payeeEntity.refUrl,
transactionType = transactionType.name,
transactionReference = payeeEntity.transactionReference,
paymentMode =
getTransactionInitiationTypeFromSendMoneyScreenSource(
sendMoneyScreenSource = sendMoneyScreenSource
)
.name +
if (
sendMoneyScreenSource is
SendMoneyScreenSource.TransactionHistoryDetail ||
sendMoneyScreenSource is
SendMoneyScreenSource.PaymentSummary
)
PAY_AGAIN
else "",
instrumentType = TransactionInstrumentType.TRANSACTION.name,
tstoreOrderId = tstoreOrderId,
txnRequestType = null
),
deviceData = deviceInfoProvider.getDeviceData(),
metaData = metadata,
)
saveDataInSavedBeneficiary(sendMoneyRequest)
val measureTimedSendMoneyResponse = measureTimedValue {
sendMoneyRepository.sendMoney(
sendMoneyRequest = sendMoneyRequest,
metricInfo = getMetricInfo(screenName = screenName)
)
}
val response = measureTimedSendMoneyResponse.value
val transactionCompletionTime = measureTimedSendMoneyResponse.duration.inWholeMilliseconds
// isTransactionEligibleForNpsComms is true for all type of sendMoney case
// Just that KEY_IS_FIRST_TRANSACTION_SUCCESSFUL should be false
val isTransactionEligibleForNpsComms =
!sharedPreferenceRepository.getBooleanValue(
key = KEY_IS_FIRST_TRANSACTION_SUCCESSFUL,
defValue = true
)
if (response.isSuccessWithData()) {
onPaymentSuccess(
transactionCompletionTime,
isTransactionEligibleForNpsComms,
response.data!!
)
} else {
onPaymentFailure(measureTimedSendMoneyResponse.value, sendMoneyRequest)
}
}
}

View File

@@ -72,6 +72,7 @@ import com.navi.pay.common.usecase.AccountListCheckBalanceUseCase
import com.navi.pay.common.usecase.LinkedAccountsUseCase
import com.navi.pay.common.usecase.LiteAccountSyncUseCase
import com.navi.pay.common.usecase.NaviPayConfigUseCase
import com.navi.pay.common.usecase.SendMoneyUseCase
import com.navi.pay.common.usecase.UpiLiteBalanceUseCase
import com.navi.pay.common.usecase.UpiLiteExperimentationUseCase
import com.navi.pay.common.usecase.UpiRequestIdUseCase
@@ -137,7 +138,6 @@ import com.navi.pay.navtype.payeeEntityNavType
import com.navi.pay.navtype.sendMoneyScreenSourceNavType
import com.navi.pay.navtype.upiTransactionTypeEnumNavType
import com.navi.pay.npcicl.CredDataProvider
import com.navi.pay.npcicl.NpciCredData
import com.navi.pay.npcicl.NpciRepository
import com.navi.pay.npcicl.NpciResult
import com.navi.pay.onboarding.account.add.model.view.AccountType
@@ -261,7 +261,8 @@ constructor(
private val upiLiteCommsAndMandateExecutionHandler: UpiLiteCommsAndMandateExecutionHandler,
val accountListCheckBalanceUseCase: AccountListCheckBalanceUseCase,
private val litmusExperimentsUseCase: LitmusExperimentsUseCase,
naviPayActivityDataProvider: NaviPayActivityDataProvider
private val sendMoneyUseCase: SendMoneyUseCase,
naviPayActivityDataProvider: NaviPayActivityDataProvider,
) : NaviPayBaseVM() {
private val _menuAction = MutableStateFlow(SendMoneyUserAction.None)
@@ -1594,61 +1595,37 @@ constructor(
)
val txnTimeStamp = DateTime.now().toString()
var liteSendMoneyCred: NpciCredData? = null
var npciCredData: NpciCredData? = null
val lrn = selectedBankAccount.activeLiteAccountLrn
if (isPaymentFromLiteAccount) {
liteSendMoneyCred =
credDataProvider.upiLiteSendMoneyCred(
linkedAccountEntity = selectedBankAccount,
payerLiteAccountNumber = lrn,
upiRequestId = upiRequestId,
payeeEntity = payeeEntity.value,
txnTimeStamp = txnTimeStamp
)
upiLiteAnalytics.onDevGenericEvent(
event = "startPayment",
params = mapOf("isPaymentFromLiteAccount" to "true")
)
} else {
npciCredData =
credDataProvider.payCred(
accountEntity = selectedBankAccount,
payeeEntity = payeeEntity.value,
upiRequestId = upiRequestId
)
}
val npciResult =
npciRepository.fetchCredentials(
npciCredData =
if (isPaymentFromLiteAccount) liteSendMoneyCred!! else npciCredData!!,
metricInfo = getMetricInfo(screenName = screenName),
)
updateTriggerDismissBottomSheet()
when (npciResult) {
is NpciResult.Success -> {
sendMoneyUseCase.generateCredBlock(
isPaymentFromLiteAccount = isPaymentFromLiteAccount,
upiRequestId = upiRequestId,
selectedBankAccount = selectedBankAccount,
payeeEntity = payeeEntity.value,
screenName = screenName,
txnTimeStamp = txnTimeStamp,
onNpciResultSuccess = { credBlock ->
if (isAutoTriggered) {
updateTriggerDismissBottomSheet()
}
onClSuccessCallback(
isExternalUpiRequestIdAvailable = isExternalUpiRequestIdAvailable,
upiRequestId = upiRequestId,
credBlock =
if (isPaymentFromLiteAccount) modifyCredBlockForLiteTxn(npciResult.data)
else npciResult.data,
credBlock = credBlock,
selectedBankAccountEntity = selectedBankAccount,
txnTimeStamp = txnTimeStamp,
isPaymentFromLiteAccount = isPaymentFromLiteAccount
)
}
is NpciResult.Error ->
handleNpciError(npciError = npciResult, upiRequestId = upiRequestId)
else -> Unit
}
},
onNpciResultError = { isUserAborted ->
handleNpciError(isUserAborted = isUserAborted, upiRequestId = upiRequestId)
},
triggerEventForUpiLitePayment = {
upiLiteAnalytics.onDevGenericEvent(
event = "startPayment",
params = mapOf("isPaymentFromLiteAccount" to "true")
)
},
onNpciCredentialsFetched = ::updateTriggerDismissBottomSheet
)
}
}
@@ -1700,13 +1677,46 @@ constructor(
if (!isPaymentFromLiteAccount) {
updateScreenState(screenState = SendMoneyScreenState.PaymentInProgressPostPinInput)
}
executeSendMoneyPostCredBlockGeneration(
isExternalUpiRequestIdAvailable = isExternalUpiRequestIdAvailable,
latestTransactionStartedAt = System.currentTimeMillis()
sendMoneyUseCase.executeSendMoney(
payeeEntity = payeeEntity.value,
upiRequestId = upiRequestId,
selectedBankAccount = selectedBankAccount.value!!,
credBlock = credBlock,
selectedBankAccount = selectedBankAccountEntity,
isPaymentFromLiteAccount = isPaymentFromLiteAccount,
sendMoneyScreenSource = source,
transactionType = transactionType,
tstoreOrderId = tstoreOrderId,
metadata = metadata,
saveDataInSavedBeneficiary = { sendMoneyRequest ->
viewModelScope.launch {
processRequestForSavedBeneficiaryData(sendMoneyRequest = sendMoneyRequest)
}
},
screenName = screenName,
txnTimeStamp = txnTimeStamp,
isPaymentFromLiteAccount = isPaymentFromLiteAccount
onPaymentSuccess = {
transactionCompletionTime,
isTransactionEligibleForNpsComms,
response ->
processPaymentSuccess(
sendMoneyResponse = response,
isTransactionEligibleForNpsComms = isTransactionEligibleForNpsComms,
isPaymentThroughLiteAccount = isPaymentFromLiteAccount,
upiRequestId = upiRequestId,
transactionCompletionTime = transactionCompletionTime
)
},
onPaymentFailure = { response, sendMoneyRequest ->
processPaymentFailure(
response = response,
accountId = selectedBankAccount.value!!.accountId,
isExternalUpiRequestIdAvailable = isExternalUpiRequestIdAvailable,
isPaymentThroughLite = isPaymentFromLiteAccount,
payeeVpa = sendMoneyRequest.gatewayPayeeInfo.vpa,
upiRequestId = upiRequestId
)
}
)
}
}
@@ -2028,11 +2038,11 @@ constructor(
}
}
private fun handleNpciError(npciError: NpciResult.Error, upiRequestId: String) {
private fun handleNpciError(isUserAborted: Boolean, upiRequestId: String) {
updateTriggerDismissBottomSheet()
if (source == SendMoneyScreenSource.PMS) {
val errorConfig =
if (npciError.isUserAborted) NaviPayCommonUtils.getBackPressErrorConfig()
if (isUserAborted) NaviPayCommonUtils.getBackPressErrorConfig()
else getGenericErrorConfig()
viewModelScope.launch(Dispatchers.Main) {
pmsPostMPINCallBack?.invoke(
@@ -2066,7 +2076,7 @@ constructor(
} else {
updateScreenState(screenState = SendMoneyScreenState.MainScreen)
updateSetDefaultStatusBarColor(setDefaultStatusBarColor = true)
if (!npciError.isUserAborted) {
if (!isUserAborted) {
notifyError()
}
}
@@ -2688,7 +2698,10 @@ constructor(
)
}
is NpciResult.Error ->
handleNpciError(npciError = npciResult, upiRequestId = upiRequestId)
handleNpciError(
isUserAborted = npciResult.isUserAborted,
upiRequestId = upiRequestId
)
else -> Unit
}
}