Replace monthly badge expires with cancellation dialogs.
This commit is contained in:
parent
62bf5abd8d
commit
df4bd1fa4a
16 changed files with 384 additions and 301 deletions
|
@ -1,52 +0,0 @@
|
|||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.models.SplashImage
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
|
||||
class CantProcessSubscriptionPaymentBottomSheetDialogFragment : DSLSettingsBottomSheetFragment() {
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
SplashImage.register(adapter)
|
||||
adapter.submitList(getConfiguration().toMappingModelList())
|
||||
}
|
||||
|
||||
private fun getConfiguration(): DSLConfiguration {
|
||||
return configure {
|
||||
customPref(SplashImage.Model(R.drawable.ic_card_process))
|
||||
|
||||
sectionHeaderPref(
|
||||
title = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__cant_process_subscription_payment, DSLSettingsText.CenterModifier)
|
||||
)
|
||||
|
||||
textPref(
|
||||
summary = DSLSettingsText.from(
|
||||
requireContext().getString(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__were_having_trouble),
|
||||
DSLSettingsText.LearnMoreModifier(ContextCompat.getColor(requireContext(), R.color.signal_accent_primary)) {
|
||||
CommunicationActions.openBrowserLink(requireContext(), requireContext().getString(R.string.donation_decline_code_error_url))
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
primaryButton(
|
||||
text = DSLSettingsText.from(android.R.string.ok)
|
||||
) {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
secondaryButtonNoOutline(
|
||||
text = DSLSettingsText.from(R.string.CantProcessSubscriptionPaymentBottomSheetDialogFragment__dont_show_this_again)
|
||||
) {
|
||||
SignalStore.donationsValues().showCantProcessDialog = false
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.badges.models.ExpiredBadge
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.mapToErrorStringResource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.shouldRouteToGooglePay
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
|
||||
/**
|
||||
* Bottom sheet displaying a fading badge with a notice and action for becoming a subscriber again.
|
||||
*/
|
||||
class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
peekHeightPercentage = 1f
|
||||
) {
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
ExpiredBadge.register(adapter)
|
||||
|
||||
adapter.submitList(getConfiguration().toMappingModelList())
|
||||
}
|
||||
|
||||
private fun getConfiguration(): DSLConfiguration {
|
||||
val args = ExpiredBadgeBottomSheetDialogFragmentArgs.fromBundle(requireArguments())
|
||||
val badge: Badge = args.badge
|
||||
val cancellationReason = UnexpectedSubscriptionCancellation.fromStatus(args.cancelationReason)
|
||||
val declineCode: StripeDeclineCode? = args.chargeFailure?.let { StripeDeclineCode.getFromCode(it) }
|
||||
val failureCode: StripeFailureCode? = args.chargeFailure?.let { StripeFailureCode.getFromCode(it) }
|
||||
val isLikelyASustainer = SignalStore.donationsValues().isLikelyASustainer()
|
||||
val inactive = cancellationReason == UnexpectedSubscriptionCancellation.INACTIVE
|
||||
|
||||
Log.d(TAG, "Displaying Expired Badge Fragment with bundle: ${requireArguments()}", true)
|
||||
|
||||
return configure {
|
||||
customPref(ExpiredBadge.Model(badge))
|
||||
|
||||
sectionHeaderPref(
|
||||
DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__monthly_donation_cancelled
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(4f).toInt())
|
||||
|
||||
noPadTextPref(
|
||||
DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired_and)
|
||||
} else if (declineCode != null) {
|
||||
getString(
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled_s,
|
||||
getString(declineCode.mapToErrorStringResource()),
|
||||
badge.name
|
||||
)
|
||||
} else if (failureCode != null) {
|
||||
getString(
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled_s,
|
||||
getString(failureCode.mapToErrorStringResource()),
|
||||
badge.name
|
||||
)
|
||||
} else if (inactive) {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
|
||||
} else {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled)
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(16f).toInt())
|
||||
|
||||
if (badge.isSubscription() && declineCode?.shouldRouteToGooglePay() == true) {
|
||||
space(DimensionUnit.DP.toPixels(68f).toInt())
|
||||
|
||||
secondaryButtonNoOutline(
|
||||
text = DSLSettingsText.from(R.string.ExpiredBadgeBottomSheetDialogFragment__go_to_google_pay),
|
||||
onClick = {
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.google_pay_url))
|
||||
}
|
||||
)
|
||||
} else {
|
||||
noPadTextPref(
|
||||
DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
if (isLikelyASustainer) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_keep
|
||||
}
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(92f).toInt())
|
||||
}
|
||||
|
||||
primaryButton(
|
||||
text = DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
if (isLikelyASustainer) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__add_a_boost
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__become_a_sustainer
|
||||
}
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__renew_subscription
|
||||
}
|
||||
),
|
||||
onClick = {
|
||||
dismiss()
|
||||
if (isLikelyASustainer) {
|
||||
requireActivity().startActivity(AppSettingsActivity.boost(requireContext()))
|
||||
} else {
|
||||
requireActivity().startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
secondaryButtonNoOutline(
|
||||
text = DSLSettingsText.from(R.string.ExpiredBadgeBottomSheetDialogFragment__not_now),
|
||||
onClick = {
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ExpiredBadgeBottomSheetDialogFragment::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun show(
|
||||
badge: Badge,
|
||||
cancellationReason: UnexpectedSubscriptionCancellation?,
|
||||
chargeFailure: ActiveSubscription.ChargeFailure?,
|
||||
fragmentManager: FragmentManager
|
||||
) {
|
||||
val args = ExpiredBadgeBottomSheetDialogFragmentArgs.Builder(badge, cancellationReason?.status, chargeFailure?.code).build()
|
||||
val fragment = ExpiredBadgeBottomSheetDialogFragment()
|
||||
fragment.arguments = args.toBundle()
|
||||
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.badges.models.ExpiredBadge
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
|
||||
/**
|
||||
* Bottom sheet displaying a fading badge with a notice and action for becoming a subscriber again.
|
||||
*/
|
||||
class ExpiredOneTimeBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
peekHeightPercentage = 1f
|
||||
) {
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
ExpiredBadge.register(adapter)
|
||||
|
||||
adapter.submitList(getConfiguration().toMappingModelList())
|
||||
}
|
||||
|
||||
private fun getConfiguration(): DSLConfiguration {
|
||||
val args = ExpiredOneTimeBadgeBottomSheetDialogFragmentArgs.fromBundle(requireArguments())
|
||||
val badge: Badge = args.badge
|
||||
val isLikelyASustainer = SignalStore.donationsValues().isLikelyASustainer()
|
||||
|
||||
Log.d(TAG, "Displaying Expired Badge Fragment with bundle: ${requireArguments()}", true)
|
||||
|
||||
return configure {
|
||||
customPref(ExpiredBadge.Model(badge))
|
||||
|
||||
sectionHeaderPref(
|
||||
DSLSettingsText.from(
|
||||
if (badge.isBoost()) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__monthly_donation_cancelled
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(4f).toInt())
|
||||
|
||||
noPadTextPref(
|
||||
DSLSettingsText.from(
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_boost_badge_has_expired_and),
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(16f).toInt())
|
||||
|
||||
noPadTextPref(
|
||||
DSLSettingsText.from(
|
||||
if (isLikelyASustainer) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_reactivate
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__you_can_keep
|
||||
},
|
||||
DSLSettingsText.CenterModifier
|
||||
)
|
||||
)
|
||||
|
||||
space(DimensionUnit.DP.toPixels(92f).toInt())
|
||||
|
||||
primaryButton(
|
||||
text = DSLSettingsText.from(
|
||||
if (isLikelyASustainer) {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__add_a_boost
|
||||
} else {
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__become_a_sustainer
|
||||
}
|
||||
),
|
||||
onClick = {
|
||||
dismiss()
|
||||
if (isLikelyASustainer) {
|
||||
requireActivity().startActivity(AppSettingsActivity.boost(requireContext()))
|
||||
} else {
|
||||
requireActivity().startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
secondaryButtonNoOutline(
|
||||
text = DSLSettingsText.from(R.string.ExpiredBadgeBottomSheetDialogFragment__not_now),
|
||||
onClick = {
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ExpiredOneTimeBadgeBottomSheetDialogFragment::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun show(
|
||||
badge: Badge,
|
||||
cancellationReason: UnexpectedSubscriptionCancellation?,
|
||||
chargeFailure: ActiveSubscription.ChargeFailure?,
|
||||
fragmentManager: FragmentManager
|
||||
) {
|
||||
val args = ExpiredOneTimeBadgeBottomSheetDialogFragmentArgs.Builder(badge, cancellationReason?.status, chargeFailure?.code).build()
|
||||
val fragment = ExpiredOneTimeBadgeBottomSheetDialogFragment()
|
||||
fragment.arguments = args.toBundle()
|
||||
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Texts
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImage112
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.mapToErrorStringResource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.manage.ManageDonationsFragment
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
|
||||
class MonthlyDonationCanceledBottomSheetDialogFragment : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val chargeFailure: ActiveSubscription.ChargeFailure? = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure()
|
||||
val declineCode: StripeDeclineCode = StripeDeclineCode.getFromCode(chargeFailure?.outcomeNetworkReason)
|
||||
val failureCode: StripeFailureCode = StripeFailureCode.getFromCode(chargeFailure?.code)
|
||||
|
||||
val errorMessage = if (declineCode.isKnown()) {
|
||||
declineCode.mapToErrorStringResource()
|
||||
} else if (failureCode.isKnown) {
|
||||
failureCode.mapToErrorStringResource()
|
||||
} else {
|
||||
declineCode.mapToErrorStringResource()
|
||||
}
|
||||
|
||||
MonthlyDonationCanceled(
|
||||
badge = SignalStore.donationsValues().getExpiredBadge(),
|
||||
errorMessageRes = errorMessage,
|
||||
onRenewClicked = {
|
||||
startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
||||
dismissAllowingStateLoss()
|
||||
},
|
||||
onNotNowClicked = {
|
||||
SignalStore.donationsValues().showMonthlyDonationCanceledDialog = false
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
val fragment = MonthlyDonationCanceledBottomSheetDialogFragment()
|
||||
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "ShortName", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "ShortName", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun MonthlyDonationCanceledPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
MonthlyDonationCanceled(
|
||||
badge = Badge(
|
||||
id = "",
|
||||
category = Badge.Category.Donor,
|
||||
name = "Signal Star",
|
||||
description = "",
|
||||
imageUrl = Uri.EMPTY,
|
||||
imageDensity = "",
|
||||
expirationTimestamp = 0L,
|
||||
visible = true,
|
||||
duration = 0L
|
||||
),
|
||||
errorMessageRes = R.string.StripeFailureCode__verify_your_bank_details_are_correct,
|
||||
onRenewClicked = {},
|
||||
onNotNowClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MonthlyDonationCanceled(
|
||||
badge: Badge?,
|
||||
@StringRes errorMessageRes: Int,
|
||||
onRenewClicked: () -> Unit,
|
||||
onNotNowClicked: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 34.dp)
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
|
||||
if (badge != null) {
|
||||
Box(modifier = Modifier.padding(top = 21.dp, bottom = 16.dp)) {
|
||||
BadgeImage112(
|
||||
badge = badge,
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_error_circle_fill_24),
|
||||
contentScale = ContentScale.Inside,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
.background(
|
||||
color = SignalTheme.colors.colorSurface1,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.MonthlyDonationCanceled__title),
|
||||
style = MaterialTheme.typography.titleLarge.copy(textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurface),
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val learnMore = stringResource(id = R.string.MonthlyDonationCanceled__learn_more)
|
||||
val errorMessage = stringResource(id = errorMessageRes)
|
||||
val fullString = stringResource(id = R.string.MonthlyDonationCanceled__message, errorMessage, learnMore)
|
||||
val spanned = SpanUtil.urlSubsequence(fullString, learnMore, ManageDonationsFragment.DONATE_TROUBLESHOOTING_URL)
|
||||
Texts.LinkifiedText(
|
||||
textWithUrlSpans = spanned,
|
||||
onUrlClick = { CommunicationActions.openBrowserLink(context, it) },
|
||||
style = MaterialTheme.typography.bodyLarge.copy(textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onSurfaceVariant),
|
||||
modifier = Modifier.padding(bottom = 36.dp)
|
||||
)
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onRenewClicked,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minWidth = 220.dp)
|
||||
.padding(bottom = 16.dp)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.MonthlyDonationCanceled__renew_button))
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = onNotNowClicked,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minWidth = 220.dp)
|
||||
.padding(bottom = 56.dp)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.MonthlyDonationCanceled__not_now_button))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -144,19 +144,21 @@ class InternalDonorErrorConfigurationViewModel : ViewModel() {
|
|||
|
||||
private fun handleSubscriptionExpiration(state: InternalDonorErrorConfigurationState) {
|
||||
SignalStore.donationsValues().setExpiredBadge(state.selectedBadge)
|
||||
SignalStore.donationsValues().clearUserManuallyCancelled()
|
||||
handleSubscriptionPaymentFailure(state)
|
||||
}
|
||||
|
||||
private fun handleSubscriptionPaymentFailure(state: InternalDonorErrorConfigurationState) {
|
||||
SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = state.selectedUnexpectedSubscriptionCancellation?.status
|
||||
SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = System.currentTimeMillis()
|
||||
SignalStore.donationsValues().showMonthlyDonationCanceledDialog = true
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(
|
||||
state.selectedStripeDeclineCode?.let {
|
||||
ActiveSubscription.ChargeFailure(
|
||||
it.code,
|
||||
"Test Charge Failure",
|
||||
"Test Network Status",
|
||||
"Test Network Reason",
|
||||
it.code,
|
||||
"Test"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,17 +31,17 @@ fun StripeFailureCode.mapToErrorStringResource(): Int {
|
|||
fun StripeDeclineCode.mapToErrorStringResource(): Int {
|
||||
return when (this) {
|
||||
is StripeDeclineCode.Known -> when (this.code) {
|
||||
StripeDeclineCode.Code.APPROVE_WITH_ID -> R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again
|
||||
StripeDeclineCode.Code.CALL_ISSUER -> R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again_if_the_problem
|
||||
StripeDeclineCode.Code.APPROVE_WITH_ID -> R.string.DeclineCode__verify_your_card_details_are_correct_and_try_again
|
||||
StripeDeclineCode.Code.CALL_ISSUER -> R.string.DeclineCode__verify_your_card_details_are_correct_and_try_again_if_the_problem_continues
|
||||
StripeDeclineCode.Code.CARD_NOT_SUPPORTED -> R.string.DeclineCode__your_card_does_not_support_this_type_of_purchase
|
||||
StripeDeclineCode.Code.EXPIRED_CARD -> R.string.DeclineCode__your_card_has_expired
|
||||
StripeDeclineCode.Code.INCORRECT_NUMBER -> R.string.DeclineCode__your_card_number_is_incorrect
|
||||
StripeDeclineCode.Code.INCORRECT_CVC -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect
|
||||
StripeDeclineCode.Code.EXPIRED_CARD -> R.string.DeclineCode__your_card_has_expired_verify_your_card_details
|
||||
StripeDeclineCode.Code.INCORRECT_NUMBER -> R.string.DeclineCode__your_card_number_is_incorrect_verify_your_card_details
|
||||
StripeDeclineCode.Code.INCORRECT_CVC -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect_verify_your_card_details
|
||||
StripeDeclineCode.Code.INSUFFICIENT_FUNDS -> R.string.DeclineCode__your_card_does_not_have_sufficient_funds
|
||||
StripeDeclineCode.Code.INVALID_CVC -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> R.string.DeclineCode__the_expiration_month
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> R.string.DeclineCode__the_expiration_year
|
||||
StripeDeclineCode.Code.INVALID_NUMBER -> R.string.DeclineCode__your_card_number_is_incorrect
|
||||
StripeDeclineCode.Code.INVALID_CVC -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect_verify_your_card_details
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> R.string.DeclineCode__the_expiration_month_on_your_card_is_incorrect
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> R.string.DeclineCode__the_expiration_year_on_your_card_is_incorrect
|
||||
StripeDeclineCode.Code.INVALID_NUMBER -> R.string.DeclineCode__your_card_number_is_incorrect_verify_your_card_details
|
||||
StripeDeclineCode.Code.ISSUER_NOT_AVAILABLE -> R.string.DeclineCode__try_completing_the_payment_again
|
||||
StripeDeclineCode.Code.PROCESSING_ERROR -> R.string.DeclineCode__try_again
|
||||
StripeDeclineCode.Code.REENTER_TRANSACTION -> R.string.DeclineCode__try_again
|
||||
|
@ -50,26 +50,3 @@ fun StripeDeclineCode.mapToErrorStringResource(): Int {
|
|||
else -> R.string.DeclineCode__try_another_payment_method_or_contact_your_bank
|
||||
}
|
||||
}
|
||||
|
||||
fun StripeDeclineCode.shouldRouteToGooglePay(): Boolean {
|
||||
return when (this) {
|
||||
is StripeDeclineCode.Known -> when (this.code) {
|
||||
StripeDeclineCode.Code.APPROVE_WITH_ID -> true
|
||||
StripeDeclineCode.Code.CALL_ISSUER -> true
|
||||
StripeDeclineCode.Code.CARD_NOT_SUPPORTED -> false
|
||||
StripeDeclineCode.Code.EXPIRED_CARD -> true
|
||||
StripeDeclineCode.Code.INCORRECT_NUMBER -> true
|
||||
StripeDeclineCode.Code.INCORRECT_CVC -> true
|
||||
StripeDeclineCode.Code.INSUFFICIENT_FUNDS -> false
|
||||
StripeDeclineCode.Code.INVALID_CVC -> true
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> true
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> true
|
||||
StripeDeclineCode.Code.INVALID_NUMBER -> true
|
||||
StripeDeclineCode.Code.ISSUER_NOT_AVAILABLE -> false
|
||||
StripeDeclineCode.Code.PROCESSING_ERROR -> false
|
||||
StripeDeclineCode.Code.REENTER_TRANSACTION -> false
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class ManageDonationsFragment :
|
|||
|
||||
companion object {
|
||||
private val alertedIdealDonations = mutableSetOf<Long>()
|
||||
private const val DONATE_TROUBLESHOOTING_URL = "https://support.signal.org/hc/articles/360031949872#fix"
|
||||
const val DONATE_TROUBLESHOOTING_URL = "https://support.signal.org/hc/articles/360031949872#fix"
|
||||
}
|
||||
|
||||
private val supportTechSummary: CharSequence by lazy {
|
||||
|
|
|
@ -27,7 +27,7 @@ data class ManageDonationsState(
|
|||
|
||||
private fun getStateFromActiveSubscription(activeSubscription: ActiveSubscription): RedemptionState? {
|
||||
return when {
|
||||
activeSubscription.isFailedPayment -> RedemptionState.FAILED
|
||||
activeSubscription.isFailedPayment && !activeSubscription.isPastDue -> RedemptionState.FAILED
|
||||
activeSubscription.isPendingBankTransfer -> RedemptionState.IS_PENDING_BANK_TRANSFER
|
||||
activeSubscription.isInProgress -> RedemptionState.IN_PROGRESS
|
||||
else -> null
|
||||
|
|
|
@ -90,8 +90,8 @@ import org.thoughtcrime.securesms.MuteDialog;
|
|||
import org.thoughtcrime.securesms.NewConversationActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.badges.self.expired.CantProcessSubscriptionPaymentBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.badges.self.expired.ExpiredBadgeBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.badges.self.expired.MonthlyDonationCanceledBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.badges.self.expired.ExpiredOneTimeBadgeBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.components.Material3SearchToolbar;
|
||||
import org.thoughtcrime.securesms.components.RatingManager;
|
||||
import org.thoughtcrime.securesms.components.SignalProgressDialog;
|
||||
|
@ -167,7 +167,6 @@ import org.thoughtcrime.securesms.stories.tabs.ConversationListTab;
|
|||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
|
@ -491,7 +490,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
Badge expiredBadge = SignalStore.donationsValues().getExpiredBadge();
|
||||
String subscriptionCancellationReason = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationReason();
|
||||
UnexpectedSubscriptionCancellation unexpectedSubscriptionCancellation = UnexpectedSubscriptionCancellation.fromStatus(subscriptionCancellationReason);
|
||||
boolean isDisplayingSubscriptionFailure = false;
|
||||
long subscriptionFailureTimestamp = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationTimestamp();
|
||||
long subscriptionFailureWatermark = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationWatermark();
|
||||
boolean isWatermarkPriorToTimestamp = subscriptionFailureWatermark < subscriptionFailureTimestamp;
|
||||
|
@ -502,9 +500,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
isWatermarkPriorToTimestamp)
|
||||
{
|
||||
Log.w(TAG, "Displaying bottom sheet for unexpected cancellation: " + unexpectedSubscriptionCancellation, true);
|
||||
new CantProcessSubscriptionPaymentBottomSheetDialogFragment().show(getChildFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
MonthlyDonationCanceledBottomSheetDialogFragment.show(getChildFragmentManager());
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationWatermark(subscriptionFailureTimestamp);
|
||||
isDisplayingSubscriptionFailure = true;
|
||||
} else if (unexpectedSubscriptionCancellation != null && SignalStore.donationsValues().isUserManuallyCancelled()) {
|
||||
Log.w(TAG, "Unexpected cancellation detected but not displaying dialog because user manually cancelled their subscription: " + unexpectedSubscriptionCancellation, true);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationWatermark(subscriptionFailureTimestamp);
|
||||
|
@ -513,17 +510,16 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationWatermark(subscriptionFailureTimestamp);
|
||||
}
|
||||
|
||||
if (expiredBadge != null && !isDisplayingSubscriptionFailure) {
|
||||
if (expiredBadge != null && expiredBadge.isBoost()) {
|
||||
SignalStore.donationsValues().setExpiredBadge(null);
|
||||
|
||||
if (expiredBadge.isBoost() || !SignalStore.donationsValues().isUserManuallyCancelled()) {
|
||||
Log.w(TAG, "Displaying bottom sheet for an expired badge", true);
|
||||
ExpiredBadgeBottomSheetDialogFragment.show(
|
||||
expiredBadge,
|
||||
unexpectedSubscriptionCancellation,
|
||||
SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure(),
|
||||
getParentFragmentManager());
|
||||
}
|
||||
Log.w(TAG, "Displaying bottom sheet for an expired badge", true);
|
||||
ExpiredOneTimeBadgeBottomSheetDialogFragment.show(
|
||||
expiredBadge,
|
||||
unexpectedSubscriptionCancellation,
|
||||
SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure(),
|
||||
getParentFragmentManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@ public class DonationReceiptRedemptionJob extends BaseJob {
|
|||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue(SUBSCRIPTION_QUEUE + (isLongRunningDonationPaymentType ? LONG_RUNNING_QUEUE_SUFFIX : ""))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setMaxInstancesForQueue(1)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.build());
|
||||
}
|
||||
|
|
|
@ -109,19 +109,6 @@ public class SubscriptionKeepAliveJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
if (activeSubscription.isFailedPayment()) {
|
||||
Log.i(TAG, "User has a subscription with a failed payment. Marking the payment failure. Status message: " + activeSubscription.getActiveSubscription().getStatus(), true);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(activeSubscription.getChargeFailure());
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(activeSubscription.getActiveSubscription().getStatus());
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(activeSubscription.getActiveSubscription().getEndOfCurrentPeriod());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeSubscription.getActiveSubscription().isActive()) {
|
||||
Log.i(TAG, "User has an inactive subscription. Status message: " + activeSubscription.getActiveSubscription().getStatus() + " Exiting.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
DonationRedemptionJobStatus status = DonationRedemptionJobWatcher.getSubscriptionRedemptionJobStatus();
|
||||
if (status != DonationRedemptionJobStatus.None.INSTANCE && status != DonationRedemptionJobStatus.FailedSubscription.INSTANCE) {
|
||||
Log.i(TAG, "Already trying to redeem donation, current status: " + status.getClass().getSimpleName(), true);
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.signal.libsignal.zkgroup.receipts.ReceiptCredential;
|
|||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequestContext;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationsConfigurationExtensionsKt;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.PayPalDeclineCode;
|
||||
|
@ -30,11 +31,13 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.subscription.Subscriber;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okio.ByteString;
|
||||
|
@ -72,7 +75,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue("ReceiptRedemption")
|
||||
.setMaxInstancesForQueue(1)
|
||||
.setLifespan(terminalDonation.isLongRunningPaymentMethod ? TimeUnit.DAYS.toMillis(30) : TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
|
@ -174,12 +176,11 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
}
|
||||
|
||||
if (isForKeepAlive) {
|
||||
Log.w(TAG, "Subscription payment failure in active subscription response (status = " + subscription.getStatus() + ").", true);
|
||||
onPaymentFailure(subscription.getStatus(), subscription.getProcessor(), chargeFailure, subscription.getEndOfCurrentPeriod(), true);
|
||||
throw new Exception("Active subscription hit a payment failure: " + subscription.getStatus());
|
||||
Log.w(TAG, "Subscription payment failure in active subscription response (status = " + subscription.getStatus() + "). Payment could still be retried by processor.", true);
|
||||
throw new Exception("Payment renewal is in retry state, let keep-alive job restart process");
|
||||
} else {
|
||||
Log.w(TAG, "New subscription has hit a payment failure. (status = " + subscription.getStatus() + ").", true);
|
||||
onPaymentFailure(subscription.getStatus(), subscription.getProcessor(), chargeFailure, subscription.getEndOfCurrentPeriod(), false);
|
||||
onPaymentFailure(subscription, chargeFailure, false);
|
||||
throw new Exception("New subscription has hit a payment failure: " + subscription.getStatus());
|
||||
}
|
||||
} else if (!subscription.isActive()) {
|
||||
|
@ -189,16 +190,19 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
|
||||
if (!isForKeepAlive) {
|
||||
Log.w(TAG, "Initial subscription payment failed, treating as a permanent failure.");
|
||||
onPaymentFailure(subscription.getStatus(), subscription.getProcessor(), chargeFailure, subscription.getEndOfCurrentPeriod(), false);
|
||||
onPaymentFailure(subscription, chargeFailure, false);
|
||||
throw new Exception("New subscription has hit a payment failure.");
|
||||
}
|
||||
}
|
||||
|
||||
if (isForKeepAlive && subscription.isCanceled()) {
|
||||
Log.w(TAG, "Permanent payment failure in renewing subscription. (status = " + subscription.getStatus() + ").", true);
|
||||
onPaymentFailure(subscription, chargeFailure, true);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
Log.w(TAG, "Subscription is not yet active. Status: " + subscription.getStatus(), true);
|
||||
throw new RetryableException();
|
||||
} else if (subscription.isCanceled()) {
|
||||
Log.w(TAG, "Subscription is marked as cancelled, but it's possible that the user cancelled and then later tried to resubscribe. Scheduling a retry.", true);
|
||||
throw new RetryableException();
|
||||
} else {
|
||||
Log.i(TAG, "Subscription is valid, proceeding with request for ReceiptCredentialResponse", true);
|
||||
long storedEndOfPeriod = SignalStore.donationsValues().getLastEndOfPeriod();
|
||||
|
@ -353,15 +357,21 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
* 1. In the case of a keep-alive event, we want to book-keep the error to show the user on a subsequent launch, and we want to sync our failure state to
|
||||
* linked devices.
|
||||
*/
|
||||
private void onPaymentFailure(@NonNull String status, @NonNull ActiveSubscription.Processor processor, @Nullable ActiveSubscription.ChargeFailure chargeFailure, long timestamp, boolean isForKeepAlive) {
|
||||
private void onPaymentFailure(@NonNull ActiveSubscription.Subscription subscription, @Nullable ActiveSubscription.ChargeFailure chargeFailure, boolean isForKeepAlive) {
|
||||
SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true);
|
||||
if (isForKeepAlive) {
|
||||
Log.d(TAG, "Is for a keep-alive and we have a status. Setting UnexpectedSubscriptionCancelation state...", true);
|
||||
Log.d(TAG, "Subscription canceled during keep-alive. Setting UnexpectedSubscriptionCancelation state...", true);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(chargeFailure);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(status);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(timestamp);
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(subscription.getStatus());
|
||||
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(subscription.getEndOfCurrentPeriod());
|
||||
SignalStore.donationsValues().setShowMonthlyDonationCanceledDialog(true);
|
||||
|
||||
ApplicationDependencies.getDonationsService().getDonationsConfiguration(Locale.getDefault()).getResult().ifPresent(config -> {
|
||||
SignalStore.donationsValues().setExpiredBadge(DonationsConfigurationExtensionsKt.getBadge(config, subscription.getLevel()));
|
||||
});
|
||||
|
||||
MultiDeviceSubscriptionSyncRequestJob.enqueue();
|
||||
} else if (chargeFailure != null && processor == ActiveSubscription.Processor.STRIPE) {
|
||||
} else if (chargeFailure != null && subscription.getProcessor() == ActiveSubscription.Processor.STRIPE) {
|
||||
Log.d(TAG, "Stripe charge failure detected: " + chargeFailure, true);
|
||||
|
||||
StripeDeclineCode declineCode = StripeDeclineCode.Companion.getFromCode(chargeFailure.getOutcomeNetworkReason());
|
||||
|
@ -399,7 +409,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
|
||||
Log.w(TAG, "Not for a keep-alive and we have a charge failure. Routing a payment setup error...", true);
|
||||
onPaymentFailedError(paymentSetupError);
|
||||
} else if (chargeFailure != null && processor == ActiveSubscription.Processor.BRAINTREE) {
|
||||
} else if (chargeFailure != null && subscription.getProcessor() == ActiveSubscription.Processor.BRAINTREE) {
|
||||
Log.d(TAG, "PayPal charge failure detected: " + chargeFailure, true);
|
||||
|
||||
|
||||
|
|
|
@ -387,7 +387,7 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
|
|||
var unexpectedSubscriptionCancelationWatermark: Long by longValue(SUBSCRIPTION_CANCELATION_WATERMARK, 0L)
|
||||
|
||||
@get:JvmName("showCantProcessDialog")
|
||||
var showCantProcessDialog: Boolean by booleanValue(SHOW_CANT_PROCESS_DIALOG, true)
|
||||
var showMonthlyDonationCanceledDialog: Boolean by booleanValue(SHOW_CANT_PROCESS_DIALOG, true)
|
||||
|
||||
/**
|
||||
* Denotes that the previous attempt to subscribe failed in some way. Either an
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
<dialog
|
||||
android:id="@+id/expiredBadgeDialog"
|
||||
android:name="org.thoughtcrime.securesms.badges.self.expired.ExpiredBadgeBottomSheetDialogFragment"
|
||||
android:name="org.thoughtcrime.securesms.badges.self.expired.ExpiredOneTimeBadgeBottomSheetDialogFragment"
|
||||
android:label="dialog_expired_badge">
|
||||
|
||||
<argument
|
||||
|
|
|
@ -6443,5 +6443,16 @@
|
|||
<!-- Step 2 of bottom sheet shown after tapping "Turn on" from the megaphone to re-enable full screen notifications for incoming call notifications, it indicates the name of the setting that needs to be re-enabled -->
|
||||
<string name="GrantFullScreenIntentPermission_bottomsheet_step2">2. %1$s Allow full screen notifications</string>
|
||||
|
||||
<!-- Bottom sheet dialog shown when a monthly donation fails to renew, title for dialog -->
|
||||
<string name="MonthlyDonationCanceled__title">Monthly donation canceled</string>
|
||||
<!-- Bottom sheet dialog shown when a monthly donation fails to renew, body for dialog. First placeholder is a payment related error message. Second placeholder is 'learn more' -->
|
||||
<string name="MonthlyDonationCanceled__message">Your recurring monthly donation was canceled. %1$s\n\nYour badge will no longer be visible on your profile. %2$s</string>
|
||||
<!-- Bottom sheet dialog shown when a monthly donation fails to renew, learn more used in placeholder for body for dialog. -->
|
||||
<string name="MonthlyDonationCanceled__learn_more">Learn more</string>
|
||||
<!-- Bottom sheet dialog shown when a monthly donation fails to renew, primary button to renew subscription with new data -->
|
||||
<string name="MonthlyDonationCanceled__renew_button">Renew subscription</string>
|
||||
<!-- Bottom sheet dialog shown when a monthly donation fails to renew, second button to dismiss the dialog entirely -->
|
||||
<string name="MonthlyDonationCanceled__not_now_button">Not now</string>
|
||||
|
||||
<!-- EOF -->
|
||||
</resources>
|
||||
|
|
|
@ -136,7 +136,11 @@ public final class ActiveSubscription {
|
|||
}
|
||||
|
||||
public boolean isInProgress() {
|
||||
return activeSubscription != null && !isActive() && !activeSubscription.isFailedPayment();
|
||||
return activeSubscription != null && !isActive() && (!activeSubscription.isFailedPayment() || activeSubscription.isPastDue());
|
||||
}
|
||||
|
||||
public boolean isPastDue() {
|
||||
return activeSubscription != null && activeSubscription.isPastDue();
|
||||
}
|
||||
|
||||
public boolean isFailedPayment() {
|
||||
|
@ -249,6 +253,10 @@ public final class ActiveSubscription {
|
|||
return Status.isPaymentFailed(getStatus());
|
||||
}
|
||||
|
||||
public boolean isPastDue() {
|
||||
return Status.getStatus(getStatus()) == Status.PAST_DUE;
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return Status.getStatus(getStatus()) == Status.CANCELED;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue