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 f23c46cb2..e2eef51f0 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 @@ -14,14 +14,14 @@ 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.extension.decodeToString 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, @@ -115,19 +115,19 @@ class IAPInteractor( suspend fun processUnfulfilledPurchase( userId: Long, enrolledCourses: List, - purchaseVerified: (PurchaseFlowData) -> Unit = {}, - ): Boolean { + verificationInitiated: (PurchaseFlowData) -> Unit = {}, + ): PurchaseFlowData? { val purchases = billingProcessor.queryPurchases() val userPurchases = purchases.filter { purchase -> - val userAccountId = purchase.accountIdentifiers?.obfuscatedAccountId?.decodeToLong() - val storeSku = purchase.accountIdentifiers?.obfuscatedProfileId?.decodeToString() + val userAccountId = purchase.getUserId() + val courseSku = purchase.getCourseSku() userAccountId == userId && enrolledCourses.any { enrolledCourse -> - storeSku == enrolledCourse.productInfo?.courseSku + courseSku == enrolledCourse.productInfo?.courseSku } } if (userPurchases.isNotEmpty()) { - userPurchases.forEach { purchase -> + userPurchases[0].let { purchase -> val courseVerified = enrolledCourses.find { enrolledCourse -> enrolledCourse.productInfo?.courseSku == purchase.getCourseSku() } @@ -143,18 +143,19 @@ class IAPInteractor( this.price = it.getPriceAmount() this.currencyCode = it.priceCurrencyCode } + this.flowStartTime = TimeUtils.getCurrentTime() } + verificationInitiated(purchaseProductFlow) startUnfulfilledVerification(purchase) - purchaseVerified(purchaseProductFlow) + return purchaseProductFlow } } - return true } else { - purchases.forEach { + purchases.subtract(userPurchases.toSet()).forEach { billingProcessor.consumePurchase(it.purchaseToken) } } - return false + return null } private suspend fun startUnfulfilledVerification(userPurchase: Purchase) { @@ -177,17 +178,17 @@ class IAPInteractor( suspend fun detectUnfulfilledPurchase( enrolledCourses: List, - purchaseVerified: (PurchaseFlowData) -> Unit, - onSuccess: () -> Unit, + verificationInitiated: (PurchaseFlowData) -> Unit, + onSuccess: (PurchaseFlowData) -> Unit, onFailure: (IAPException) -> Unit, ) { if (isIAPEnabled) { preferencesManager.user?.id?.let { userId -> runCatching { - processUnfulfilledPurchase(userId, enrolledCourses, purchaseVerified) - }.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/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 a296f3bdc..6947ccf85 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,7 +15,7 @@ import org.openedx.core.utils.TimeUtils class IAPEventLogger( private val analytics: IAPAnalytics, - private val isSilentIAPFlow: Boolean? = null, + val isSilentIAPFlow: Boolean? = null, var purchaseFlowData: PurchaseFlowData? = null, ) { fun upgradeNowClickedEvent() { 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..7561475b2 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 @@ -84,7 +84,9 @@ class IAPViewModel( iapNotifier.notifier.onEach { event -> when (event) { is CourseDataUpdated -> { - eventLogger.upgradeSuccessEvent() + if (eventLogger.isSilentIAPFlow == null) { + eventLogger.upgradeSuccessEvent() + } _uiMessage.emit(UIMessage.ToastMessage(resourceManager.getString(R.string.iap_success_message))) _uiState.value = IAPUIState.CourseDataUpdated } 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..f90966ca2 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 @@ -286,7 +286,6 @@ class CourseContainerViewModel( } if (isIAPFlow) { if (isExpiredCoursePurchase) { - eventLogger.upgradeSuccessEvent() _uiMessage.emit( UIMessage.ToastMessage( resourceManager.getString(CoreR.string.iap_success_message) 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 5bb2e551e..10c259d38 100644 --- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt +++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt @@ -281,13 +281,17 @@ class DashboardGalleryViewModel( interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses iapInteractor.detectUnfulfilledPurchase( enrolledCourses = enrolledCourses, - purchaseVerified = { purchaseFlowData -> + verificationInitiated = { purchaseFlowData -> eventLogger.apply { this.purchaseFlowData = purchaseFlowData this.logUnfulfilledPurchaseInitiatedEvent() } }, - onSuccess = { + onSuccess = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + eventLogger.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 6dfa68613..8966913cc 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt @@ -321,13 +321,17 @@ class DashboardListViewModel( interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses iapInteractor.detectUnfulfilledPurchase( enrolledCourses = enrolledCourses, - purchaseVerified = { purchaseFlowData -> + verificationInitiated = { purchaseFlowData -> eventLogger.apply { this.purchaseFlowData = purchaseFlowData this.logUnfulfilledPurchaseInitiatedEvent() } }, - onSuccess = { + onSuccess = { purchaseFlowData -> + eventLogger.apply { + this.purchaseFlowData = purchaseFlowData + eventLogger.upgradeSuccessEvent() + } _iapUiState.tryEmit(IAPUIState.PurchasesFulfillmentCompleted) }, onFailure = { 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 ad1ac0a34..d22664403 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 @@ -264,16 +264,16 @@ class SettingsViewModel( iapInteractor.processUnfulfilledPurchase( userId, enrolledCourses, - purchaseVerified = { purchaseFlowData -> + verificationInitiated = { purchaseFlowData -> eventLogger.apply { this.purchaseFlowData = purchaseFlowData this.logUnfulfilledPurchaseInitiatedEvent() } }) - }.onSuccess { - if (it) { + }.onSuccess { purchaseFlowData -> + purchaseFlowData?.let { _iapUiState.emit(IAPUIState.PurchasesFulfillmentCompleted) - } else { + } ?: run { _iapUiState.emit(IAPUIState.FakePurchasesFulfillmentCompleted) } }.onFailure {