diff --git a/app/src/main/java/org/openedx/app/di/ScreenModule.kt b/app/src/main/java/org/openedx/app/di/ScreenModule.kt index 57da53c2c..aae54b301 100644 --- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt +++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt @@ -172,6 +172,7 @@ val screenModule = module { get(), get(), get(), + get(), windowSize, get(), ) diff --git a/core/src/main/java/org/openedx/core/domain/interactor/IAPInteractor.kt b/core/src/main/java/org/openedx/core/domain/interactor/IAPInteractor.kt index 970d4ef9e..d9af0ffa4 100644 --- a/core/src/main/java/org/openedx/core/domain/interactor/IAPInteractor.kt +++ b/core/src/main/java/org/openedx/core/domain/interactor/IAPInteractor.kt @@ -10,15 +10,18 @@ import org.openedx.core.R import org.openedx.core.config.Config import org.openedx.core.data.repository.iap.IAPRepository import org.openedx.core.data.storage.CorePreferences +import org.openedx.core.domain.model.EnrolledCourse import org.openedx.core.domain.model.iap.ProductInfo +import org.openedx.core.domain.model.iap.PurchaseFlowData import org.openedx.core.exception.iap.IAPException -import org.openedx.core.extension.decodeToLong import org.openedx.core.module.billing.BillingProcessor import org.openedx.core.module.billing.getCourseSku import org.openedx.core.module.billing.getPriceAmount +import org.openedx.core.module.billing.getUserId import org.openedx.core.presentation.global.AppData import org.openedx.core.presentation.iap.IAPRequestType import org.openedx.core.utils.EmailUtil +import org.openedx.core.utils.TimeUtils class IAPInteractor( private val appData: AppData, @@ -109,52 +112,83 @@ class IAPInteractor( } } - suspend fun processUnfulfilledPurchase(userId: Long): Boolean { + suspend fun processUnfulfilledPurchase( + userId: Long, + enrolledCourses: List, + verificationInitiated: (PurchaseFlowData) -> Unit = {}, + ): PurchaseFlowData? { val purchases = billingProcessor.queryPurchases() - val userPurchases = - purchases.filter { it.accountIdentifiers?.obfuscatedAccountId?.decodeToLong() == userId } + val userPurchases = purchases.filter { purchase -> + val userAccountId = purchase.getUserId() + val courseSku = purchase.getCourseSku() + + userAccountId == userId && enrolledCourses.any { enrolledCourse -> + courseSku == enrolledCourse.productInfo?.courseSku + } + } if (userPurchases.isNotEmpty()) { - startUnfulfilledVerification(userPurchases) - return true + userPurchases.first().let { purchase -> + val courseVerified = enrolledCourses.find { enrolledCourse -> + enrolledCourse.productInfo?.courseSku == purchase.getCourseSku() + } + courseVerified?.let { + val productDetails = + billingProcessor.querySyncDetails(purchase.products[0]).productDetailsList?.firstOrNull() + val purchaseProductFlow = PurchaseFlowData( + courseId = courseVerified.course.id, + isSelfPaced = courseVerified.course.isSelfPaced, + productInfo = courseVerified.productInfo + ).apply { + productDetails?.oneTimePurchaseOfferDetails?.let { + this.price = it.getPriceAmount() + this.currencyCode = it.priceCurrencyCode + } + this.flowStartTime = TimeUtils.getCurrentTime() + } + verificationInitiated(purchaseProductFlow) + startUnfulfilledVerification(purchase) + return purchaseProductFlow + } + } } else { purchases.forEach { billingProcessor.consumePurchase(it.purchaseToken) } } - return false + return null } - private suspend fun startUnfulfilledVerification(userPurchases: List) { - userPurchases.forEach { purchase -> - val productDetail = - billingProcessor.querySyncDetails(purchase.products.first()).productDetailsList?.firstOrNull() - productDetail?.oneTimePurchaseOfferDetails?.takeIf { - purchase.getCourseSku().isNullOrEmpty().not() - }?.let { oneTimeProductDetails -> - val courseSku = purchase.getCourseSku() ?: return@let - val basketId = addToBasket(courseSku) - executeOrder( - basketId = basketId, - purchaseToken = purchase.purchaseToken, - price = oneTimeProductDetails.getPriceAmount(), - currencyCode = oneTimeProductDetails.priceCurrencyCode, - ) - consumePurchase(purchase.purchaseToken) - } + private suspend fun startUnfulfilledVerification(userPurchase: Purchase) { + val productDetail = + billingProcessor.querySyncDetails(userPurchase.products.first()).productDetailsList?.firstOrNull() + productDetail?.oneTimePurchaseOfferDetails?.takeIf { + userPurchase.getCourseSku().isNullOrEmpty().not() + }?.let { oneTimeProductDetails -> + val courseSku = userPurchase.getCourseSku() ?: return@let + val basketId = addToBasket(courseSku) + executeOrder( + basketId = basketId, + purchaseToken = userPurchase.purchaseToken, + price = oneTimeProductDetails.getPriceAmount(), + currencyCode = oneTimeProductDetails.priceCurrencyCode, + ) + consumePurchase(userPurchase.purchaseToken) } } suspend fun detectUnfulfilledPurchase( - onSuccess: () -> Unit, + enrolledCourses: List, + verificationInitiated: (PurchaseFlowData) -> Unit, + onSuccess: (PurchaseFlowData) -> Unit, onFailure: (IAPException) -> Unit, ) { if (isIAPEnabled) { preferencesManager.user?.id?.let { userId -> runCatching { - processUnfulfilledPurchase(userId) - }.onSuccess { - if (it) { - onSuccess() + processUnfulfilledPurchase(userId, enrolledCourses, verificationInitiated) + }.onSuccess { purchaseFlowData -> + purchaseFlowData?.let { + onSuccess(purchaseFlowData) } }.onFailure { if (it is IAPException) { diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseEnrollments.kt b/core/src/main/java/org/openedx/core/domain/model/CourseEnrollments.kt index 6606902c2..6ab336c3c 100644 --- a/core/src/main/java/org/openedx/core/domain/model/CourseEnrollments.kt +++ b/core/src/main/java/org/openedx/core/domain/model/CourseEnrollments.kt @@ -4,4 +4,8 @@ data class CourseEnrollments( val enrollments: DashboardCourseList, val configs: AppConfig, val primary: EnrolledCourse?, -) +) { + fun hasEnrolledCourses(): Boolean { + return primary != null || enrollments.courses.isNotEmpty() + } +} diff --git a/core/src/main/java/org/openedx/core/domain/model/iap/PurchaseFlowData.kt b/core/src/main/java/org/openedx/core/domain/model/iap/PurchaseFlowData.kt index 48233d299..c6fc65885 100644 --- a/core/src/main/java/org/openedx/core/domain/model/iap/PurchaseFlowData.kt +++ b/core/src/main/java/org/openedx/core/domain/model/iap/PurchaseFlowData.kt @@ -37,6 +37,20 @@ data class PurchaseFlowData( basketId = -1 flowStartTime = 0 } + + fun isSilentIAPFlow(): Boolean? { + return when (iapFlow) { + IAPFlow.SILENT -> { + true + } + IAPFlow.RESTORE -> { + false + } + else -> { + null + } + } + } } enum class IAPFlow(val value: String) { diff --git a/core/src/main/java/org/openedx/core/module/billing/BillingProcessor.kt b/core/src/main/java/org/openedx/core/module/billing/BillingProcessor.kt index dff7a717c..2c3d38ee1 100644 --- a/core/src/main/java/org/openedx/core/module/billing/BillingProcessor.kt +++ b/core/src/main/java/org/openedx/core/module/billing/BillingProcessor.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import org.openedx.core.domain.model.iap.ProductInfo +import org.openedx.core.extension.decodeToLong import org.openedx.core.extension.decodeToString import org.openedx.core.extension.encodeToString import org.openedx.core.extension.safeResume @@ -203,3 +204,7 @@ fun ProductDetails.OneTimePurchaseOfferDetails.getPriceAmount(): Double = fun Purchase.getCourseSku(): String? { return this.accountIdentifiers?.obfuscatedProfileId?.decodeToString() } + +fun Purchase.getUserId(): Long? { + return this.accountIdentifiers?.obfuscatedAccountId?.decodeToLong() +} diff --git a/core/src/main/java/org/openedx/core/presentation/iap/IAPEventLogger.kt b/core/src/main/java/org/openedx/core/presentation/iap/IAPEventLogger.kt index 0d5e0c16f..301a43cb6 100644 --- a/core/src/main/java/org/openedx/core/presentation/iap/IAPEventLogger.kt +++ b/core/src/main/java/org/openedx/core/presentation/iap/IAPEventLogger.kt @@ -15,8 +15,8 @@ import org.openedx.core.utils.TimeUtils class IAPEventLogger( private val analytics: IAPAnalytics, - private val purchaseFlowData: PurchaseFlowData? = null, - private val isSilentIAPFlow: Boolean? = null, + var isSilentIAPFlow: Boolean? = null, + var purchaseFlowData: PurchaseFlowData? = null, ) { fun upgradeNowClickedEvent() { logIAPEvent(IAPAnalyticsEvent.IAP_UPGRADE_NOW_CLICKED) @@ -64,7 +64,7 @@ class IAPEventLogger( IAPRequestType.PRICE_CODE, IAPRequestType.NO_SKU_CODE, - -> { + -> { priceLoadErrorEvent(feedbackErrorMessage) } @@ -186,7 +186,7 @@ class IAPEventLogger( putAll(params) putAll(getIAPEventParams()) putAll(getUnfulfilledIAPEventParams()) - }, + } ) } } diff --git a/core/src/main/java/org/openedx/core/presentation/iap/IAPViewModel.kt b/core/src/main/java/org/openedx/core/presentation/iap/IAPViewModel.kt index 981e6b107..2e8504b77 100644 --- a/core/src/main/java/org/openedx/core/presentation/iap/IAPViewModel.kt +++ b/core/src/main/java/org/openedx/core/presentation/iap/IAPViewModel.kt @@ -24,6 +24,7 @@ import org.openedx.core.domain.model.iap.IAPFlow import org.openedx.core.domain.model.iap.IAPFlowSource import org.openedx.core.domain.model.iap.PurchaseFlowData import org.openedx.core.exception.iap.IAPException +import org.openedx.core.extension.isNull import org.openedx.core.module.billing.BillingProcessor import org.openedx.core.module.billing.getCourseSku import org.openedx.core.module.billing.getPriceAmount @@ -55,6 +56,7 @@ class IAPViewModel( val eventLogger = IAPEventLogger( analytics = analytics, + isSilentIAPFlow = purchaseData.isSilentIAPFlow(), purchaseFlowData = purchaseData ) @@ -84,7 +86,9 @@ class IAPViewModel( iapNotifier.notifier.onEach { event -> when (event) { is CourseDataUpdated -> { - eventLogger.upgradeSuccessEvent() + if (eventLogger.isSilentIAPFlow.isNull()) { + eventLogger.upgradeSuccessEvent() + } _uiMessage.emit(UIMessage.ToastMessage(resourceManager.getString(R.string.iap_success_message))) _uiState.value = IAPUIState.CourseDataUpdated } diff --git a/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt b/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt index 804d84a65..80464be11 100644 --- a/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt +++ b/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt @@ -1,12 +1,17 @@ package org.openedx.core.system.notifier.app +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow class AppNotifier { - private val channel = MutableSharedFlow(replay = 0, extraBufferCapacity = 0) + private val channel = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) val notifier: Flow = channel.asSharedFlow() @@ -16,4 +21,7 @@ class AppNotifier { suspend fun send(event: AppUpgradeEvent) = channel.emit(event) + suspend fun send(event: EnrolledCourseEvent) = channel.emit(event) + + suspend fun send(event: RequestEnrolledCourseEvent) = channel.emit(event) } diff --git a/core/src/main/java/org/openedx/core/system/notifier/app/EnrolledCourseEvent.kt b/core/src/main/java/org/openedx/core/system/notifier/app/EnrolledCourseEvent.kt new file mode 100644 index 000000000..bcd0d0de9 --- /dev/null +++ b/core/src/main/java/org/openedx/core/system/notifier/app/EnrolledCourseEvent.kt @@ -0,0 +1,5 @@ +package org.openedx.core.system.notifier.app + +import org.openedx.core.domain.model.EnrolledCourse + +class EnrolledCourseEvent(val enrolledCourses: List,) : AppEvent diff --git a/core/src/main/java/org/openedx/core/system/notifier/app/RequestEnrolledCourseEvent.kt b/core/src/main/java/org/openedx/core/system/notifier/app/RequestEnrolledCourseEvent.kt new file mode 100644 index 000000000..09e2490e3 --- /dev/null +++ b/core/src/main/java/org/openedx/core/system/notifier/app/RequestEnrolledCourseEvent.kt @@ -0,0 +1,3 @@ +package org.openedx.core.system.notifier.app + +object RequestEnrolledCourseEvent : AppEvent diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt index 1f212af6c..f72291493 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt @@ -39,6 +39,7 @@ import org.openedx.core.domain.model.iap.PurchaseFlowData import org.openedx.core.exception.iap.IAPException import org.openedx.core.extension.isFalse import org.openedx.core.extension.isNotNull +import org.openedx.core.extension.isNull import org.openedx.core.extension.isTrue import org.openedx.core.module.billing.BillingProcessor import org.openedx.core.module.billing.getCourseSku @@ -286,10 +287,14 @@ class CourseContainerViewModel( } if (isIAPFlow) { if (isExpiredCoursePurchase) { - eventLogger.upgradeSuccessEvent() + if (eventLogger.isSilentIAPFlow.isNull()) { + eventLogger.upgradeSuccessEvent() + } _uiMessage.emit( UIMessage.ToastMessage( - resourceManager.getString(CoreR.string.iap_success_message) + resourceManager.getString( + CoreR.string.iap_success_message + ) ) ) } else { diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt index 7958694e9..7106721e4 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt @@ -263,8 +263,8 @@ private fun DashboardGalleryView( }, onIAPAction = onIAPAction, ) - LaunchedEffect(uiState.userCourses.enrollments.courses) { - if (uiState.userCourses.enrollments.courses.isNotEmpty()) { + LaunchedEffect(uiState.userCourses.hasEnrolledCourses()) { + if (uiState.userCourses.hasEnrolledCourses()) { onIAPAction(IAPAction.ACTION_UNFULFILLED, null, null) } } diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt index 78631b9b7..d42372325 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt @@ -43,8 +43,12 @@ import org.openedx.core.system.notifier.NavigationToDiscovery import org.openedx.core.system.notifier.PushEvent import org.openedx.core.system.notifier.PushNotifier import org.openedx.core.system.notifier.UpdateCourseData +import org.openedx.core.system.notifier.app.AppNotifier +import org.openedx.core.system.notifier.app.EnrolledCourseEvent +import org.openedx.core.system.notifier.app.RequestEnrolledCourseEvent import org.openedx.core.ui.WindowSize import org.openedx.core.utils.FileUtil +import org.openedx.dashboard.domain.CourseStatusFilter import org.openedx.dashboard.domain.interactor.DashboardInteractor import org.openedx.dashboard.presentation.DashboardRouter @@ -60,6 +64,7 @@ class DashboardGalleryViewModel( private val dashboardRouter: DashboardRouter, private val iapNotifier: IAPNotifier, private val pushNotifier: PushNotifier, + private val appNotifier: AppNotifier, private val iapInteractor: IAPInteractor, private val windowSize: WindowSize, iapAnalytics: IAPAnalytics, @@ -76,7 +81,7 @@ class DashboardGalleryViewModel( val uiMessage: SharedFlow get() = _uiMessage.asSharedFlow() - private val _updating = MutableStateFlow(false) + private val _updating = MutableStateFlow(false) val updating: StateFlow get() = _updating.asStateFlow() @@ -96,11 +101,25 @@ class DashboardGalleryViewModel( private var isLoading = false init { + collectAppEvent() collectDiscoveryNotifier() collectIapNotifier() getCourses() } + private fun collectAppEvent() { + appNotifier.notifier + .onEach { + if (it is RequestEnrolledCourseEvent) { + val enrolledCourses = + interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses + appNotifier.send(EnrolledCourseEvent(enrolledCourses)) + } + } + .distinctUntilChanged() + .launchIn(viewModelScope) + } + fun getCourses(isIAPFlow: Boolean = false) { viewModelScope.launch { try { @@ -258,9 +277,21 @@ class DashboardGalleryViewModel( private fun detectUnfulfilledPurchase() { viewModelScope.launch(Dispatchers.IO) { + val enrolledCourses = + interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses iapInteractor.detectUnfulfilledPurchase( - onSuccess = { - eventLogger.logUnfulfilledPurchaseInitiatedEvent() + enrolledCourses = enrolledCourses, + verificationInitiated = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.logUnfulfilledPurchaseInitiatedEvent() + } + }, + onSuccess = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.upgradeSuccessEvent() + } _iapUiState.tryEmit(IAPUIState.PurchasesFulfillmentCompleted) }, onFailure = { diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt index bb54964ed..fd8028428 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt @@ -45,6 +45,9 @@ import org.openedx.core.system.notifier.PushNotifier import org.openedx.core.system.notifier.UpdateCourseData import org.openedx.core.system.notifier.app.AppNotifier import org.openedx.core.system.notifier.app.AppUpgradeEvent +import org.openedx.core.system.notifier.app.EnrolledCourseEvent +import org.openedx.core.system.notifier.app.RequestEnrolledCourseEvent +import org.openedx.dashboard.domain.CourseStatusFilter import org.openedx.dashboard.domain.interactor.DashboardInteractor @SuppressLint("StaticFieldLeak") @@ -124,7 +127,7 @@ class DashboardListViewModel( init { getCourses() - collectAppUpgradeEvent() + collectAppEvent() } fun getCourses() { @@ -292,14 +295,20 @@ class DashboardListViewModel( } } - private fun collectAppUpgradeEvent() { - viewModelScope.launch { - appNotifier.notifier.collect { event -> - if (event is AppUpgradeEvent) { - _appUpgradeEvent.value = event + private fun collectAppEvent() { + appNotifier.notifier + .onEach { + if (it is AppUpgradeEvent) { + _appUpgradeEvent.value = it + } + if (it is RequestEnrolledCourseEvent) { + val enrolledCourses = + interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses + appNotifier.send(EnrolledCourseEvent(enrolledCourses)) } } - } + .distinctUntilChanged() + .launchIn(viewModelScope) } fun dashboardCourseClickedEvent(courseId: String, courseName: String) { @@ -308,9 +317,21 @@ class DashboardListViewModel( private fun detectUnfulfilledPurchase() { viewModelScope.launch(Dispatchers.IO) { + val enrolledCourses = + interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses iapInteractor.detectUnfulfilledPurchase( - onSuccess = { - eventLogger.logUnfulfilledPurchaseInitiatedEvent() + enrolledCourses = enrolledCourses, + verificationInitiated = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.logUnfulfilledPurchaseInitiatedEvent() + } + }, + onSuccess = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.upgradeSuccessEvent() + } _iapUiState.tryEmit(IAPUIState.PurchasesFulfillmentCompleted) }, onFailure = { diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt index f4bc7b2c6..66cc55244 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt @@ -102,7 +102,7 @@ class SettingsFragment : Fragment() { } SettingsScreenAction.RestorePurchaseClick -> { - viewModel.restorePurchase() + viewModel.restorePurchasesClicked() } SettingsScreenAction.FeedbackFormClick -> { diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt index 78b7d7495..95b3770e5 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt @@ -21,6 +21,7 @@ import org.openedx.core.UIMessage import org.openedx.core.config.Config import org.openedx.core.data.storage.CorePreferences import org.openedx.core.domain.interactor.IAPInteractor +import org.openedx.core.domain.model.EnrolledCourse import org.openedx.core.exception.iap.IAPException import org.openedx.core.extension.isInternetError import org.openedx.core.module.DownloadWorkerController @@ -34,7 +35,9 @@ import org.openedx.core.system.AppCookieManager import org.openedx.core.system.ResourceManager import org.openedx.core.system.notifier.app.AppNotifier import org.openedx.core.system.notifier.app.AppUpgradeEvent +import org.openedx.core.system.notifier.app.EnrolledCourseEvent import org.openedx.core.system.notifier.app.LogoutEvent +import org.openedx.core.system.notifier.app.RequestEnrolledCourseEvent import org.openedx.core.utils.EmailUtil import org.openedx.profile.domain.interactor.ProfileInteractor import org.openedx.profile.domain.model.Configuration @@ -96,7 +99,7 @@ class SettingsViewModel( ) init { - collectAppUpgradeEvent() + collectAppEvent() collectProfileEvent() } @@ -128,12 +131,15 @@ class SettingsViewModel( } } - private fun collectAppUpgradeEvent() { + private fun collectAppEvent() { viewModelScope.launch { appNotifier.notifier.collect { event -> if (event is AppUpgradeEvent) { _appUpgradeEvent.value = event } + if (event is EnrolledCourseEvent) { + restorePurchase(event.enrolledCourses) + } } } } @@ -239,8 +245,14 @@ class SettingsViewModel( ) } - fun restorePurchase() { + fun restorePurchasesClicked() { eventLogger.logRestorePurchasesClickedEvent() + viewModelScope.launch { + appNotifier.send(RequestEnrolledCourseEvent) + } + } + + private fun restorePurchase(enrolledCourses: List) { viewModelScope.launch(Dispatchers.IO) { val userId = corePreferences.user?.id ?: return@launch @@ -249,12 +261,23 @@ class SettingsViewModel( delay(2000) runCatching { - iapInteractor.processUnfulfilledPurchase(userId) - }.onSuccess { - if (it) { - eventLogger.logUnfulfilledPurchaseInitiatedEvent() + iapInteractor.processUnfulfilledPurchase( + userId, + enrolledCourses, + verificationInitiated = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.logUnfulfilledPurchaseInitiatedEvent() + } + }) + }.onSuccess { purchaseFlowData -> + purchaseFlowData?.let { + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + this.upgradeSuccessEvent() + } _iapUiState.emit(IAPUIState.PurchasesFulfillmentCompleted) - } else { + } ?: run { _iapUiState.emit(IAPUIState.FakePurchasesFulfillmentCompleted) } }.onFailure {