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

Feature/8182 iap single subcription requirement #8206

Merged
merged 18 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d0474b4
Ensure user has no active IAP subscription
JorgeMucientes Jan 17, 2023
389cd22
Refactor error handling
JorgeMucientes Jan 17, 2023
b7aea27
Fix and add tests
JorgeMucientes Jan 17, 2023
bc1e515
Fix detekt indentation issues
JorgeMucientes Jan 17, 2023
583e447
Add missing assertion
JorgeMucientes Jan 18, 2023
00989fc
Update message for use case where user has an active IAP Woo subscrip…
JorgeMucientes Jan 19, 2023
ea8d561
Add new error handling for RemoteCommunication IAP errors
JorgeMucientes Jan 20, 2023
4485f34
Update FluxC version to include new CreateOrderErrorType network error
JorgeMucientes Jan 20, 2023
0fac0d0
Remove duplicated implementations of IAPMobilePayAPI
JorgeMucientes Jan 20, 2023
a061571
Merge branch 'trunk' into feature/8182-iap-single-subcription-require…
JorgeMucientes Jan 20, 2023
5b5d96a
Add curated message for cases where plan is purchased but not acknowl…
JorgeMucientes Jan 20, 2023
b131154
Fix detekt indentation issue
JorgeMucientes Jan 20, 2023
68ebb47
Fix and add unit tests
JorgeMucientes Jan 20, 2023
ea981c8
Merge branch 'trunk' into feature/8182-iap-single-subcription-require…
JorgeMucientes Jan 23, 2023
1d3c232
Add FluxC latest trunk changeset to fix compile issues
JorgeMucientes Jan 23, 2023
0ec9cb1
Merge branch 'trunk' into feature/8182-iap-single-subcription-require…
JorgeMucientes Feb 1, 2023
49b9389
Fix error mapping after bad merge
JorgeMucientes Feb 1, 2023
50e07db
Remove empty file after merge
JorgeMucientes Feb 1, 2023
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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import com.woocommerce.android.R
import com.woocommerce.android.iap.pub.IAPActivityWrapper
import com.woocommerce.android.iap.pub.IAPSitePurchasePlanFactory
import com.woocommerce.android.iapshowcase.IAPDebugLogWrapper
import com.woocommerce.android.ui.login.storecreation.iap.IapMobilePayApiProvider
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class IAPShowcasePurchaseFragment : Fragment(R.layout.fragment_iap_showcase_purchase) {
@Inject
lateinit var mobilePayAPIProvider: IAPShowcaseMobilePayAPIProvider
lateinit var mobilePayAPIProvider: IapMobilePayApiProvider

@Inject
lateinit var debugLogWrapper: IAPDebugLogWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.woocommerce.android.extensions.navigateSafely
import com.woocommerce.android.extensions.navigateToHelpScreen
import com.woocommerce.android.ui.base.BaseFragment
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
import com.woocommerce.android.ui.login.storecreation.iap.IapEligibilityViewModel.IapEligibilityEvent.NavigateToNextStep
Expand Down Expand Up @@ -49,6 +50,10 @@ class CheckIapEligibilityFragment : BaseFragment() {
is NavigateToNextStep -> navigateToStoreCreationNative()
is Exit -> findNavController().popBackStack()
is MultiLiveEvent.Event.ShowDialog -> event.showDialog()
is MultiLiveEvent.Event.NavigateToHelpScreen -> {
findNavController().popBackStack()
navigateToHelpScreen(event.origin)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package com.woocommerce.android.ui.login.storecreation.iap

import android.content.DialogInterface
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import com.woocommerce.android.R
import com.woocommerce.android.analytics.AnalyticsEvent
import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.iap.pub.PurchaseWPComPlanActions
import com.woocommerce.android.iap.pub.PurchaseWpComPlanSupportChecker
import com.woocommerce.android.iap.pub.model.IAPError
import com.woocommerce.android.iap.pub.model.IAPSupportedResult
import com.woocommerce.android.iap.pub.model.PurchaseStatus
import com.woocommerce.android.iap.pub.model.WPComIsPurchasedResult
import com.woocommerce.android.support.help.HelpOrigin.STORE_CREATION
import com.woocommerce.android.ui.login.storecreation.iap.IapEligibilityViewModel.IapEligibilityEvent.NavigateToNextStep
import com.woocommerce.android.viewmodel.MultiLiveEvent
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.NavigateToHelpScreen
import com.woocommerce.android.viewmodel.ScopedViewModel
import com.woocommerce.android.viewmodel.getStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -22,7 +30,8 @@ class IapEligibilityViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val planSupportChecker: PurchaseWpComPlanSupportChecker,
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper,
private val isIAPEnabled: IsIAPEnabled
private val isIAPEnabled: IsIAPEnabled,
private val iapManager: PurchaseWPComPlanActions
) : ScopedViewModel(savedStateHandle, planSupportChecker) {
private val _isCheckingIapEligibility = savedState.getStateFlow(scope = this, initialValue = true)
val isCheckingIapEligibility: LiveData<Boolean> = _isCheckingIapEligibility.asLiveData()
Expand All @@ -32,33 +41,46 @@ class IapEligibilityViewModel @Inject constructor(
launch {
when (val result = planSupportChecker.isIAPSupported()) {
is IAPSupportedResult.Success -> onSuccess(result)
is IAPSupportedResult.Error -> onError(result)
is IAPSupportedResult.Error -> onIAPError(result.errorType)
}
}
} else {
triggerEvent(NavigateToNextStep)
}
}

private fun onError(result: IAPSupportedResult.Error) {
analyticsTrackerWrapper.track(
AnalyticsEvent.SITE_CREATION_IAP_ELIGIBILITY_ERROR,
mapOf(AnalyticsTracker.KEY_ERROR_TYPE to result.errorType.toString())
private fun onUserNotEligibleForIAP(
@StringRes title: Int = R.string.store_creation_iap_eligibility_check_error_title,
@StringRes message: Int,
@StringRes positiveButtonId: Int? = null,
positiveBtnAction: DialogInterface.OnClickListener? = null,
) {
_isCheckingIapEligibility.value = false
triggerDialogError(
title = title,
message = message,
positiveButtonId = positiveButtonId,
positiveBtnAction = positiveBtnAction
)
onUserNotEligibleForIAP()
}

private fun onUserNotEligibleForIAP() {
_isCheckingIapEligibility.value = false
private fun triggerDialogError(
@StringRes title: Int,
@StringRes message: Int,
@StringRes positiveButtonId: Int? = null,
positiveBtnAction: DialogInterface.OnClickListener? = null,
) {
triggerEvent(
MultiLiveEvent.Event.ShowDialog(
titleId = R.string.store_creation_iap_eligibility_check_error_title,
messageId = R.string.store_creation_iap_eligibility_check_error_description,
titleId = title,
messageId = message,
positiveButtonId = positiveButtonId,
positiveBtnAction = positiveBtnAction,
negativeBtnAction = { dialog, _ ->
triggerEvent(MultiLiveEvent.Event.Exit)
dialog.dismiss()
},
negativeButtonId = R.string.close
negativeButtonId = R.string.close,
)
)
}
Expand All @@ -69,9 +91,60 @@ class IapEligibilityViewModel @Inject constructor(
mapOf(AnalyticsTracker.KEY_IAP_ELIGIBLE to result.isSupported)
)
when {
result.isSupported -> triggerEvent(NavigateToNextStep)
else -> onUserNotEligibleForIAP()
result.isSupported -> checkIfWooPlanAlreadyPurchased()
else -> onUserNotEligibleForIAP(
message = R.string.store_creation_iap_eligibility_check_error_not_available_for_country
)
}
}

private fun checkIfWooPlanAlreadyPurchased() {
launch {
when (val result = iapManager.isWPComPlanPurchased()) {
is WPComIsPurchasedResult.Success -> {
when (result.purchaseStatus) {
PurchaseStatus.PURCHASED -> {
onUserNotEligibleForIAP(
message = R.string.store_creation_iap_eligibility_existing_purchase_not_acknowledged,
positiveButtonId = R.string.support_contact,
positiveBtnAction = { dialog, _ ->
triggerEvent(NavigateToHelpScreen(STORE_CREATION))
dialog.dismiss()
}
)
}
PurchaseStatus.PURCHASED_AND_ACKNOWLEDGED -> onUserNotEligibleForIAP(
message = R.string.store_creation_iap_eligibility_check_error_existing_subscription
)
PurchaseStatus.NOT_PURCHASED -> triggerEvent(NavigateToNextStep)
}
}
is WPComIsPurchasedResult.Error -> onIAPError(result.errorType)
}
}
}

private fun onIAPError(error: IAPError) {
analyticsTrackerWrapper.track(
AnalyticsEvent.SITE_CREATION_IAP_ELIGIBILITY_ERROR,
mapOf(AnalyticsTracker.KEY_ERROR_TYPE to error.toString())
)
val message = when (error) {
is IAPError.Billing.DeveloperError,
is IAPError.Billing.ServiceDisconnected,
is IAPError.Billing.FeatureNotSupported,
is IAPError.Billing.BillingUnavailable,
is IAPError.Billing.Unknown,
is IAPError.Billing.ItemUnavailable,
is IAPError.Billing.ServiceTimeout,
is IAPError.Billing.ServiceUnavailable,
is IAPError.Billing.ItemNotOwned,
is IAPError.Billing.UserCancelled -> R.string.store_creation_iap_eligibility_check_generic_error
is IAPError.Billing.ItemAlreadyOwned ->
R.string.store_creation_iap_eligibility_check_error_existing_subscription
is IAPError.RemoteCommunication -> R.string.store_creation_iap_eligibility_network_error
}
onUserNotEligibleForIAP(message = message)
}

sealed class IapEligibilityEvent : MultiLiveEvent.Event() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ import org.wordpress.android.fluxc.network.rest.wpcom.mobilepay.MobilePayRestCli
import org.wordpress.android.fluxc.store.MobilePayStore
import javax.inject.Inject

/**
* This class provides a default implementation of IAPMobilePayAPI for store creation flow. It duplicates
* code from IAPShowcaseMobilePayAPIProvider. We should double check if we need this duplication to remove
* one of the files and move the other to the correct package.
* Github issue: https://github.com/woocommerce/woocommerce-android/issues/8148
*/
class IapMobilePayApiProvider @Inject constructor(private val mobilePayStore: MobilePayStore) {
fun buildMobilePayAPI(customUrl: String?) = object : IAPMobilePayAPI {
override suspend fun createAndConfirmOrder(
Expand Down Expand Up @@ -40,10 +34,10 @@ class IapMobilePayApiProvider @Inject constructor(private val mobilePayStore: Mo
MobilePayRestClient.CreateOrderErrorType.API_ERROR,
MobilePayRestClient.CreateOrderErrorType.AUTH_ERROR,
MobilePayRestClient.CreateOrderErrorType.GENERIC_ERROR,
MobilePayRestClient.CreateOrderErrorType.NETWORK_ERROR,
MobilePayRestClient.CreateOrderErrorType.INVALID_RESPONSE ->
CreateAndConfirmOrderResponse.Server(response.message ?: "Reason is not provided")
MobilePayRestClient.CreateOrderErrorType.TIMEOUT -> CreateAndConfirmOrderResponse.Network
MobilePayRestClient.CreateOrderErrorType.TIMEOUT,
MobilePayRestClient.CreateOrderErrorType.NETWORK_ERROR -> CreateAndConfirmOrderResponse.Network
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2817,7 +2817,11 @@
<string name="store_creation_iap_eligibility_loading_title">We are getting ready for your store creation</string>
<string name="store_creation_iap_eligibility_loading_subtitle">Please remain connected.</string>
<string name="store_creation_iap_eligibility_check_error_title">Store Creation Unavailable </string>
<string name="store_creation_iap_eligibility_check_error_description">Store creation from the mobile app is currently unavailable for your country.</string>
<string name="store_creation_iap_eligibility_check_error_not_available_for_country">Store creation from the mobile app is currently unavailable for your country.</string>
<string name="store_creation_iap_eligibility_check_error_existing_subscription">You can only purchase the eCommerce plan from the mobile app once. To create additional sites visit our website.</string>
<string name="store_creation_iap_eligibility_check_generic_error">Store creation is currently unavailable. Please, try again later.</string>
<string name="store_creation_iap_eligibility_network_error">A network error occurred. Please check your connection and try again.</string>
<string name="store_creation_iap_eligibility_existing_purchase_not_acknowledged">You already have an active eCommerce subscription, please contact support if you are experiencing issues with your current subscription not linked to your new site.</string>
<string name="store_creation_create_new_store_label">Create a new store</string>
<string name="store_creation_cannot_load_store">Couldn\'t load your store.\nPlease try again…</string>
<string name="store_creation_in_progress">Creating your store…</string>
Expand Down
Loading