From b65d62e0653c8d69e68748f7490a2bbc3d8e25f1 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 8 Apr 2022 13:07:19 -0300 Subject: [PATCH] Update prioritization of donation error bottom sheets. --- .../subscribe/SubscribeViewModel.kt | 2 ++ .../ConversationListFragment.java | 27 +++++++++++++------ .../securesms/jobs/RefreshOwnProfileJob.java | 3 --- .../jobs/SubscriptionKeepAliveJob.java | 9 ++++++- ...SubscriptionReceiptRequestResponseJob.java | 8 +++--- .../securesms/keyvalue/DonationsValues.kt | 6 +++++ .../securesms/storage/StorageSyncHelper.java | 1 + 7 files changed, 40 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt index 37e8042559..7f7805a0ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt @@ -168,6 +168,7 @@ class SubscribeViewModel( SignalStore.donationsValues().clearLevelOperations() SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt = false SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null + SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L MultiDeviceSubscriptionSyncRequestJob.enqueue() } } else { @@ -185,6 +186,7 @@ class SubscribeViewModel( SignalStore.donationsValues().clearLevelOperations() SignalStore.donationsValues().markUserManuallyCancelled() SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null + SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L refreshActiveSubscription() MultiDeviceSubscriptionSyncRequestJob.enqueue() donationPaymentRepository.scheduleSyncForAccountRecordChange() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 0baeee2909..7d3d087fd0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -342,21 +342,32 @@ 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; - if (expiredBadge != null) { + if (unexpectedSubscriptionCancellation != null && + !SignalStore.donationsValues().isUserManuallyCancelled() && + SignalStore.donationsValues().showCantProcessDialog() && + isWatermarkPriorToTimestamp) { + Log.w(TAG, "Displaying bottom sheet for unexpected cancellation: " + unexpectedSubscriptionCancellation, true); + new CantProcessSubscriptionPaymentBottomSheetDialogFragment().show(getChildFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG); + 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); + } else if (unexpectedSubscriptionCancellation != null && !SignalStore.donationsValues().showCantProcessDialog()) { + Log.w(TAG, "Unexpected cancellation detected but not displaying dialog because user has silenced it.", true); + } + + if (expiredBadge != null && !isDisplayingSubscriptionFailure) { 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, getParentFragmentManager()); } - } else if (unexpectedSubscriptionCancellation != null && !SignalStore.donationsValues().isUserManuallyCancelled() && SignalStore.donationsValues().showCantProcessDialog()) { - Log.w(TAG, "Displaying bottom sheet for unexpected cancellation: " + unexpectedSubscriptionCancellation, true); - new CantProcessSubscriptionPaymentBottomSheetDialogFragment().show(getChildFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG); - } 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); - } else if (unexpectedSubscriptionCancellation != null && !SignalStore.donationsValues().showCantProcessDialog()) { - Log.w(TAG, "Unexpected cancellation detected but not displaying dialog because user has silenced it.", true); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java index 30eba1f9e7..7ea869d21c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java @@ -11,7 +11,6 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.thoughtcrime.securesms.badges.BadgeRepository; import org.thoughtcrime.securesms.badges.Badges; import org.thoughtcrime.securesms.badges.models.Badge; -import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -269,7 +268,6 @@ public class RefreshOwnProfileJob extends BaseJob { ActiveSubscription activeSubscription = response.getResult().get(); if (activeSubscription.isFailedPayment()) { Log.d(TAG, "Unexpected expiry due to payment failure.", true); - SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(activeSubscription.getActiveSubscription().getStatus()); isDueToPaymentFailure = true; } } @@ -277,7 +275,6 @@ public class RefreshOwnProfileJob extends BaseJob { if (!isDueToPaymentFailure) { Log.d(TAG, "Unexpected expiry due to inactivity.", true); - SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(UnexpectedSubscriptionCancellation.INACTIVE.getStatus()); } MultiDeviceSubscriptionSyncRequestJob.enqueue(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java index 836ee21fee..c511bd754a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java @@ -91,7 +91,14 @@ public class SubscriptionKeepAliveJob extends BaseJob { ActiveSubscription activeSubscription = activeSubscriptionResponse.getResult().get(); if (activeSubscription.getActiveSubscription() == null) { - Log.i(TAG, "User does not have an active subscription. Exiting.", true); + Log.i(TAG, "User does not have a subscription. Exiting.", true); + 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().setUnexpectedSubscriptionCancelationReason(activeSubscription.getActiveSubscription().getStatus()); + SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(activeSubscription.getActiveSubscription().getEndOfCurrentPeriod()); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java index 60c221f17b..7212f398cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java @@ -144,7 +144,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { throw new RetryableException(); } else if (subscription.isFailedPayment()) { Log.w(TAG, "Subscription payment failure in active subscription response (status = " + subscription.getStatus() + ").", true); - onPaymentFailure(subscription.getStatus()); + onPaymentFailure(subscription.getStatus(), subscription.getEndOfCurrentPeriod()); throw new Exception("Subscription has a payment failure: " + subscription.getStatus()); } else if (!subscription.isActive()) { Log.w(TAG, "Subscription is not yet active. Status: " + subscription.getStatus(), true); @@ -234,7 +234,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { throw new Exception(response.getApplicationError().get()); case 402: Log.w(TAG, "Subscription payment failure in credential response.", response.getApplicationError().get(), true); - onPaymentFailure(null); + onPaymentFailure(null, 0L); throw new Exception(response.getApplicationError().get()); case 403: Log.w(TAG, "SubscriberId password mismatch or account auth was present.", response.getApplicationError().get(), true); @@ -253,13 +253,13 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { } } - private void onPaymentFailure(@Nullable String status) { + private void onPaymentFailure(@Nullable String status, long timestamp) { SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); if (status == null) { DonationError.routeDonationError(context, DonationError.genericPaymentFailure(getErrorSource())); } else { - SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(status); + SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(timestamp); MultiDeviceSubscriptionSyncRequestJob.enqueue(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt index 451e817eb7..211c85ffd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt @@ -35,6 +35,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign private const val SUBSCRIPTION_REDEMPTION_FAILED = "donation.subscription.redemption.failed" private const val SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT = "donation.should.cancel.subscription.before.next.subscribe.attempt" private const val SUBSCRIPTION_CANCELATION_REASON = "donation.subscription.cancelation.reason" + private const val SUBSCRIPTION_CANCELATION_TIMESTAMP = "donation.subscription.cancelation.timestamp" + private const val SUBSCRIPTION_CANCELATION_WATERMARK = "donation.subscription.cancelation.watermark" private const val SHOW_CANT_PROCESS_DIALOG = "show.cant.process.dialog" } @@ -46,6 +48,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign KEY_LAST_END_OF_PERIOD_SECONDS, SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT, SUBSCRIPTION_CANCELATION_REASON, + SUBSCRIPTION_CANCELATION_TIMESTAMP, + SUBSCRIPTION_CANCELATION_WATERMARK, SHOW_CANT_PROCESS_DIALOG ) @@ -230,6 +234,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign } var unexpectedSubscriptionCancelationReason: String? by stringValue(SUBSCRIPTION_CANCELATION_REASON, null) + var unexpectedSubscriptionCancelationTimestamp: Long by longValue(SUBSCRIPTION_CANCELATION_TIMESTAMP, 0L) + var unexpectedSubscriptionCancelationWatermark: Long by longValue(SUBSCRIPTION_CANCELATION_WATERMARK, 0L) @get:JvmName("showCantProcessDialog") var showCantProcessDialog: Boolean by booleanValue(SHOW_CANT_PROCESS_DIALOG, true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java index fca775d28c..8369e2cf84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java @@ -162,6 +162,7 @@ public final class StorageSyncHelper { if (update.getNew().isSubscriptionManuallyCancelled()) { SignalStore.donationsValues().markUserManuallyCancelled(); SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(null); + SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(0L); } else { SignalStore.donationsValues().clearUserManuallyCancelled(); }