Don't fail backup redemption pipeline after 24hrs.

This commit is contained in:
Alex Hart
2025-09-09 11:46:59 -03:00
committed by GitHub
parent 3c7534f7fa
commit 40ba967192
7 changed files with 66 additions and 8 deletions

View File

@@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
@@ -223,10 +224,14 @@ object InAppPaymentsRepository {
* Returns a duration to utilize for jobs tied to different payment methods. For long running bank transfers, we need to
* allow extra time for completion.
*/
fun resolveContextJobLifespan(inAppPayment: InAppPaymentTable.InAppPayment): Duration {
return when (inAppPayment.data.paymentMethodType) {
InAppPaymentData.PaymentMethodType.SEPA_DEBIT, InAppPaymentData.PaymentMethodType.IDEAL -> 30.days
else -> 1.days
fun resolveContextJobLifespanMillis(inAppPayment: InAppPaymentTable.InAppPayment): Long {
return if (inAppPayment.type == InAppPaymentType.RECURRING_BACKUP) {
Job.Parameters.IMMORTAL
} else {
when (inAppPayment.data.paymentMethodType) {
InAppPaymentData.PaymentMethodType.SEPA_DEBIT, InAppPaymentData.PaymentMethodType.IDEAL -> 30.days.inWholeMilliseconds
else -> 1.days.inWholeMilliseconds
}
}
}

View File

@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.util.parcelers.MillisecondDurationParceler
import org.thoughtcrime.securesms.util.parcelers.NullableSubscriberIdParceler
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -184,6 +185,20 @@ class InAppPaymentTable(context: Context, databaseHelper: SignalDatabase) : Data
AppDependencies.databaseObserver.notifyInAppPaymentsObservers(inAppPayment)
}
fun getOldPendingPayments(type: InAppPaymentType): List<InAppPayment> {
val oneDayAgo = System.currentTimeMillis().milliseconds - 24.hours
return readableDatabase
.select()
.from(TABLE_NAME)
.where(
"$STATE = ? AND $TYPE = ? AND $UPDATED_AT <= ${oneDayAgo.inWholeSeconds}",
State.serialize(State.PENDING),
InAppPaymentType.serialize(type)
)
.run()
.readToList(mapper = InAppPayment::deserialize)
}
/**
* Returns true if the user has submitted a pre-pending recurring donation.
* In this state, the user would have had to cancel their subscription or be in the process of trying

View File

@@ -174,7 +174,11 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
val isGooglePlayBillingCanceled = purchase is BillingPurchaseResult.Success && !purchase.isAutoRenewing
if (isGooglePlayBillingCanceled && (!hasActiveSignalSubscription || isSignalSubscriptionFailedOrCanceled)) {
Log.i(TAG, "Valid cancel state. Clearing mismatch. (isGooglePlayBillingCanceled: true, hasActiveSignalSubscription: $hasActiveSignalSubscription, isSignalSubscriptionFailedOrCanceled: $isSignalSubscriptionFailedOrCanceled", true)
Log.i(
TAG,
"Valid cancel state. Clearing mismatch. (isGooglePlayBillingCanceled: true, hasActiveSignalSubscription: $hasActiveSignalSubscription, isSignalSubscriptionFailedOrCanceled: $isSignalSubscriptionFailedOrCanceled",
true
)
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
} else {

View File

@@ -151,6 +151,8 @@ class InAppPaymentKeepAliveJob private constructor(
return
}
clearOldPendingPaymentsIfRequired()
val activeInAppPayment = getActiveInAppPayment(subscriber, subscription)
if (activeInAppPayment == null) {
warn(type, "Failed to generate active in-app payment. Exiting")
@@ -227,6 +229,38 @@ class InAppPaymentKeepAliveJob private constructor(
}
}
/**
* If we have a pending payment that is at least 24 hours old AND we have no jobs
* enqueued that are associated with it, then something weird has happened. We should
* fail the job and let the keep-alive check create a new one as necessary.
*/
private fun clearOldPendingPaymentsIfRequired() {
val inAppPayments = SignalDatabase.inAppPayments.getOldPendingPayments(type.inAppPaymentType)
inAppPayments.forEach { inAppPayment ->
val queue = InAppPaymentsRepository.resolveJobQueueKey(inAppPayment)
if (AppDependencies.jobManager.isQueueEmpty(queue)) {
Log.i(TAG, "User has an aged-out pending in-app payment [${inAppPayment.id}][${inAppPayment.type}]. Marking failed and proceeding with check.")
SignalDatabase.inAppPayments.update(
inAppPayment = inAppPayment.copy(
notified = true,
endOfPeriod = 0.seconds,
subscriberId = null,
state = InAppPaymentTable.State.END,
data = inAppPayment.data.newBuilder()
.error(
InAppPaymentData.Error(
type = InAppPaymentData.Error.Type.REDEMPTION
)
)
.build()
)
)
}
}
}
private fun getActiveInAppPayment(
subscriber: InAppPaymentSubscriberRecord,
subscription: ActiveSubscription.Subscription

View File

@@ -54,7 +54,7 @@ class InAppPaymentOneTimeContextJob private constructor(
parameters = Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue(InAppPaymentsRepository.resolveJobQueueKey(inAppPayment))
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespan(inAppPayment).inWholeMilliseconds)
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespanMillis(inAppPayment))
.setMaxAttempts(Parameters.UNLIMITED)
.build()
)

View File

@@ -61,7 +61,7 @@ class InAppPaymentRecurringContextJob private constructor(
parameters = Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue(InAppPaymentsRepository.resolveJobQueueKey(inAppPayment))
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespan(inAppPayment).inWholeMilliseconds)
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespanMillis(inAppPayment))
.setMaxAttempts(Parameters.UNLIMITED)
.build()
)

View File

@@ -44,7 +44,7 @@ abstract class InAppPaymentSetupJob(
protected fun getParameters(inAppPayment: InAppPaymentTable.InAppPayment): Parameters {
return Parameters.Builder()
.setQueue(InAppPaymentsRepository.resolveJobQueueKey(inAppPayment))
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespan(inAppPayment).inWholeMilliseconds)
.setLifespan(InAppPaymentsRepository.resolveContextJobLifespanMillis(inAppPayment))
.setMaxAttempts(Parameters.UNLIMITED)
.build()
}