mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-20 08:45:50 +01:00
Fix bad state with lapsed in app donation.
This commit is contained in:
+33
-18
@@ -97,27 +97,42 @@ object InAppPaymentsRepository {
|
||||
*/
|
||||
fun updateInAppPaymentWithCancelation(activeSubscription: ActiveSubscription, subscriberType: InAppPaymentSubscriberRecord.Type) {
|
||||
if (activeSubscription.isCanceled || (subscriberType == InAppPaymentSubscriberRecord.Type.BACKUP && activeSubscription.willCancelAtPeriodEnd()) || activeSubscription.isFailedPayment) {
|
||||
val subscriber = getSubscriber(subscriberType) ?: return
|
||||
val latestPayment = SignalDatabase.inAppPayments.getLatestBySubscriberId(subscriber.subscriberId) ?: return
|
||||
if (latestPayment.state == InAppPaymentTable.State.END && latestPayment.data.cancellation == null) {
|
||||
synchronized(subscriber.type.lock) {
|
||||
val payment = SignalDatabase.inAppPayments.getLatestBySubscriberId(subscriber.subscriberId) ?: return
|
||||
val chargeFailure: ActiveSubscription.ChargeFailure? = activeSubscription.chargeFailure
|
||||
writeCancelation(subscriberType, activeSubscription.chargeFailure)
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "[$subscriberType] Recording cancelation in the database. (has charge failure? ${chargeFailure != null})")
|
||||
SignalDatabase.inAppPayments.update(
|
||||
payment.copy(
|
||||
data = payment.data.newBuilder()
|
||||
.cancellation(
|
||||
InAppPaymentData.Cancellation(
|
||||
reason = if (chargeFailure != null) InAppPaymentData.Cancellation.Reason.PAST_DUE else InAppPaymentData.Cancellation.Reason.CANCELED,
|
||||
chargeFailure = chargeFailure?.toInAppPaymentDataChargeFailure()
|
||||
)
|
||||
/**
|
||||
* Records a cancelation for a subscriber whose subscription is no longer present on the server at all. None of the
|
||||
* predicates in [updateInAppPaymentWithCancelation] can detect that state, as every one of them requires a subscription
|
||||
* object to inspect. Callers must have confirmed the subscription is gone rather than merely terminal.
|
||||
*
|
||||
* As with [updateInAppPaymentWithCancelation], this only writes if the latest payment is in the END state without
|
||||
* cancelation data.
|
||||
*/
|
||||
fun updateInAppPaymentWithLapsedSubscription(subscriberType: InAppPaymentSubscriberRecord.Type) {
|
||||
writeCancelation(subscriberType, chargeFailure = null)
|
||||
}
|
||||
|
||||
private fun writeCancelation(subscriberType: InAppPaymentSubscriberRecord.Type, chargeFailure: ActiveSubscription.ChargeFailure?) {
|
||||
val subscriber = getSubscriber(subscriberType) ?: return
|
||||
val latestPayment = SignalDatabase.inAppPayments.getLatestBySubscriberId(subscriber.subscriberId) ?: return
|
||||
if (latestPayment.state == InAppPaymentTable.State.END && latestPayment.data.cancellation == null) {
|
||||
synchronized(subscriber.type.lock) {
|
||||
val payment = SignalDatabase.inAppPayments.getLatestBySubscriberId(subscriber.subscriberId) ?: return
|
||||
|
||||
Log.i(TAG, "[$subscriberType] Recording cancelation in the database. (has charge failure? ${chargeFailure != null})")
|
||||
SignalDatabase.inAppPayments.update(
|
||||
payment.copy(
|
||||
data = payment.data.newBuilder()
|
||||
.cancellation(
|
||||
InAppPaymentData.Cancellation(
|
||||
reason = if (chargeFailure != null) InAppPaymentData.Cancellation.Reason.PAST_DUE else InAppPaymentData.Cancellation.Reason.CANCELED,
|
||||
chargeFailure = chargeFailure?.toInAppPaymentDataChargeFailure()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,12 @@ class InAppPaymentKeepAliveJob private constructor(
|
||||
|
||||
val subscription: ActiveSubscription.Subscription? = activeSubscription.activeSubscription
|
||||
if (subscription == null) {
|
||||
info(type, "User does not have a subscription. Exiting.")
|
||||
info(type, "User does not have a subscription.")
|
||||
|
||||
if (type == InAppPaymentSubscriberRecord.Type.DONATION) {
|
||||
InAppPaymentsRepository.updateInAppPaymentWithLapsedSubscription(type)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule
|
||||
import org.thoughtcrime.securesms.testutil.MockSignalStoreRule
|
||||
import org.thoughtcrime.securesms.testutil.SystemOutLogger
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.ChargeFailure
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse
|
||||
@@ -93,6 +94,38 @@ class InAppPaymentKeepAliveJobTest {
|
||||
verify(exactly = 1) { InAppPaymentsRepository.updateInAppPaymentWithCancelation(activeSubscription, InAppPaymentSubscriberRecord.Type.DONATION) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no subscription on the server, when I run, then I write cancellation and return early`() {
|
||||
inAppPaymentsTestRule.initializeActiveSubscriptionMock(activeSubscription = ActiveSubscription(null, null))
|
||||
every { InAppPaymentsRepository.updateInAppPaymentWithLapsedSubscription(any()) } returns Unit
|
||||
|
||||
val job = InAppPaymentKeepAliveJob.create(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||
val result = job.run()
|
||||
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
verify(exactly = 1) { InAppPaymentsRepository.updateInAppPaymentWithLapsedSubscription(InAppPaymentSubscriberRecord.Type.DONATION) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no subscription on the server for a backup subscriber, when I run, then I do not write cancellation`() {
|
||||
val backupSubscriber = InAppPaymentSubscriberRecord(
|
||||
subscriberId = SubscriberId.generate(),
|
||||
currency = java.util.Currency.getInstance("USD"),
|
||||
type = InAppPaymentSubscriberRecord.Type.BACKUP,
|
||||
requiresCancel = false,
|
||||
paymentMethodType = org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData.PaymentMethodType.CARD,
|
||||
iapSubscriptionId = null
|
||||
)
|
||||
every { InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP) } returns backupSubscriber
|
||||
inAppPaymentsTestRule.initializeActiveSubscriptionMock(activeSubscription = ActiveSubscription(null, null))
|
||||
every { InAppPaymentsRepository.updateInAppPaymentWithLapsedSubscription(any()) } returns Unit
|
||||
|
||||
val job = InAppPaymentKeepAliveJob.create(InAppPaymentSubscriberRecord.Type.BACKUP)
|
||||
job.run()
|
||||
|
||||
verify(exactly = 0) { InAppPaymentsRepository.updateInAppPaymentWithLapsedSubscription(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a past-due subscription with charge failure, when I run, then I do not write cancellation`() {
|
||||
val chargeFailure = ChargeFailure("test", "", "", "", "")
|
||||
|
||||
Reference in New Issue
Block a user