NTP-6591 | lite low balance notification and redirection (#13236)
This commit is contained in:
committed by
Shivam Goyal
parent
dbb8f58be4
commit
89e75416da
@@ -89,7 +89,7 @@ class NaviFirebaseMessagingService : FirebaseMessagingService() {
|
||||
ModuleName.NAVIPAY.name ->
|
||||
NaviPayNotificationHandler.handlePushNotification(
|
||||
context = context,
|
||||
remoteMessage = remoteMessage
|
||||
data = remoteMessage.data
|
||||
)
|
||||
else -> {
|
||||
handleNotificationForSuperApp(remoteMessage, context)
|
||||
|
||||
@@ -155,6 +155,8 @@ object FirebaseRemoteConfigHelper {
|
||||
const val NAVI_PAY_SEND_MONEY_FTUE_LIMIT = "NAVI_PAY_SEND_MONEY_FTUE_LIMIT"
|
||||
const val NAVI_PAY_AUTO_FLASH_DISABLED = "NAVI_PAY_AUTO_FLASH_DISABLED"
|
||||
const val NAVI_PAY_AUTO_FLASH_LIGHT_LUX_THRESHOLD = "NAVI_PAY_AUTO_FLASH_LIGHT_LUX_THRESHOLD"
|
||||
const val NAVI_PAY_UPI_LITE_LOW_BALANCE_NOTIFICATION_PN_DISABLED =
|
||||
"NAVI_PAY_UPI_LITE_LOW_BALANCE_NOTIFICATION_PN_DISABLED"
|
||||
|
||||
// PAYMENTS
|
||||
const val NAVI_PMT_JUSPAY_INIT_OPTIMISATION_DISABLE =
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.pay.common.model.view
|
||||
|
||||
data class UpiLiteLowBalanceNotificationData(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val hours: Int,
|
||||
val ctaUrl: String
|
||||
)
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2024 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.pay.common.usecase
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.navi.base.cache.model.NaviCacheEntity
|
||||
import com.navi.base.cache.repository.NaviCacheRepository
|
||||
import com.navi.common.di.CoroutineDispatcherProvider
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_UPI_LITE_LOW_BALANCE_BANNER_MAX_LIMIT
|
||||
import com.navi.pay.analytics.NaviPayAnalytics
|
||||
import com.navi.pay.common.utils.NaviPayNotificationHandler
|
||||
import com.navi.pay.management.lite.models.NaviPayUpiLiteConfig
|
||||
import com.navi.pay.management.mandate.model.network.MandateItem
|
||||
import com.navi.pay.management.mandate.model.view.MandateStatus
|
||||
import com.navi.pay.network.di.NaviPayGsonBuilder
|
||||
import com.navi.pay.utils.ConfigKey
|
||||
import com.navi.pay.utils.KEY_UPI_LITE_LAST_NOTIFICATION_TIMESTAMP
|
||||
import com.navi.pay.utils.KEY_UPI_LITE_MANDATE_INFO
|
||||
import com.navi.pay.utils.NAVI_PAY_NOTIFICATION_BODY
|
||||
import com.navi.pay.utils.NAVI_PAY_NOTIFICATION_DATA_CTA_KEY
|
||||
import com.navi.pay.utils.NAVI_PAY_NOTIFICATION_ID
|
||||
import com.navi.pay.utils.NAVI_PAY_NOTIFICATION_TITLE
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Hours
|
||||
|
||||
class UpiLiteLowBalanceNotificationUseCase
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
private val naviPayConfigUseCase: NaviPayConfigUseCase,
|
||||
private val naviCacheRepository: NaviCacheRepository,
|
||||
private val upiLiteBalanceUseCase: UpiLiteBalanceUseCase,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
@NaviPayGsonBuilder private val gson: Gson
|
||||
) {
|
||||
private val naviPayAnalytics: NaviPayAnalytics.NaviPayUPILite =
|
||||
NaviPayAnalytics.INSTANCE.NaviPayUPILite()
|
||||
|
||||
companion object {
|
||||
private const val UPI_LITE_LOW_BALANCE_NOTIFICATION_ID = 2000
|
||||
}
|
||||
|
||||
suspend fun execute(screenName: String) {
|
||||
val upiLiteConfig = getUpiLiteConfig(screenName = screenName)
|
||||
val upiLiteMandateInfo = getCachedMandateInfo()
|
||||
val upiLiteBalance = upiLiteBalanceUseCase.execute().toDoubleOrNull() ?: 0.0
|
||||
val lowBalanceThreshold =
|
||||
FirebaseRemoteConfigHelper.getLong(NAVI_PAY_UPI_LITE_LOW_BALANCE_BANNER_MAX_LIMIT)
|
||||
|
||||
naviPayAnalytics.onDevGenericEvent(
|
||||
event = "NaviPay_UpiLiteLowBalanceNotificationUseCase_execute",
|
||||
params =
|
||||
mapOf(
|
||||
"mandateInfo" to upiLiteMandateInfo.toString(),
|
||||
"config" to upiLiteConfig.toString(),
|
||||
"balance" to upiLiteBalance.toString(),
|
||||
"threshold" to lowBalanceThreshold.toString()
|
||||
)
|
||||
)
|
||||
|
||||
// Trigger notification only if zero non-terminal lite mandates are present
|
||||
if (!shouldTriggerLowBalanceNotification(upiLiteMandateInfo = upiLiteMandateInfo)) {
|
||||
return
|
||||
}
|
||||
|
||||
// If balance is sufficient, do not trigger notification
|
||||
if (upiLiteBalance >= lowBalanceThreshold) return
|
||||
|
||||
// Check if notification was recently triggered
|
||||
if (wasRecentlyNotified(hoursLimit = upiLiteConfig.config.lowBalanceNotificationData.hours))
|
||||
return
|
||||
|
||||
sendNotificationAndUpdateCache(upiLiteConfig = upiLiteConfig)
|
||||
}
|
||||
|
||||
private suspend fun wasRecentlyNotified(hoursLimit: Int): Boolean {
|
||||
val lastTriggeredTimeStamp =
|
||||
naviCacheRepository.get(key = KEY_UPI_LITE_LAST_NOTIFICATION_TIMESTAMP)?.value
|
||||
return lastTriggeredTimeStamp?.toLongOrNull()?.let { timestampMillis ->
|
||||
Hours.hoursBetween(DateTime(timestampMillis), DateTime.now()).hours <= hoursLimit
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private suspend fun sendNotificationAndUpdateCache(upiLiteConfig: NaviPayUpiLiteConfig) {
|
||||
val notificationData = upiLiteConfig.config.lowBalanceNotificationData
|
||||
NaviPayNotificationHandler.handlePushNotification(
|
||||
context = context,
|
||||
data =
|
||||
mapOf(
|
||||
NAVI_PAY_NOTIFICATION_DATA_CTA_KEY to notificationData.ctaUrl,
|
||||
NAVI_PAY_NOTIFICATION_TITLE to notificationData.title,
|
||||
NAVI_PAY_NOTIFICATION_BODY to notificationData.description,
|
||||
NAVI_PAY_NOTIFICATION_ID to UPI_LITE_LOW_BALANCE_NOTIFICATION_ID.toString()
|
||||
)
|
||||
)
|
||||
|
||||
// Update cache with current timestamp
|
||||
naviCacheRepository.save(
|
||||
naviCacheEntity =
|
||||
NaviCacheEntity(
|
||||
key = KEY_UPI_LITE_LAST_NOTIFICATION_TIMESTAMP,
|
||||
value = DateTime.now().millis.toString(),
|
||||
version = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun shouldTriggerLowBalanceNotification(upiLiteMandateInfo: MandateItem?): Boolean {
|
||||
return upiLiteMandateInfo == null ||
|
||||
isMandateCompleted(upiLiteMandateInfo = upiLiteMandateInfo)
|
||||
}
|
||||
|
||||
private fun isMandateCompleted(upiLiteMandateInfo: MandateItem?): Boolean {
|
||||
return upiLiteMandateInfo?.let {
|
||||
!MandateStatus.isNonCompletedLiteMandate(status = it.status)
|
||||
} ?: true
|
||||
}
|
||||
|
||||
private suspend fun getUpiLiteConfig(screenName: String): NaviPayUpiLiteConfig {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
naviPayConfigUseCase.execute<NaviPayUpiLiteConfig>(
|
||||
ConfigKey.UPI_LITE,
|
||||
object : TypeToken<NaviPayUpiLiteConfig>() {}.type,
|
||||
screenName = screenName
|
||||
) ?: NaviPayUpiLiteConfig()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCachedMandateInfo(): MandateItem? {
|
||||
val liteMandateInfoFromCache = naviCacheRepository.get(key = KEY_UPI_LITE_MANDATE_INFO)
|
||||
return liteMandateInfoFromCache?.value?.let { liteMandateInfo ->
|
||||
gson.fromJson(liteMandateInfo, MandateItem::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.DEFAULT_ALL
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.navi.base.sharedpref.PreferenceManager
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.INTENT_DATA_INSERT_DISABLED
|
||||
@@ -42,11 +41,11 @@ import kotlinx.coroutines.launch
|
||||
|
||||
object NaviPayNotificationHandler {
|
||||
|
||||
fun handlePushNotification(context: Context, remoteMessage: RemoteMessage) {
|
||||
fun handlePushNotification(context: Context, data: Map<String, String>) {
|
||||
val intent = Intent(context, NaviPayActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
|
||||
val remoteDataAsHashMap = HashMap(remoteMessage.data.toMutableMap())
|
||||
val remoteDataAsHashMap = HashMap(data.toMutableMap())
|
||||
|
||||
intent.putExtra(NAVI_PAY_NOTIFICATION_DATA_BUNDLE, remoteDataAsHashMap.getBundle())
|
||||
PreferenceManager.setBooleanPreference(PENDING_REQUEST_REFRESHED_REQUIRED, true)
|
||||
@@ -64,18 +63,17 @@ object NaviPayNotificationHandler {
|
||||
)
|
||||
|
||||
val notificationContentTitle =
|
||||
remoteMessage.data[NAVI_PAY_NOTIFICATION_TITLE]
|
||||
data[NAVI_PAY_NOTIFICATION_TITLE]
|
||||
?: context.getString(R.string.default_navi_pay_notification_title)
|
||||
|
||||
val notificationContentBody =
|
||||
remoteMessage.data[NAVI_PAY_NOTIFICATION_BODY]
|
||||
data[NAVI_PAY_NOTIFICATION_BODY]
|
||||
?: context.getString(R.string.default_navi_pay_notification_body)
|
||||
|
||||
val channelName = context.getString(R.string.navi_pay_channel_name)
|
||||
val channelId = context.getString(R.string.navi_pay_channel_id)
|
||||
|
||||
val notificationId =
|
||||
getNotificationIdAsInt(data = remoteMessage.data[NAVI_PAY_NOTIFICATION_ID])
|
||||
val notificationId = getNotificationIdAsInt(data = data[NAVI_PAY_NOTIFICATION_ID])
|
||||
val vibrationPatternArray = longArrayOf(100, 200, 300)
|
||||
val notificationBuilder =
|
||||
NotificationCompat.Builder(context, channelId)
|
||||
@@ -106,10 +104,7 @@ object NaviPayNotificationHandler {
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
if (
|
||||
remoteMessage.data[NAVI_PAY_NOTIFICATION_DATA_CTA_KEY] ==
|
||||
NAVI_PAY_NOTIFICATION_COLLECT_REQUEST
|
||||
) {
|
||||
if (data[NAVI_PAY_NOTIFICATION_DATA_CTA_KEY] == NAVI_PAY_NOTIFICATION_COLLECT_REQUEST) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
CollectRequestPopupEventHandler.postEvent(triggerScreenOverlayApi = true)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.navi.common.R as CommonR
|
||||
import com.navi.common.di.CoroutineDispatcherProvider
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_SCRATCH_CARD_OPTIMISATION_V2_ENABLED
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_PAY_UPI_LITE_LOW_BALANCE_NOTIFICATION_PN_DISABLED
|
||||
import com.navi.common.network.models.RepoResult
|
||||
import com.navi.common.network.models.isSuccess
|
||||
import com.navi.common.network.models.isSuccessWithData
|
||||
@@ -73,6 +74,7 @@ import com.navi.pay.common.usecase.NaviPayConfigUseCase
|
||||
import com.navi.pay.common.usecase.SubsequentLiteMandateExecutionUseCase
|
||||
import com.navi.pay.common.usecase.UpiLiteBalanceUseCase
|
||||
import com.navi.pay.common.usecase.UpiLiteExperimentationUseCase
|
||||
import com.navi.pay.common.usecase.UpiLiteLowBalanceNotificationUseCase
|
||||
import com.navi.pay.common.usecase.UpiRequestIdUseCase
|
||||
import com.navi.pay.common.usecase.ValidateVpaUseCase
|
||||
import com.navi.pay.common.utils.DeviceInfoProvider
|
||||
@@ -255,6 +257,7 @@ constructor(
|
||||
private val naviCacheRepository: NaviCacheRepository,
|
||||
private val subsequentLiteMandateExecutionUseCase: SubsequentLiteMandateExecutionUseCase,
|
||||
private val upiLiteExperimentationUseCase: UpiLiteExperimentationUseCase,
|
||||
private val upiLiteLowBalanceNotificationUseCase: UpiLiteLowBalanceNotificationUseCase,
|
||||
naviPayActivityDataProvider: NaviPayActivityDataProvider
|
||||
) : NaviPayBaseVM() {
|
||||
|
||||
@@ -2221,6 +2224,11 @@ constructor(
|
||||
arpc = sendMoneyResponse.arpc,
|
||||
accountId = selectedBankAccount.value?.accountId.orEmpty()
|
||||
)
|
||||
if (isLiteLowBalanceNotificationsEnabled()) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
upiLiteLowBalanceNotificationUseCase.execute(screenName = screenName)
|
||||
}
|
||||
}
|
||||
}
|
||||
val upiLiteBalance = upiLiteBalanceUseCase.execute()
|
||||
updateScreenState(
|
||||
@@ -2247,6 +2255,23 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isLiteLowBalanceNotificationsEnabled(): Boolean {
|
||||
val isLiteLowBalanceNotificationsEnabled =
|
||||
!FirebaseRemoteConfigHelper.getBoolean(
|
||||
key = NAVI_PAY_UPI_LITE_LOW_BALANCE_NOTIFICATION_PN_DISABLED
|
||||
)
|
||||
|
||||
upiLiteAnalytics.onDevGenericEvent(
|
||||
event = ::isLiteLowBalanceNotificationsEnabled.name,
|
||||
params =
|
||||
mapOf(
|
||||
"isLiteLowBalanceNotificationsEnabled" to
|
||||
isLiteLowBalanceNotificationsEnabled.toString()
|
||||
)
|
||||
)
|
||||
return isLiteLowBalanceNotificationsEnabled
|
||||
}
|
||||
|
||||
private suspend fun syncLiteBalance(arpc: String? = null, accountId: String) {
|
||||
val formattedAccountId = accountId.substringBefore(UNDERSCORE)
|
||||
if (arpc.isNullOrBlank()) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package com.navi.pay.management.lite.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.pay.common.model.view.UpiLiteLowBalanceNotificationData
|
||||
import com.navi.pay.utils.NAVI_PAY_UPI_LITE_LOGO_URL
|
||||
|
||||
data class NaviPayUpiLiteConfig(
|
||||
@@ -43,5 +44,14 @@ data class NaviPayUpiLiteConfigContent(
|
||||
@SerializedName("upiLiteNonOnboardedBannerTitle3")
|
||||
val upiLiteNonOnboardedBannerTitle3: String = "Withdraw",
|
||||
@SerializedName("upiLiteNonOnboardedBannerSubTitle3")
|
||||
val upiLiteNonOnboardedBannerSubTitle3: String = "balance anytime"
|
||||
val upiLiteNonOnboardedBannerSubTitle3: String = "balance anytime",
|
||||
@SerializedName("lowBalanceNotificationData")
|
||||
val lowBalanceNotificationData: UpiLiteLowBalanceNotificationData =
|
||||
UpiLiteLowBalanceNotificationData(
|
||||
title = "Lite balance is running low!",
|
||||
description =
|
||||
"Recharge your Lite wallet now for lightning fast PIN-free payments in the future.",
|
||||
hours = 24,
|
||||
ctaUrl = "naviPay/NAVI_PAY_UPI_LITE"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -204,7 +204,7 @@ fun UPILiteScreen(
|
||||
if (naviPayActivity.needsResult) {
|
||||
naviPayActivity.finish()
|
||||
} else {
|
||||
navigator.navigateUp()
|
||||
navigator.navigateUp(naviPayActivity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ fun UPILiteScreen(
|
||||
// Do nothing, back press is disabled for N seconds when top-up is in progress
|
||||
}
|
||||
is UpiLiteBackPressAction.GoBack -> {
|
||||
navigator.navigateUp()
|
||||
navigator.navigateUp(naviPayActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.navi.pay.onboarding.launcher.model.view
|
||||
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.mandate.model.view.MandateEntity
|
||||
import com.ramcosta.composedestinations.spec.Direction
|
||||
|
||||
sealed class LauncherScreenToNextScreenNavigate {
|
||||
data class OnLocalDataValidState(val customerStatus: String) :
|
||||
@@ -28,4 +29,7 @@ sealed class LauncherScreenToNextScreenNavigate {
|
||||
LauncherScreenToNextScreenNavigate()
|
||||
|
||||
data object None : LauncherScreenToNextScreenNavigate()
|
||||
|
||||
data class RedirectToDestination(val destination: Direction) :
|
||||
LauncherScreenToNextScreenNavigate()
|
||||
}
|
||||
|
||||
@@ -150,5 +150,12 @@ private fun handleNavigation(
|
||||
LauncherScreenToNextScreenNavigate.None -> {
|
||||
naviPayActivity.finish()
|
||||
}
|
||||
is LauncherScreenToNextScreenNavigate.RedirectToDestination -> {
|
||||
navigator.clearBackStackUpToAndNavigate(
|
||||
destination = navigateToNextScreen.destination,
|
||||
popUpTo = NavGraphs.root,
|
||||
inclusive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,12 @@ constructor(
|
||||
message =
|
||||
"checkNotificationDataAndNavigate. Next screen destination is $nextScreenDestination"
|
||||
)
|
||||
updateNavigateToNextScreenValue(value = LauncherScreenToNextScreenNavigate.None)
|
||||
updateNavigateToNextScreenValue(
|
||||
value =
|
||||
LauncherScreenToNextScreenNavigate.RedirectToDestination(
|
||||
destination = nextScreenDestination
|
||||
)
|
||||
)
|
||||
} else {
|
||||
navigateToNextScreenBasedOnCustomerStatus(customerStatus = customerStatus)
|
||||
}
|
||||
@@ -484,7 +489,12 @@ constructor(
|
||||
message =
|
||||
"checkNaviPayLocalUriAndNavigate. Next screen destination is $nextScreenDestination"
|
||||
)
|
||||
updateNavigateToNextScreenValue(value = LauncherScreenToNextScreenNavigate.None)
|
||||
updateNavigateToNextScreenValue(
|
||||
value =
|
||||
LauncherScreenToNextScreenNavigate.RedirectToDestination(
|
||||
destination = nextScreenDestination
|
||||
)
|
||||
)
|
||||
} else {
|
||||
logEvent(
|
||||
message = "checkNaviPayLocalUriAndNavigate. Next screen destination is null"
|
||||
|
||||
@@ -175,6 +175,7 @@ const val NAVI_PAY_SEND_MONEY_FTUE_NUDGE_COUNTER = "sendMoneyFtueNudgeCounter"
|
||||
const val NAVI_PAY_SETTING_QR_PAGER_ANIMATION_COUNTER = "settingQrPagerAnimationCounter"
|
||||
const val NAVI_PAY_AUTO_POPUP_SCRATCH_CARD_COUNTER = "autoPopupScratchCardCounter"
|
||||
const val KEY_UPI_LITE_MANDATE_INFO = "liteMandateInfo"
|
||||
const val KEY_UPI_LITE_LAST_NOTIFICATION_TIMESTAMP = "upiLiteLastNotificationTimestamp"
|
||||
|
||||
// Sync DB keys
|
||||
const val NAVI_PAY_SYNC_TABLE_TRANSACTION_HISTORY_KEY = "transactionHistoryKey"
|
||||
|
||||
Reference in New Issue
Block a user