diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt index b7e4647933..29036ee901 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/analytics/NaviPayAnalytics.kt @@ -1036,6 +1036,13 @@ class NaviPayAnalytics private constructor() { NaviTrackEvent.trackEventOnClickStream("NaviPay_RsmsAutoReadOtpSuccess") } + fun onRsmsBindDeviceApiResponseAwaitFailed(message: String) { + NaviTrackEvent.trackEventOnClickStream( + "NaviPay_RsmsBindDeviceApiResponseAwaitFailed", + mapOf("message" to message), + ) + } + fun onDropOffFunnelEntered(funnelStep: FunnelStep) { NaviTrackEvent.trackEventOnClickStream( eventName = "NaviPay_DropOffFunnelEntered", diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt index e3e5580b58..1eeecf42ea 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt @@ -22,6 +22,7 @@ import com.navi.base.deeplink.util.DeeplinkConstants import com.navi.base.utils.BaseUtils import com.navi.base.utils.ResourceProvider import com.navi.base.utils.TrustedTimeAccessor +import com.navi.base.utils.isNull import com.navi.base.utils.orFalse import com.navi.base.utils.orTrue import com.navi.common.di.CoroutineDispatcherProvider @@ -115,6 +116,7 @@ import com.navi.pay.tstore.list.usecase.SyncOrderHistoryUseCase import com.navi.pay.utils.ALLOW import com.navi.pay.utils.DEFAULT_CONFIG import com.navi.pay.utils.DENY +import com.navi.pay.utils.DEVICE_BINDING_TIMEOUT import com.navi.pay.utils.INDIA_COUNTRY_CODE_WITHOUT_PLUS import com.navi.pay.utils.KEY_IS_FIRST_TRANSACTION_SUCCESSFUL import com.navi.pay.utils.LITMUS_EXPERIMENT_NAVIPAY_REVERSE_SMS_BINDING @@ -130,7 +132,6 @@ import com.navi.pay.utils.NON_VERIFIED_SENDER_LOGIN import com.navi.pay.utils.ONBOARDING_CONFIG import com.navi.pay.utils.OTP_AUTO_READ_TIMEOUT_IN_SECONDS import com.navi.pay.utils.PHONE_NUMBER_LENGTH -import com.navi.pay.utils.SMS_SENT_CHECK_TIMEOUT import com.navi.pay.utils.SMS_VERIFICATION_PENDING import com.navi.pay.utils.TOO_MANY_OTP_GENERATE_ATTEMPTS import com.navi.pay.utils.TOO_MANY_OTP_VALIDATION_ATTEMPTS @@ -162,6 +163,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.Json import org.json.JSONObject @@ -249,6 +251,9 @@ constructor( private var deviceData: DeviceData? = null private lateinit var bindDeviceResponse: BindDeviceResponse + private val bindDeviceApiSuccessCompletionSignal = + MutableSharedFlow(replay = 1) + private val deferredApiCallList = mutableListOf>() private val _isHardUpdateRequired = MutableSharedFlow(replay = 1) @@ -957,6 +962,7 @@ constructor( ) this@NaviPayOnboardingViewModel.bindDeviceResponse = bindDeviceAPIResponse.data!! + bindDeviceApiSuccessCompletionSignal.tryEmit(bindDeviceAPIResponse.data!!) val merchantCustomerId = bindDeviceResponse.pspDetails[onboardingPsp]?.merchantCustomerId.orEmpty() @@ -1156,7 +1162,7 @@ constructor( val smsContent = smsBindingData.smsContent val startTime = System.currentTimeMillis() - while (System.currentTimeMillis() - startTime < SMS_SENT_CHECK_TIMEOUT) { + while (System.currentTimeMillis() - startTime < DEVICE_BINDING_TIMEOUT) { delay(timeMillis = 1000) if (checkIfMessageIsSent(vmnNumbers = vmnNumbers, smsContent = smsContent)) { startStatusPolling(bindingType = BindingType.SMS) @@ -1218,6 +1224,19 @@ constructor( bindingType = bindingType, ) + if (!::bindDeviceResponse.isInitialized || bindDeviceResponse.isNull()) { + updateBottomSheetUIState(showBottomSheet = false) + notifyError(errorConfig = getGenericErrorConfig().copy(cancelable = false)) + naviPayAnalytics.simBindingFailure( + errorType = "BindDeviceResponse not initialized", + error = null, + onboardingSource = onboardingSource.value, + naviPaySessionAttributes = getNaviPaySessionAttributes(), + ) + updateDeviceBindingState(DeviceBindingState.Failure) + return + } + naviApiPoller .startPolling( onTimeout = { @@ -2095,9 +2114,6 @@ constructor( fun onRsmsAutoReadOtpReceived(otp: String, senderAddress: String?) { viewModelScope.launch(Dispatchers.IO) { - if (bindingType !is BindingType.RSMS) { - return@launch - } updateGeneratedOtp(otp = otp) updateAutoReadOtpVerificationState(AutoReadOtpVerificationState.VERIFYING) otpTimerJob?.cancel() @@ -2105,6 +2121,26 @@ constructor( naviPayAnalytics.onRsmsAutoReadOtpSuccess() + try { + withTimeout(timeMillis = DEVICE_BINDING_TIMEOUT) { + bindDeviceApiSuccessCompletionSignal.first() + } + } catch (e: Exception) { + naviPayAnalytics.onRsmsBindDeviceApiResponseAwaitFailed( + message = e.message.toString() + ) + updateBottomSheetUIState(showBottomSheet = false) + notifyError(errorConfig = getGenericErrorConfig().copy(cancelable = false)) + naviPayAnalytics.simBindingFailure( + errorType = e.message ?: "", + error = null, + onboardingSource = onboardingSource.value, + naviPaySessionAttributes = getNaviPaySessionAttributes(), + ) + updateDeviceBindingState(DeviceBindingState.Failure) + return@launch + } + startStatusPolling( bindingType = BindingType.RSMS(otp = otp, senderAddress = senderAddress ?: "") ) diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt index 271aae25c8..45a88e167d 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayConstants.kt @@ -24,7 +24,7 @@ const val IS_FROM_IAN = "IS_FROM_IAN" const val LITE_MAX_SEND_MONEY = 500.0 const val AADHAAR_OTP_LENGTH = 6 const val PAY_AGAIN = "|PAY_AGAIN" -const val SMS_SENT_CHECK_TIMEOUT = 45 * 1000L // 45 seconds +const val DEVICE_BINDING_TIMEOUT = 45 * 1000L // 45 seconds const val DOT_PNG = ".png" const val BOTTOM_SHEET_HEIGHT_PERCENTAGE_80 = 0.80f const val BOTTOM_SHEET_HEIGHT_PERCENTAGE_85 = 0.85f