diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BoostReceiptRequestResponseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/BoostReceiptRequestResponseJob.java index 03109a53c5..170bfbea33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BoostReceiptRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BoostReceiptRequestResponseJob.java @@ -60,7 +60,7 @@ public class BoostReceiptRequestResponseJob extends BaseJob { public static JobManager.Chain createJobChain(StripeApi.PaymentIntent paymentIntent) { BoostReceiptRequestResponseJob requestReceiptJob = createJob(paymentIntent); DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForBoost(); - RefreshOwnProfileJob refreshOwnProfileJob = new RefreshOwnProfileJob(); + RefreshOwnProfileJob refreshOwnProfileJob = RefreshOwnProfileJob.forBoost(); return ApplicationDependencies.getJobManager() .startChain(requestReceiptJob) @@ -195,19 +195,19 @@ public class BoostReceiptRequestResponseJob extends BaseJob { * - expiration_time is between now and 60 days from now */ private boolean isCredentialValid(@NonNull ReceiptCredential receiptCredential) { - long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); - long monthFromNow = now + TimeUnit.DAYS.toSeconds(60); - boolean isCorrectLevel = receiptCredential.getReceiptLevel() == 1; - boolean isExpiration86400 = receiptCredential.getReceiptExpirationTime() % 86400 == 0; - boolean isExpirationInTheFuture = receiptCredential.getReceiptExpirationTime() > now; - boolean isExpirationWithinAMonth = receiptCredential.getReceiptExpirationTime() <= monthFromNow; + long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + long maxExpirationTime = now + TimeUnit.DAYS.toSeconds(60); + boolean isCorrectLevel = receiptCredential.getReceiptLevel() == 1; + boolean isExpiration86400 = receiptCredential.getReceiptExpirationTime() % 86400 == 0; + boolean isExpirationInTheFuture = receiptCredential.getReceiptExpirationTime() > now; + boolean isExpirationWithinMax = receiptCredential.getReceiptExpirationTime() <= maxExpirationTime; Log.d(TAG, "Credential validation: isCorrectLevel(" + isCorrectLevel + ") isExpiration86400(" + isExpiration86400 + ") isExpirationInTheFuture(" + isExpirationInTheFuture + - ") isExpirationWithinAMonth(" + isExpirationWithinAMonth + ")", true); + ") isExpirationWithinMax(" + isExpirationWithinMax + ")", true); - return isCorrectLevel && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinAMonth; + return isCorrectLevel && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinMax; } @Override 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 adfd98d14b..620922ba5d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java @@ -48,13 +48,28 @@ public class RefreshOwnProfileJob extends BaseJob { private static final String TAG = Log.tag(RefreshOwnProfileJob.class); + private static final String SUBSCRIPTION_QUEUE = ProfileUploadJob.QUEUE + "_Subscription"; + private static final String BOOST_QUEUE = ProfileUploadJob.QUEUE + "_Boost"; + public RefreshOwnProfileJob() { + this(ProfileUploadJob.QUEUE); + } + + private RefreshOwnProfileJob(@NonNull String queue) { this(new Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setQueue(ProfileUploadJob.QUEUE) - .setMaxInstancesForFactory(1) - .setMaxAttempts(10) - .build()); + .addConstraint(NetworkConstraint.KEY) + .setQueue(queue) + .setMaxInstancesForFactory(1) + .setMaxAttempts(10) + .build()); + } + + public static @NonNull RefreshOwnProfileJob forSubscription() { + return new RefreshOwnProfileJob(SUBSCRIPTION_QUEUE); + } + + public static @NonNull RefreshOwnProfileJob forBoost() { + return new RefreshOwnProfileJob(BOOST_QUEUE); } @@ -198,11 +213,11 @@ public class RefreshOwnProfileJob extends BaseJob { .max(Comparator.comparingLong(Badge::getExpirationTimestamp)) .get(); - Log.d(TAG, "Marking subscription badge as expired, should notifiy next time the conversation list is open."); + Log.d(TAG, "Marking subscription badge as expired, should notify next time the conversation list is open.", true); SignalStore.donationsValues().setExpiredBadge(mostRecentExpiration); if (!SignalStore.donationsValues().isUserManuallyCancelled()) { - Log.d(TAG, "Detected an unexpected subscription expiry."); + Log.d(TAG, "Detected an unexpected subscription expiry.", true); SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); } } else if (!remoteHasBoostBadges && localHasBoostBadges) { @@ -214,7 +229,7 @@ public class RefreshOwnProfileJob extends BaseJob { .max(Comparator.comparingLong(Badge::getExpirationTimestamp)) .get(); - Log.d(TAG, "Marking boost badge as expired, should notifiy next time the conversation list is open."); + Log.d(TAG, "Marking boost badge as expired, should notify next time the conversation list is open.", true); SignalStore.donationsValues().setExpiredBadge(mostRecentExpiration); } 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 fb60957e78..b2715cb763 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java @@ -67,7 +67,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { Subscriber subscriber = SignalStore.donationsValues().requireSubscriber(); SubscriptionReceiptRequestResponseJob requestReceiptJob = createJob(subscriber.getSubscriberId()); DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForSubscription(); - RefreshOwnProfileJob refreshOwnProfileJob = new RefreshOwnProfileJob(); + RefreshOwnProfileJob refreshOwnProfileJob = RefreshOwnProfileJob.forSubscription(); return ApplicationDependencies.getJobManager() .startChain(requestReceiptJob) @@ -115,14 +115,16 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { private void doRun() throws Exception { ActiveSubscription.Subscription subscription = getLatestSubscriptionInformation(); - if (subscription == null || !subscription.isActive()) { - Log.w(TAG, "User does not have an active subscription yet.", true); + if (subscription == null) { + Log.w(TAG, "Subscription is null.", true); throw new RetryableException(); } else if (subscription.isFailedPayment()) { - Log.w(TAG, "Subscription payment failure. Passing through to redemption job.", true); - SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); - setOutputData(new Data.Builder().putBoolean(DonationReceiptRedemptionJob.INPUT_PAYMENT_FAILURE, true).build()); + Log.w(TAG, "Subscription payment failure in active subscription response (status = " + subscription.getStatus() + "). Passing through to redemption job.", true); + onPaymentFailure(); return; + } else if (!subscription.isActive()) { + Log.w(TAG, "Subscription is not yet active. Status: " + subscription.getStatus(), true); + throw new RetryableException(); } else { Log.i(TAG, "Recording end of period from active subscription.", true); SignalStore.donationsValues().setLastEndOfPeriod(subscription.getEndOfCurrentPeriod()); @@ -150,10 +152,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { if (response.getApplicationError().isPresent()) { handleApplicationError(response); - - if (response.getStatus() == 204) { - SignalStore.donationsValues().clearSubscriptionRedemptionFailed(); - } } else if (response.getResult().isPresent()) { ReceiptCredential receiptCredential = getReceiptCredential(response.getResult().get()); @@ -214,15 +212,15 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { private void handleApplicationError(ServiceResponse response) throws Exception { switch (response.getStatus()) { case 204: - Log.w(TAG, "User does not have receipts available to exchange. Exiting.", response.getApplicationError().get(), true); - break; + Log.w(TAG, "Payment is still processing. Trying again.", response.getApplicationError().get(), true); + SignalStore.donationsValues().clearSubscriptionRedemptionFailed(); + throw new RetryableException(); case 400: Log.w(TAG, "Receipt credential request failed to validate.", response.getApplicationError().get(), true); throw new Exception(response.getApplicationError().get()); case 402: - Log.w(TAG, "Subscription payment failure. Passing through to redemption job.", true); - SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); - setOutputData(new Data.Builder().putBoolean(DonationReceiptRedemptionJob.INPUT_PAYMENT_FAILURE, true).build()); + Log.w(TAG, "Subscription payment failure in credential response. Passing through to redemption job.", true); + onPaymentFailure(); break; case 403: Log.w(TAG, "SubscriberId password mismatch or account auth was present.", response.getApplicationError().get(), true); @@ -239,6 +237,11 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { } } + private void onPaymentFailure() { + SignalStore.donationsValues().setShouldCancelSubscriptionBeforeNextSubscribeAttempt(true); + setOutputData(new Data.Builder().putBoolean(DonationReceiptRedemptionJob.INPUT_PAYMENT_FAILURE, true).build()); + } + /** * Checks that the generated Receipt Credential has the following characteristics * - level should match the current subscription level and be the same level you signed up for at the time the subscription was last updated @@ -247,21 +250,21 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { * - expiration_time is between now and 60 days from now */ private boolean isCredentialValid(@NonNull ActiveSubscription.Subscription subscription, @NonNull ReceiptCredential receiptCredential) { - long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); - long monthFromNow = now + TimeUnit.DAYS.toSeconds(60); - boolean isSameLevel = subscription.getLevel() == receiptCredential.getReceiptLevel(); - boolean isExpirationAfterSub = subscription.getEndOfCurrentPeriod() < receiptCredential.getReceiptExpirationTime(); - boolean isExpiration86400 = receiptCredential.getReceiptExpirationTime() % 86400 == 0; - boolean isExpirationInTheFuture = receiptCredential.getReceiptExpirationTime() > now; - boolean isExpirationWithinAMonth = receiptCredential.getReceiptExpirationTime() <= monthFromNow; + long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + long maxExpirationTime = now + TimeUnit.DAYS.toSeconds(60); + boolean isSameLevel = subscription.getLevel() == receiptCredential.getReceiptLevel(); + boolean isExpirationAfterSub = subscription.getEndOfCurrentPeriod() < receiptCredential.getReceiptExpirationTime(); + boolean isExpiration86400 = receiptCredential.getReceiptExpirationTime() % 86400 == 0; + boolean isExpirationInTheFuture = receiptCredential.getReceiptExpirationTime() > now; + boolean isExpirationWithinMax = receiptCredential.getReceiptExpirationTime() <= maxExpirationTime; Log.d(TAG, "Credential validation: isSameLevel(" + isSameLevel + ") isExpirationAfterSub(" + isExpirationAfterSub + ") isExpiration86400(" + isExpiration86400 + ") isExpirationInTheFuture(" + isExpirationInTheFuture + - ") isExpirationWithinAMonth(" + isExpirationWithinAMonth + ")", true); + ") isExpirationWithinMax(" + isExpirationWithinMax + ")", true); - return isSameLevel && isExpirationAfterSub && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinAMonth; + return isSameLevel && isExpirationAfterSub && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinMax; } @Override diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/subscriptions/ActiveSubscription.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/subscriptions/ActiveSubscription.java index 730cec3156..080b305860 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/subscriptions/ActiveSubscription.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/subscriptions/ActiveSubscription.java @@ -4,7 +4,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; public final class ActiveSubscription { @@ -49,6 +52,13 @@ public final class ActiveSubscription { private final String status; + private static final Set FAILURE_STATUSES = new HashSet<>(Arrays.asList( + INCOMPLETE_EXPIRED, + PAST_DUE, + CANCELED, + UNPAID + )); + Status(String status) { this.status = status; } @@ -64,8 +74,7 @@ public final class ActiveSubscription { } static boolean isPaymentFailed(String status) { - Status s = getStatus(status); - return s == INCOMPLETE || s == INCOMPLETE_EXPIRED; + return FAILURE_STATUSES.contains(getStatus(status)); } } @@ -85,11 +94,11 @@ public final class ActiveSubscription { } public boolean isInProgress() { - return activeSubscription != null && !isActive() && !isFailedPayment(); + return activeSubscription != null && !isActive() && !activeSubscription.isFailedPayment(); } public boolean isFailedPayment() { - return activeSubscription != null && !isActive() && isFailedPayment(); + return activeSubscription != null && !isActive() && activeSubscription.isFailedPayment(); } public static final class Subscription { @@ -171,8 +180,7 @@ public final class ActiveSubscription { } public boolean isInProgress() { - return !isActive() && - !Status.isPaymentFailed(getStatus()); + return !isActive() && !Status.isPaymentFailed(getStatus()); } public boolean isFailedPayment() {