diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/DonationReceiptRedemptionJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/DonationReceiptRedemptionJob.java index c50950245..9f286f027 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/DonationReceiptRedemptionJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/DonationReceiptRedemptionJob.java @@ -28,6 +28,7 @@ public class DonationReceiptRedemptionJob extends BaseJob { public static final String KEY = "DonationReceiptRedemptionJob"; public static final String INPUT_RECEIPT_CREDENTIAL_PRESENTATION = "data.receipt.credential.presentation"; public static final String INPUT_PAYMENT_FAILURE = "data.payment.failure"; + public static final String INPUT_KEEP_ALIVE_409 = "data.keep.alive.409"; public static DonationReceiptRedemptionJob createJobForSubscription() { return new DonationReceiptRedemptionJob( @@ -72,6 +73,9 @@ public class DonationReceiptRedemptionJob extends BaseJob { if (inputData != null && inputData.getBooleanOrDefault(INPUT_PAYMENT_FAILURE, false)) { DonorBadgeNotifications.PaymentFailed.INSTANCE.show(context); + } else if (inputData != null && inputData.getBooleanOrDefault(INPUT_KEEP_ALIVE_409, false)) { + Log.i(TAG, "Skipping redemption due to 409 error during keep-alive."); + return; } else { DonorBadgeNotifications.RedemptionFailed.INSTANCE.show(context); } @@ -79,6 +83,7 @@ public class DonationReceiptRedemptionJob extends BaseJob { if (isForSubscription()) { Log.d(TAG, "Marking subscription failure", true); SignalStore.donationsValues().markSubscriptionRedemptionFailed(); + 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 71e6cb955..0f039a507 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionKeepAliveJob.java @@ -15,6 +15,7 @@ import org.whispersystems.signalservice.internal.EmptyResponse; import org.whispersystems.signalservice.internal.ServiceResponse; import java.io.IOException; +import java.util.Locale; import java.util.concurrent.TimeUnit; /** @@ -95,8 +96,14 @@ public class SubscriptionKeepAliveJob extends BaseJob { } if (activeSubscription.getActiveSubscription().getEndOfCurrentPeriod() > SignalStore.donationsValues().getLastEndOfPeriod()) { - Log.i(TAG, "Last end of period change. Requesting receipt refresh.", true); - SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue(); + Log.i(TAG, + String.format(Locale.US, + "Last end of period change. Requesting receipt refresh. (old: %d to new: %d)", + SignalStore.donationsValues().getLastEndOfPeriod(), + activeSubscription.getActiveSubscription().getEndOfCurrentPeriod()), + true); + + SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain(true).enqueue(); } } 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 b7c6353ad..bc8a921cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java @@ -39,16 +39,18 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { public static final String KEY = "SubscriptionReceiptCredentialsSubmissionJob"; - private static final String DATA_REQUEST_BYTES = "data.request.bytes"; - private static final String DATA_SUBSCRIBER_ID = "data.subscriber.id"; + private static final String DATA_REQUEST_BYTES = "data.request.bytes"; + private static final String DATA_SUBSCRIBER_ID = "data.subscriber.id"; + private static final String DATA_IS_FOR_KEEP_ALIVE = "data.is.for.keep.alive"; public static final Object MUTEX = new Object(); private ReceiptCredentialRequestContext requestContext; private final SubscriberId subscriberId; + private final boolean isForKeepAlive; - static SubscriptionReceiptRequestResponseJob createJob(SubscriberId subscriberId) { + private static SubscriptionReceiptRequestResponseJob createJob(SubscriberId subscriberId, boolean isForKeepAlive) { return new SubscriptionReceiptRequestResponseJob( new Parameters .Builder() @@ -59,13 +61,18 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { .setMaxAttempts(Parameters.UNLIMITED) .build(), null, - subscriberId + subscriberId, + isForKeepAlive ); } public static JobManager.Chain createSubscriptionContinuationJobChain() { + return createSubscriptionContinuationJobChain(false); + } + + public static JobManager.Chain createSubscriptionContinuationJobChain(boolean isForKeepAlive) { Subscriber subscriber = SignalStore.donationsValues().requireSubscriber(); - SubscriptionReceiptRequestResponseJob requestReceiptJob = createJob(subscriber.getSubscriberId()); + SubscriptionReceiptRequestResponseJob requestReceiptJob = createJob(subscriber.getSubscriberId(), isForKeepAlive); DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForSubscription(); RefreshOwnProfileJob refreshOwnProfileJob = RefreshOwnProfileJob.forSubscription(); @@ -77,16 +84,19 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { private SubscriptionReceiptRequestResponseJob(@NonNull Parameters parameters, @Nullable ReceiptCredentialRequestContext requestContext, - @NonNull SubscriberId subscriberId) + @NonNull SubscriberId subscriberId, + boolean isForKeepAlive) { super(parameters); this.requestContext = requestContext; this.subscriberId = subscriberId; + this.isForKeepAlive = isForKeepAlive; } @Override public @NonNull Data serialize() { - Data.Builder builder = new Data.Builder().putBlobAsString(DATA_SUBSCRIBER_ID, subscriberId.getBytes()); + Data.Builder builder = new Data.Builder().putBlobAsString(DATA_SUBSCRIBER_ID, subscriberId.getBytes()) + .putBoolean(DATA_IS_FOR_KEEP_ALIVE, isForKeepAlive); if (requestContext != null) { builder.putBlobAsString(DATA_REQUEST_BYTES, requestContext.serialize()); @@ -102,9 +112,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { @Override public void onFailure() { - DonorBadgeNotifications.RedemptionFailed.INSTANCE.show(context); - SignalStore.donationsValues().markSubscriptionRedemptionFailed(); - MultiDeviceSubscriptionSyncRequestJob.enqueue(); } @Override @@ -231,8 +238,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { Log.w(TAG, "SubscriberId not found or misformed.", response.getApplicationError().get(), true); throw new Exception(response.getApplicationError().get()); case 409: - Log.w(TAG, "Latest paid receipt on subscription already redeemed with a different request credential.", response.getApplicationError().get(), true); - throw new Exception(response.getApplicationError().get()); + onAlreadyRedeemed(response); + break; default: Log.w(TAG, "Encountered a server failure response: " + response.getStatus(), response.getApplicationError().get(), true); throw new RetryableException(); @@ -245,6 +252,16 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { MultiDeviceSubscriptionSyncRequestJob.enqueue(); } + private void onAlreadyRedeemed(ServiceResponse response) throws Exception { + if (isForKeepAlive) { + Log.i(TAG, "KeepAlive: Latest paid receipt on subscription already redeemed with a different request credential, ignoring.", response.getApplicationError().get(), true); + setOutputData(new Data.Builder().putBoolean(DonationReceiptRedemptionJob.INPUT_KEEP_ALIVE_409, true).build()); + } else { + Log.w(TAG, "Latest paid receipt on subscription already redeemed with a different request credential.", response.getApplicationError().get(), true); + throw new Exception(response.getApplicationError().get()); + } + } + /** * 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 @@ -282,16 +299,17 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { public static class Factory implements Job.Factory { @Override public @NonNull SubscriptionReceiptRequestResponseJob create(@NonNull Parameters parameters, @NonNull Data data) { - SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID)); + SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID)); + boolean isForKeepAlive = data.getBooleanOrDefault(DATA_IS_FOR_KEEP_ALIVE, false); try { if (data.hasString(DATA_REQUEST_BYTES)) { byte[] blob = data.getStringAsBlob(DATA_REQUEST_BYTES); ReceiptCredentialRequestContext requestContext = new ReceiptCredentialRequestContext(blob); - return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId); + return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId, isForKeepAlive); } else { - return new SubscriptionReceiptRequestResponseJob(parameters, null, subscriberId); + return new SubscriptionReceiptRequestResponseJob(parameters, null, subscriberId, isForKeepAlive); } } catch (InvalidInputException e) { throw new IllegalStateException(e);