Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add missing course properties in IAP restore & unfulfilled events #82

Open
wants to merge 2 commits into
base: 2U/develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
windowSize,
get(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ 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.extension.decodeToString
import org.openedx.core.module.billing.BillingProcessor
import org.openedx.core.module.billing.getCourseSku
import org.openedx.core.module.billing.getPriceAmount
Expand Down Expand Up @@ -109,12 +112,42 @@ class IAPInteractor(
}
}

suspend fun processUnfulfilledPurchase(userId: Long): Boolean {
suspend fun processUnfulfilledPurchase(
userId: Long,
enrolledCourses: List<EnrolledCourse>,
purchaseVerified: (PurchaseFlowData) -> Unit = {},
): Boolean {
val purchases = billingProcessor.queryPurchases()
val userPurchases =
purchases.filter { it.accountIdentifiers?.obfuscatedAccountId?.decodeToLong() == userId }
val userPurchases = purchases.filter { purchase ->
val userAccountId = purchase.accountIdentifiers?.obfuscatedAccountId?.decodeToLong()
val storeSku = purchase.accountIdentifiers?.obfuscatedProfileId?.decodeToString()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use Purchase.getCourseSku() extension function here.


userAccountId == userId && enrolledCourses.any { enrolledCourse ->
storeSku == enrolledCourse.productInfo?.courseSku
}
}
if (userPurchases.isNotEmpty()) {
startUnfulfilledVerification(userPurchases)
userPurchases.forEach { purchase ->
val courseVerified = enrolledCourses.find { enrolledCourse ->
enrolledCourse.productInfo?.courseSku == purchase.getCourseSku()
}
Comment on lines +131 to +133

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is same as above.

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
}
}
startUnfulfilledVerification(purchase)
purchaseVerified(purchaseProductFlow)
}
}
return true
} else {
purchases.forEach {
Expand All @@ -124,34 +157,34 @@ class IAPInteractor(
return false
}

private suspend fun startUnfulfilledVerification(userPurchases: List<Purchase>) {
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(
enrolledCourses: List<EnrolledCourse>,
purchaseVerified: (PurchaseFlowData) -> Unit,
onSuccess: () -> Unit,
onFailure: (IAPException) -> Unit,
) {
if (isIAPEnabled) {
preferencesManager.user?.id?.let { userId ->
runCatching {
processUnfulfilledPurchase(userId)
processUnfulfilledPurchase(userId, enrolledCourses, purchaseVerified)
}.onSuccess {
if (it) {
onSuccess()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 purchaseFlowData: PurchaseFlowData? = null,
) {
fun upgradeNowClickedEvent() {
logIAPEvent(IAPAnalyticsEvent.IAP_UPGRADE_NOW_CLICKED)
Expand Down Expand Up @@ -64,7 +64,7 @@ class IAPEventLogger(

IAPRequestType.PRICE_CODE,
IAPRequestType.NO_SKU_CODE,
-> {
-> {
priceLoadErrorEvent(feedbackErrorMessage)
}

Expand Down Expand Up @@ -186,7 +186,7 @@ class IAPEventLogger(
putAll(params)
putAll(getIAPEventParams())
putAll(getUnfulfilledIAPEventParams())
},
}
)
}
}
Original file line number Diff line number Diff line change
@@ -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<AppEvent>(replay = 0, extraBufferCapacity = 0)
private val channel = MutableSharedFlow<AppEvent>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)

val notifier: Flow<AppEvent> = channel.asSharedFlow()

Expand All @@ -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)
Comment on lines +24 to +26

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should rename them and place them in IAPNotifier.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.openedx.core.system.notifier.app

import org.openedx.core.domain.model.EnrolledCourse

class EnrolledCourseEvent(val enrolledCourses: List<EnrolledCourse>,) : AppEvent
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.openedx.core.system.notifier.app

object RequestEnrolledCourseEvent : AppEvent
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -76,7 +81,7 @@ class DashboardGalleryViewModel(
val uiMessage: SharedFlow<UIMessage?>
get() = _uiMessage.asSharedFlow()

private val _updating = MutableStateFlow<Boolean>(false)
private val _updating = MutableStateFlow(false)
val updating: StateFlow<Boolean>
get() = _updating.asStateFlow()

Expand All @@ -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 {
Expand Down Expand Up @@ -258,9 +277,17 @@ class DashboardGalleryViewModel(

private fun detectUnfulfilledPurchase() {
viewModelScope.launch(Dispatchers.IO) {
val enrolledCourses =
interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses
iapInteractor.detectUnfulfilledPurchase(
enrolledCourses = enrolledCourses,
purchaseVerified = { purchaseFlowData ->
eventLogger.apply {
this.purchaseFlowData = purchaseFlowData
this.logUnfulfilledPurchaseInitiatedEvent()
}
},
onSuccess = {
eventLogger.logUnfulfilledPurchaseInitiatedEvent()
_iapUiState.tryEmit(IAPUIState.PurchasesFulfillmentCompleted)
},
onFailure = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -124,7 +127,7 @@ class DashboardListViewModel(

init {
getCourses()
collectAppUpgradeEvent()
collectAppEvent()
}

fun getCourses() {
Expand Down Expand Up @@ -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) {
Expand All @@ -308,9 +317,17 @@ class DashboardListViewModel(

private fun detectUnfulfilledPurchase() {
viewModelScope.launch(Dispatchers.IO) {
val enrolledCourses =
interactor.getAllUserCourses(status = CourseStatusFilter.ALL).courses
iapInteractor.detectUnfulfilledPurchase(
enrolledCourses = enrolledCourses,
purchaseVerified = { purchaseFlowData ->
eventLogger.apply {
this.purchaseFlowData = purchaseFlowData
this.logUnfulfilledPurchaseInitiatedEvent()
}
},
onSuccess = {
eventLogger.logUnfulfilledPurchaseInitiatedEvent()
_iapUiState.tryEmit(IAPUIState.PurchasesFulfillmentCompleted)
},
onFailure = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class SettingsFragment : Fragment() {
}

SettingsScreenAction.RestorePurchaseClick -> {
viewModel.restorePurchase()
viewModel.restorePurchasesClicked()
}

SettingsScreenAction.FeedbackFormClick -> {
Expand Down
Loading
Loading