Skip to content

Commit

Permalink
Implement restore purchase from Google Play
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandarIlic committed Nov 7, 2024
1 parent 1dc6f59 commit 7e2b4fd
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface PremiumBuyingContract {
val subscriptions: List<SubscriptionProduct> = emptyList(),
val stage: PremiumStage = PremiumStage.Home,
val primalName: String? = null,
val hasActiveSubscription: Boolean = false,

val profile: ProfileDetailsUi? = null,
val promoCodeValidity: Boolean? = null,
Expand All @@ -23,6 +24,8 @@ interface PremiumBuyingContract {
data class ApplyPromoCode(val promoCode: String) : UiEvent()
data object ClearPromoCodeValidity : UiEvent()

data object RestoreSubscription : UiEvent()

data class RequestPurchase(
val activity: Activity,
val subscriptionProduct: SubscriptionProduct,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import net.primal.android.profile.repository.ProfileRepository
import net.primal.android.user.accounts.active.ActiveAccountStore
import net.primal.android.wallet.store.PrimalBillingClient
import net.primal.android.wallet.store.domain.InAppPurchaseException
import net.primal.android.wallet.store.domain.SubscriptionPurchase
import timber.log.Timber

@HiltViewModel
Expand All @@ -31,6 +32,8 @@ class PremiumBuyingViewModel @Inject constructor(
private val activeAccountStore: ActiveAccountStore,
) : ViewModel() {

private var purchase: SubscriptionPurchase? = null

private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
private fun setState(reducer: UiState.() -> UiState) = _state.getAndUpdate { it.reducer() }
Expand All @@ -42,10 +45,10 @@ class PremiumBuyingViewModel @Inject constructor(
observeEvents()
observePurchases()
observeActiveProfile()
fetchSubscriptionProducts()
initBillingClient()
}

private fun fetchSubscriptionProducts() {
private fun initBillingClient() {
viewModelScope.launch {
if (isGoogleBuild()) {
val subscriptionProducts = primalBillingClient.querySubscriptionProducts()
Expand All @@ -54,6 +57,16 @@ class PremiumBuyingViewModel @Inject constructor(
premiumRepository.fetchMembershipProducts()
setState { copy(loading = false) }
}

fetchActiveSubscription()
}
}

private fun fetchActiveSubscription() {
viewModelScope.launch {
val purchases = primalBillingClient.queryActiveSubscriptions()
purchase = purchases.firstOrNull()
setState { copy(hasActiveSubscription = purchase != null) }
}
}

Expand All @@ -66,6 +79,7 @@ class PremiumBuyingViewModel @Inject constructor(
is UiEvent.ApplyPromoCode -> tryApplyPromoCode(it.promoCode)
UiEvent.ClearPromoCodeValidity -> setState { copy(promoCodeValidity = null) }
is UiEvent.RequestPurchase -> launchBillingFlow(it)
UiEvent.RestoreSubscription -> restorePurchase()
}
}
}
Expand Down Expand Up @@ -95,6 +109,8 @@ class PremiumBuyingViewModel @Inject constructor(
setState { copy(stage = PremiumBuyingContract.PremiumStage.Success) }
} catch (error: WssException) {
Timber.e(error)
this@PremiumBuyingViewModel.purchase = purchase
setState { copy(hasActiveSubscription = true) }
}
}
}
Expand All @@ -118,4 +134,22 @@ class PremiumBuyingViewModel @Inject constructor(
Timber.w(error)
}
}

private fun restorePurchase() =
viewModelScope.launch {
val primalName = _state.value.primalName
val existingPurchase = purchase
if (primalName != null && existingPurchase != null) {
try {
premiumRepository.purchaseMembership(
userId = activeAccountStore.activeUserId(),
primalName = primalName,
purchase = existingPurchase,
)
setState { copy(stage = PremiumBuyingContract.PremiumStage.Success) }
} catch (error: WssException) {
Timber.e(error)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
Expand Down Expand Up @@ -141,15 +140,19 @@ fun PremiumPurchaseStage(
if (activity != null) {
BuyPremiumButtons(
modifier = Modifier.padding(horizontal = 12.dp),
hasActiveSubscription = state.hasActiveSubscription,
subscriptions = state.subscriptions,
onClick = { subscription ->
onBuySubscription = { subscription ->
eventPublisher(
PremiumBuyingContract.UiEvent.RequestPurchase(
activity = activity,
subscriptionProduct = subscription,
),
)
},
onRestoreSubscription = {
eventPublisher(PremiumBuyingContract.UiEvent.RestoreSubscription)
},
)
TOSNotice()
}
Expand Down Expand Up @@ -190,21 +193,47 @@ private fun MoreInfoPromoCodeRow(

@Composable
fun BuyPremiumButtons(
modifier: Modifier = Modifier,
modifier: Modifier,
hasActiveSubscription: Boolean,
subscriptions: List<SubscriptionProduct>,
onClick: (SubscriptionProduct) -> Unit,
onBuySubscription: (SubscriptionProduct) -> Unit,
onRestoreSubscription: () -> Unit,
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
subscriptions.forEach {
BuyPremiumButton(
startText = it.toGetSubscriptionString(),
endText = it.toPricingString(),
onClick = { onClick(it) },
if (hasActiveSubscription) {
Text(
modifier = Modifier.padding(horizontal = 32.dp),
text = stringResource(R.string.premium_purchase_restore_subscription_explanation),
textAlign = TextAlign.Center,
style = AppTheme.typography.bodySmall,
color = AppTheme.extraColorScheme.onSurfaceVariantAlt2,
)

PrimalFilledButton(
modifier = Modifier.fillMaxWidth(),
height = 64.dp,
shape = RoundedCornerShape(percent = 100),
onClick = onRestoreSubscription,
) {
Text(
text = stringResource(R.string.premium_purchase_restore_subscription),
style = AppTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp,
)
}
} else {
subscriptions.forEach {
BuyPremiumButton(
startText = it.toGetSubscriptionString(),
endText = it.toPricingString(),
onClick = { onBuySubscription(it) },
)
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
<string name="premium_purchase_promo_code">Have a promo code?</string>
<string name="premium_purchase_get_annual_plan">Get Annual Plan</string>
<string name="premium_purchase_get_monthly_plan">Get Monthly Plan</string>
<string name="premium_purchase_restore_subscription">Restore Your Subscription</string>
<string name="premium_purchase_restore_subscription_explanation">Your Google Play account already has an active Primal Premium subscription, connected to a different Nostr account. You can restore it below:</string>
<string name="premium_purchase_tos_notice">By proceeding you acknowledge that\nyou agree to our </string>
<string name="premium_purchase_tos">Terms of Service</string>

Expand Down

0 comments on commit 7e2b4fd

Please sign in to comment.