From 035863102980cbfe31563a8e6069bd8a40a453c2 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 26 Mar 2025 16:11:49 -0300 Subject: [PATCH] Add ability to Self-heal SEPA bug. --- .../jobs/InAppPaymentKeepAliveJob.kt | 33 ++++++++++++++++++- .../api/subscriptions/ActiveSubscription.java | 4 +-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentKeepAliveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentKeepAliveJob.kt index f3f50c4afb..6021fcf9ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentKeepAliveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentKeepAliveJob.kt @@ -162,6 +162,7 @@ class InAppPaymentKeepAliveJob private constructor( SignalDatabase.inAppPayments.update(payment) InAppPaymentRecurringContextJob.createJobChain(payment).enqueue() } + InAppPaymentData.RedemptionState.Stage.CONVERSION_STARTED -> { if (activeInAppPayment.data.redemption.receiptCredentialRequestContext == null) { warn(type, "We are in the CONVERSION_STARTED state without a request credential. Exiting.") @@ -171,6 +172,7 @@ class InAppPaymentKeepAliveJob private constructor( info(type, "We have a request credential we have not turned into a token.") InAppPaymentRecurringContextJob.createJobChain(activeInAppPayment).enqueue() } + InAppPaymentData.RedemptionState.Stage.REDEMPTION_STARTED -> { if (activeInAppPayment.data.redemption.receiptCredentialPresentation == null) { warn(type, "We are in the REDEMPTION_STARTED state without a request credential. Exiting.") @@ -180,6 +182,7 @@ class InAppPaymentKeepAliveJob private constructor( info(type, "We have a receipt credential presentation but have not yet redeemed it.") InAppPaymentRedemptionJob.enqueueJobChainForRecurringKeepAlive(activeInAppPayment) } + else -> info(type, "Nothing to do. Exiting.") } } @@ -197,6 +200,7 @@ class InAppPaymentKeepAliveJob private constructor( 403, 404 -> { warn(type, "Invalid or malformed subscriber id. Status: ${serviceResponse.status}", error) } + else -> { warn(type, "An unknown server error occurred: ${serviceResponse.status}", error) throw InAppPaymentRetryException(error) @@ -213,7 +217,7 @@ class InAppPaymentKeepAliveJob private constructor( val type = subscriber.type val current: InAppPaymentTable.InAppPayment? = SignalDatabase.inAppPayments.getByEndOfPeriod(type.inAppPaymentType, endOfCurrentPeriod) - return if (current == null) { + val inAppPayment = if (current == null) { val oldInAppPayment = SignalDatabase.inAppPayments.getByLatestEndOfPeriod(type.inAppPaymentType) val oldEndOfPeriod = oldInAppPayment?.endOfPeriod ?: InAppPaymentsRepository.getFallbackLastEndOfPeriod(type) if (oldEndOfPeriod > endOfCurrentPeriod) { @@ -256,9 +260,11 @@ class InAppPaymentKeepAliveJob private constructor( InAppPaymentData.PaymentMethodType.CARD } } + InAppPaymentData.PaymentMethodType.UNKNOWN -> { subscriberPaymentMethodType } + else -> subscriptionPaymentMethodType } @@ -318,6 +324,31 @@ class InAppPaymentKeepAliveJob private constructor( } else { current } + + if ( + inAppPayment != null && + inAppPayment.state == InAppPaymentTable.State.END && + ActiveSubscription.Status.getStatus(subscription.status) == ActiveSubscription.Status.ACTIVE && + inAppPayment.endOfPeriodSeconds == subscription.endOfCurrentPeriod && + inAppPayment.data.error != null && + inAppPayment.data.redemption?.stage == InAppPaymentData.RedemptionState.Stage.CONVERSION_STARTED && + inAppPayment.data.paymentMethodType == InAppPaymentData.PaymentMethodType.SEPA_DEBIT + ) { + warn(type, "Detected possible timeout failure due to SEPA bug. We will resubmit.") + + SignalDatabase.inAppPayments.update( + inAppPayment.copy( + state = InAppPaymentTable.State.PENDING, + data = inAppPayment.data.newBuilder() + .error(null) + .build() + ) + ) + + return SignalDatabase.inAppPayments.getById(inAppPayment.id) + } else { + return inAppPayment + } } private fun info(type: InAppPaymentSubscriberRecord.Type, message: String) { 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 61c2eb0d09..0a8dc3c618 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 @@ -72,7 +72,7 @@ public final class ActiveSubscription { } } - private enum Status { + public enum Status { /** * The subscription is currently in a trial period and it's safe to provision your product for your customer. * The subscription transitions automatically to active when the first payment is made. @@ -123,7 +123,7 @@ public final class ActiveSubscription { this.status = status; } - private static Status getStatus(String status) { + public static Status getStatus(String status) { for (Status s : Status.values()) { if (Objects.equals(status, s.status)) { return s;