Add support and tracking of ChargeFailure in ActiveSubscription.

This commit is contained in:
Alex Hart
2022-04-18 16:37:12 -03:00
committed by GitHub
parent 159d67ec59
commit 115f7063d5
7 changed files with 125 additions and 13 deletions

View File

@@ -25,7 +25,7 @@ class SubscriptionsRepository(private val donationsService: DonationsService) {
donationsService.getSubscription(localSubscription.subscriberId)
.flatMap(ServiceResponse<ActiveSubscription>::flattenResult)
} else {
Single.just(ActiveSubscription(null))
Single.just(ActiveSubscription.EMPTY)
}
}

View File

@@ -148,7 +148,7 @@ class SubscribeViewModel(
.getActiveSubscription()
.subscribeBy(
onSuccess = { activeSubscriptionSubject.onNext(it) },
onError = { activeSubscriptionSubject.onNext(ActiveSubscription(null)) }
onError = { activeSubscriptionSubject.onNext(ActiveSubscription.EMPTY) }
)
}
@@ -167,6 +167,7 @@ class SubscribeViewModel(
SignalStore.donationsValues().setLastEndOfPeriod(0L)
SignalStore.donationsValues().clearLevelOperations()
SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt = false
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(null)
SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null
SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L
MultiDeviceSubscriptionSyncRequestJob.enqueue()
@@ -185,6 +186,7 @@ class SubscribeViewModel(
SignalStore.donationsValues().setLastEndOfPeriod(0L)
SignalStore.donationsValues().clearLevelOperations()
SignalStore.donationsValues().markUserManuallyCancelled()
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(null)
SignalStore.donationsValues().unexpectedSubscriptionCancelationReason = null
SignalStore.donationsValues().unexpectedSubscriptionCancelationTimestamp = 0L
refreshActiveSubscription()

View File

@@ -97,6 +97,7 @@ public class SubscriptionKeepAliveJob extends BaseJob {
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;

View File

@@ -138,13 +138,20 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
}
private void doRun() throws Exception {
ActiveSubscription.Subscription subscription = getLatestSubscriptionInformation();
ActiveSubscription activeSubscription = getLatestSubscriptionInformation();
ActiveSubscription.Subscription subscription = activeSubscription.getActiveSubscription();
if (subscription == null) {
Log.w(TAG, "Subscription is null.", true);
throw new RetryableException();
} else if (subscription.isFailedPayment()) {
ActiveSubscription.ChargeFailure chargeFailure = activeSubscription.getChargeFailure();
if (chargeFailure != null) {
Log.w(TAG, "Subscription payment charge failure code: " + chargeFailure.getCode() + ", message: " + chargeFailure.getMessage(), true);
}
Log.w(TAG, "Subscription payment failure in active subscription response (status = " + subscription.getStatus() + ").", true);
onPaymentFailure(subscription.getStatus(), subscription.getEndOfCurrentPeriod());
onPaymentFailure(subscription.getStatus(), chargeFailure, 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);
@@ -184,13 +191,13 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
}
}
private @Nullable ActiveSubscription.Subscription getLatestSubscriptionInformation() throws Exception {
private @NonNull ActiveSubscription getLatestSubscriptionInformation() throws Exception {
ServiceResponse<ActiveSubscription> activeSubscription = ApplicationDependencies.getDonationsService()
.getSubscription(subscriberId)
.blockingGet();
if (activeSubscription.getResult().isPresent()) {
return activeSubscription.getResult().get().getActiveSubscription();
return activeSubscription.getResult().get();
} else if (activeSubscription.getApplicationError().isPresent()) {
Log.w(TAG, "Unrecoverable error getting the user's current subscription. Failing.", activeSubscription.getApplicationError().get(), true);
DonationError.routeDonationError(context, DonationError.genericBadgeRedemptionFailure(getErrorSource()));
@@ -234,7 +241,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, 0L);
onPaymentFailure(null, 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,11 +260,12 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
}
}
private void onPaymentFailure(@Nullable String status, long timestamp) {
private void onPaymentFailure(@Nullable String status, @Nullable ActiveSubscription.ChargeFailure chargeFailure, long timestamp) {
SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true);
if (status == null) {
DonationError.routeDonationError(context, DonationError.genericPaymentFailure(getErrorSource()));
} else {
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationChargeFailure(chargeFailure);
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationReason(status);
SignalStore.donationsValues().setUnexpectedSubscriptionCancelationTimestamp(timestamp);
MultiDeviceSubscriptionSyncRequestJob.enqueue();

View File

@@ -11,8 +11,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
import org.thoughtcrime.securesms.subscription.Subscriber
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.internal.util.JsonUtil
import java.util.Currency
import java.util.Locale
import java.util.concurrent.TimeUnit
@@ -34,6 +36,7 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
private const val DISPLAY_BADGES_ON_PROFILE = "donation.display.badges.on.profile"
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_CHARGE_FAILURE = "donation.subscription.cancelation.charge.failure"
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"
@@ -233,6 +236,23 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
putBoolean(SUBSCRIPTION_REDEMPTION_FAILED, false)
}
fun setUnexpectedSubscriptionCancelationChargeFailure(chargeFailure: ActiveSubscription.ChargeFailure?) {
if (chargeFailure == null) {
remove(SUBSCRIPTION_CANCELATION_CHARGE_FAILURE)
} else {
putString(SUBSCRIPTION_CANCELATION_CHARGE_FAILURE, JsonUtil.toJson(chargeFailure))
}
}
fun getUnexpectedSubscriptionCancelationChargeFailure(): ActiveSubscription.ChargeFailure? {
val json = getString(SUBSCRIPTION_CANCELATION_CHARGE_FAILURE, null)
return if (json.isNullOrEmpty()) {
null
} else {
JsonUtil.fromJson(json, ActiveSubscription.ChargeFailure::class.java)
}
}
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)