mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 00:29:11 +01:00
Utilize InAppPaymentTable as the SSOT for ManageDonationsFragment.
This commit is contained in:
committed by
jeffrey-signal
parent
d06febd5b5
commit
7fb866fcfb
@@ -81,15 +81,24 @@ object InAppPaymentsRepository {
|
||||
* This operation will only be performed if we find a latest payment for the given subscriber id in the END state without cancelation data
|
||||
*/
|
||||
fun updateBackupInAppPaymentWithCancelation(activeSubscription: ActiveSubscription) {
|
||||
if (activeSubscription.isCanceled || activeSubscription.willCancelAtPeriodEnd()) {
|
||||
val subscriber = getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP) ?: return
|
||||
updateInAppPaymentWithCancelation(activeSubscription, InAppPaymentSubscriberRecord.Type.BACKUP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the latest payment object for the given subscriber type with cancelation information as necessary.
|
||||
*
|
||||
* This operation will only be performed if we find a latest payment for the given subscriber id in the END state without cancelation data.
|
||||
*/
|
||||
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
|
||||
|
||||
Log.i(TAG, "Recording cancelation in the database. (has charge failure? ${chargeFailure != null})")
|
||||
Log.i(TAG, "[$subscriberType] Recording cancelation in the database. (has charge failure? ${chargeFailure != null})")
|
||||
SignalDatabase.inAppPayments.update(
|
||||
payment.copy(
|
||||
data = payment.data.newBuilder()
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
@@ -31,7 +30,7 @@ object ActiveSubscriptionPreference {
|
||||
val subscription: Subscription,
|
||||
val renewalTimestamp: Long = -1L,
|
||||
val redemptionState: ManageDonationsState.RedemptionState,
|
||||
val activeSubscription: ActiveSubscription.Subscription?,
|
||||
val isPaymentFailure: Boolean = false,
|
||||
val subscriberRequiresCancel: Boolean,
|
||||
val onContactSupport: () -> Unit,
|
||||
val onRowClick: (ManageDonationsState.RedemptionState) -> Unit
|
||||
@@ -46,7 +45,7 @@ object ActiveSubscriptionPreference {
|
||||
renewalTimestamp == newItem.renewalTimestamp &&
|
||||
redemptionState == newItem.redemptionState &&
|
||||
FiatMoney.equals(price, newItem.price) &&
|
||||
activeSubscription == newItem.activeSubscription
|
||||
isPaymentFailure == newItem.isPaymentFailure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +111,7 @@ object ActiveSubscriptionPreference {
|
||||
}
|
||||
|
||||
private fun presentFailureState(model: Model) {
|
||||
if (model.activeSubscription?.isFailedPayment == true || model.subscriberRequiresCancel) {
|
||||
if (model.isPaymentFailure || model.subscriberRequiresCancel) {
|
||||
presentPaymentFailureState(model)
|
||||
} else {
|
||||
presentRedemptionFailureState(model)
|
||||
|
||||
@@ -33,9 +33,12 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.models.Ne
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.thanks.ThanksForYourSupportBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DonationErrorValue
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.help.HelpFragment
|
||||
import org.thoughtcrime.securesms.jobs.InAppPaymentKeepAliveJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.subscription.Subscription
|
||||
@@ -44,9 +47,7 @@ import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
|
||||
/**
|
||||
@@ -193,13 +194,16 @@ class ManageDonationsFragment :
|
||||
|
||||
space(16.dp)
|
||||
|
||||
if (state.subscriptionTransactionState is ManageDonationsState.TransactionState.NotInTransaction) {
|
||||
val activeSubscription = state.subscriptionTransactionState.activeSubscription.activeSubscription
|
||||
|
||||
if (activeSubscription != null && !activeSubscription.isCanceled) {
|
||||
val subscription: Subscription? = state.availableSubscriptions.firstOrNull { it.level == activeSubscription.level }
|
||||
if (!state.isLoaded) {
|
||||
customPref(IndeterminateLoadingCircle)
|
||||
} else if (state.networkError) {
|
||||
presentNetworkFailureSettings(state, state.hasReceipts)
|
||||
} else {
|
||||
val payment = state.activeSubscription
|
||||
if (payment != null && payment.data.cancellation == null) {
|
||||
val subscription: Subscription? = state.availableSubscriptions.firstOrNull { it.level == payment.data.level.toInt() }
|
||||
if (subscription != null) {
|
||||
presentSubscriptionSettings(activeSubscription, subscription, state)
|
||||
presentSubscriptionSettings(payment, subscription, state)
|
||||
} else {
|
||||
customPref(IndeterminateLoadingCircle)
|
||||
}
|
||||
@@ -215,10 +219,6 @@ class ManageDonationsFragment :
|
||||
} else {
|
||||
presentNotADonorSettings(state.hasReceipts)
|
||||
}
|
||||
} else if (state.subscriptionTransactionState == ManageDonationsState.TransactionState.NetworkFailure) {
|
||||
presentNetworkFailureSettings(state, state.hasReceipts)
|
||||
} else {
|
||||
customPref(IndeterminateLoadingCircle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,22 +275,28 @@ class ManageDonationsFragment :
|
||||
}
|
||||
|
||||
private fun DSLConfiguration.presentSubscriptionSettings(
|
||||
activeSubscription: ActiveSubscription.Subscription,
|
||||
payment: InAppPaymentTable.InAppPayment,
|
||||
subscription: Subscription,
|
||||
state: ManageDonationsState
|
||||
) {
|
||||
val price = payment.data.amount!!.toFiatMoney()
|
||||
val renewalTimestamp = payment.endOfPeriodSeconds.seconds.inWholeMilliseconds
|
||||
val isPaymentFailure = payment.data.error?.let {
|
||||
it.type != InAppPaymentData.Error.Type.REDEMPTION && it.data_ != InAppPaymentKeepAliveJob.KEEP_ALIVE
|
||||
} ?: false
|
||||
|
||||
presentSubscriptionSettingsWithState(state) {
|
||||
customPref(
|
||||
ActiveSubscriptionPreference.Model(
|
||||
price = FiatMoney.fromSignalNetworkAmount(activeSubscription.amount, Currency.getInstance(activeSubscription.currency)),
|
||||
price = price,
|
||||
subscription = subscription,
|
||||
renewalTimestamp = TimeUnit.SECONDS.toMillis(activeSubscription.endOfCurrentPeriod),
|
||||
redemptionState = state.getMonthlyDonorRedemptionState(),
|
||||
renewalTimestamp = renewalTimestamp,
|
||||
redemptionState = state.subscriptionRedemptionState,
|
||||
onContactSupport = {
|
||||
requireActivity().finish()
|
||||
requireActivity().startActivity(AppSettingsActivity.help(requireContext(), HelpFragment.DONATION_INDEX))
|
||||
},
|
||||
activeSubscription = activeSubscription,
|
||||
isPaymentFailure = isPaymentFailure,
|
||||
subscriberRequiresCancel = state.subscriberRequiresCancel,
|
||||
onRowClick = {
|
||||
launcher.launch(InAppPaymentType.RECURRING_DONATION)
|
||||
@@ -312,7 +318,6 @@ class ManageDonationsFragment :
|
||||
subscription = subscription,
|
||||
redemptionState = ManageDonationsState.RedemptionState.IN_PROGRESS,
|
||||
onContactSupport = {},
|
||||
activeSubscription = null,
|
||||
subscriberRequiresCancel = state.subscriberRequiresCancel,
|
||||
onRowClick = {}
|
||||
)
|
||||
|
||||
@@ -1,47 +1,24 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.manage
|
||||
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.subscription.Subscription
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
|
||||
data class ManageDonationsState(
|
||||
val hasOneTimeBadge: Boolean = false,
|
||||
val hasReceipts: Boolean = false,
|
||||
val featuredBadge: Badge? = null,
|
||||
val subscriptionTransactionState: TransactionState = TransactionState.Init,
|
||||
val isLoaded: Boolean = false,
|
||||
val networkError: Boolean = false,
|
||||
val availableSubscriptions: List<Subscription> = emptyList(),
|
||||
val activeSubscription: InAppPaymentTable.InAppPayment? = null,
|
||||
val subscriptionRedemptionState: RedemptionState = RedemptionState.NONE,
|
||||
val pendingOneTimeDonation: PendingOneTimeDonation? = null,
|
||||
val nonVerifiedMonthlyDonation: NonVerifiedMonthlyDonation? = null,
|
||||
val subscriberRequiresCancel: Boolean = false,
|
||||
private val subscriptionRedemptionState: RedemptionState = RedemptionState.NONE
|
||||
val subscriberRequiresCancel: Boolean = false
|
||||
) {
|
||||
|
||||
fun getMonthlyDonorRedemptionState(): RedemptionState {
|
||||
return when (subscriptionTransactionState) {
|
||||
TransactionState.Init -> subscriptionRedemptionState
|
||||
TransactionState.NetworkFailure -> subscriptionRedemptionState
|
||||
TransactionState.InTransaction -> RedemptionState.IN_PROGRESS
|
||||
is TransactionState.NotInTransaction -> getStateFromActiveSubscription(subscriptionTransactionState.activeSubscription) ?: subscriptionRedemptionState
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStateFromActiveSubscription(activeSubscription: ActiveSubscription): RedemptionState? {
|
||||
return when {
|
||||
activeSubscription.isFailedPayment && !activeSubscription.isPastDue -> RedemptionState.FAILED
|
||||
activeSubscription.isPendingBankTransfer -> RedemptionState.IS_PENDING_BANK_TRANSFER
|
||||
activeSubscription.isInProgress -> RedemptionState.IN_PROGRESS
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TransactionState {
|
||||
data object Init : TransactionState()
|
||||
data object NetworkFailure : TransactionState()
|
||||
data object InTransaction : TransactionState()
|
||||
class NotInTransaction(val activeSubscription: ActiveSubscription) : TransactionState()
|
||||
}
|
||||
|
||||
enum class RedemptionState {
|
||||
NONE,
|
||||
IN_PROGRESS,
|
||||
|
||||
@@ -3,33 +3,35 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.manage
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.asFlow
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DonationErrorValue
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.jobs.InAppPaymentKeepAliveJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.subscription.LevelUpdate
|
||||
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import java.util.Optional
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class ManageDonationsViewModel : ViewModel() {
|
||||
|
||||
@@ -62,6 +64,46 @@ class ManageDonationsViewModel : ViewModel() {
|
||||
internalDisplayThanksBottomSheetPulse.emit(Badges.fromDatabaseBadge(it.data.badge!!))
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
updateRecurringDonationState()
|
||||
|
||||
InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.RECURRING_DONATION)
|
||||
.asFlow()
|
||||
.collect { redemptionStatus ->
|
||||
val latestPayment = SignalDatabase.inAppPayments.getLatestInAppPaymentByType(InAppPaymentType.RECURRING_DONATION)
|
||||
|
||||
val activeSubscription: InAppPaymentTable.InAppPayment? = latestPayment?.let {
|
||||
if (it.data.cancellation == null) it else null
|
||||
}
|
||||
|
||||
store.update { manageDonationsState ->
|
||||
manageDonationsState.copy(
|
||||
nonVerifiedMonthlyDonation = if (redemptionStatus is DonationRedemptionJobStatus.PendingExternalVerification) redemptionStatus.nonVerifiedMonthlyDonation else null,
|
||||
subscriptionRedemptionState = deriveRedemptionState(redemptionStatus, latestPayment),
|
||||
activeSubscription = activeSubscription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.ONE_TIME_DONATION)
|
||||
.asFlow()
|
||||
.collect { redemptionStatus ->
|
||||
val pendingOneTimeDonation = when (redemptionStatus) {
|
||||
is DonationRedemptionJobStatus.PendingExternalVerification -> redemptionStatus.pendingOneTimeDonation
|
||||
DonationRedemptionJobStatus.PendingReceiptRedemption,
|
||||
DonationRedemptionJobStatus.PendingReceiptRequest -> {
|
||||
val latestPayment = SignalDatabase.inAppPayments.getLatestInAppPaymentByType(InAppPaymentType.ONE_TIME_DONATION)
|
||||
latestPayment?.toPendingOneTimeDonation()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
store.update { it.copy(pendingOneTimeDonation = pendingOneTimeDonation) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
@@ -69,8 +111,8 @@ class ManageDonationsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun retry() {
|
||||
if (!disposables.isDisposed && store.state.subscriptionTransactionState == ManageDonationsState.TransactionState.NetworkFailure) {
|
||||
store.update { it.copy(subscriptionTransactionState = ManageDonationsState.TransactionState.Init) }
|
||||
if (!disposables.isDisposed && store.state.networkError) {
|
||||
store.update { it.copy(networkError = false) }
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
@@ -78,16 +120,23 @@ class ManageDonationsViewModel : ViewModel() {
|
||||
fun refresh() {
|
||||
disposables.clear()
|
||||
|
||||
val levelUpdateOperationEdges: Observable<Boolean> = LevelUpdate.isProcessing.distinctUntilChanged()
|
||||
val activeSubscription: Single<ActiveSubscription> = RecurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||
InAppPaymentKeepAliveJob.enqueueAndTrackTime(System.currentTimeMillis().milliseconds)
|
||||
|
||||
disposables += Single.fromCallable {
|
||||
InAppPaymentsRepository.getShouldCancelSubscriptionBeforeNextSubscribeAttempt(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||
}.subscribeOn(Schedulers.io()).subscribeBy { requiresCancel ->
|
||||
store.update {
|
||||
it.copy(subscriberRequiresCancel = requiresCancel)
|
||||
}.subscribeOn(Schedulers.io()).subscribeBy(
|
||||
onSuccess = { requiresCancel ->
|
||||
store.update {
|
||||
it.copy(subscriberRequiresCancel = requiresCancel)
|
||||
}
|
||||
},
|
||||
onError = { throwable ->
|
||||
Log.w(TAG, "Error retrieving cancel state", throwable)
|
||||
store.update {
|
||||
it.copy(networkError = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
disposables += Recipient.observable(Recipient.self().id).map { it.badges }.subscribeBy { badges ->
|
||||
store.update { state ->
|
||||
@@ -101,53 +150,6 @@ class ManageDonationsViewModel : ViewModel() {
|
||||
store.update { it.copy(hasReceipts = hasReceipts) }
|
||||
}
|
||||
|
||||
disposables += InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.RECURRING_DONATION).subscribeBy { redemptionStatus ->
|
||||
store.update { manageDonationsState ->
|
||||
manageDonationsState.copy(
|
||||
nonVerifiedMonthlyDonation = if (redemptionStatus is DonationRedemptionJobStatus.PendingExternalVerification) redemptionStatus.nonVerifiedMonthlyDonation else null,
|
||||
subscriptionRedemptionState = mapStatusToRedemptionState(redemptionStatus)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
disposables += Observable.combineLatest(
|
||||
SignalStore.inAppPayments.observablePendingOneTimeDonation,
|
||||
InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.ONE_TIME_DONATION)
|
||||
) { pendingFromStore, pendingFromJob ->
|
||||
if (pendingFromStore.isPresent) {
|
||||
pendingFromStore
|
||||
} else if (pendingFromJob is DonationRedemptionJobStatus.PendingExternalVerification) {
|
||||
Optional.ofNullable(pendingFromJob.pendingOneTimeDonation)
|
||||
} else {
|
||||
Optional.empty()
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.subscribeBy { pending ->
|
||||
store.update { it.copy(pendingOneTimeDonation = pending.orNull()) }
|
||||
}
|
||||
|
||||
disposables += levelUpdateOperationEdges.switchMapSingle { isProcessing ->
|
||||
if (isProcessing) {
|
||||
Single.just(ManageDonationsState.TransactionState.InTransaction)
|
||||
} else {
|
||||
activeSubscription.map { ManageDonationsState.TransactionState.NotInTransaction(it) }
|
||||
}
|
||||
}.subscribeBy(
|
||||
onNext = { transactionState ->
|
||||
store.update {
|
||||
it.copy(subscriptionTransactionState = transactionState)
|
||||
}
|
||||
},
|
||||
onError = { throwable ->
|
||||
Log.w(TAG, "Error retrieving subscription transaction state", throwable)
|
||||
|
||||
store.update {
|
||||
it.copy(subscriptionTransactionState = ManageDonationsState.TransactionState.NetworkFailure)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
disposables += RecurringInAppPaymentRepository.getSubscriptions().subscribeBy(
|
||||
onSuccess = { subs ->
|
||||
store.update { it.copy(availableSubscriptions = subs) }
|
||||
@@ -158,18 +160,70 @@ class ManageDonationsViewModel : ViewModel() {
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapStatusToRedemptionState(status: DonationRedemptionJobStatus): ManageDonationsState.RedemptionState {
|
||||
private fun updateRecurringDonationState() {
|
||||
val latestPayment = SignalDatabase.inAppPayments.getLatestInAppPaymentByType(InAppPaymentType.RECURRING_DONATION)
|
||||
|
||||
val activeSubscription: InAppPaymentTable.InAppPayment? = latestPayment?.let {
|
||||
if (it.data.cancellation == null) it else null
|
||||
}
|
||||
|
||||
store.update { manageDonationsState ->
|
||||
manageDonationsState.copy(
|
||||
isLoaded = true,
|
||||
activeSubscription = activeSubscription
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveRedemptionState(status: DonationRedemptionJobStatus, latestPayment: InAppPaymentTable.InAppPayment?): ManageDonationsState.RedemptionState {
|
||||
return when (status) {
|
||||
DonationRedemptionJobStatus.FailedSubscription -> ManageDonationsState.RedemptionState.FAILED
|
||||
DonationRedemptionJobStatus.None -> ManageDonationsState.RedemptionState.NONE
|
||||
DonationRedemptionJobStatus.PendingKeepAlive -> ManageDonationsState.RedemptionState.SUBSCRIPTION_REFRESH
|
||||
DonationRedemptionJobStatus.FailedSubscription -> ManageDonationsState.RedemptionState.FAILED
|
||||
|
||||
is DonationRedemptionJobStatus.PendingExternalVerification -> {
|
||||
if (latestPayment != null && (latestPayment.data.paymentMethodType == InAppPaymentData.PaymentMethodType.SEPA_DEBIT || latestPayment.data.paymentMethodType == InAppPaymentData.PaymentMethodType.IDEAL)) {
|
||||
ManageDonationsState.RedemptionState.IS_PENDING_BANK_TRANSFER
|
||||
} else {
|
||||
ManageDonationsState.RedemptionState.IN_PROGRESS
|
||||
}
|
||||
}
|
||||
|
||||
is DonationRedemptionJobStatus.PendingExternalVerification,
|
||||
DonationRedemptionJobStatus.PendingReceiptRedemption,
|
||||
DonationRedemptionJobStatus.PendingReceiptRequest -> ManageDonationsState.RedemptionState.IN_PROGRESS
|
||||
}
|
||||
}
|
||||
|
||||
private fun InAppPaymentTable.InAppPayment.toPendingOneTimeDonation(): PendingOneTimeDonation? {
|
||||
if (type.recurring || data.amount == null || data.badge == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return PendingOneTimeDonation(
|
||||
paymentMethodType = when (data.paymentMethodType) {
|
||||
InAppPaymentData.PaymentMethodType.UNKNOWN -> PendingOneTimeDonation.PaymentMethodType.CARD
|
||||
InAppPaymentData.PaymentMethodType.GOOGLE_PAY -> PendingOneTimeDonation.PaymentMethodType.CARD
|
||||
InAppPaymentData.PaymentMethodType.CARD -> PendingOneTimeDonation.PaymentMethodType.CARD
|
||||
InAppPaymentData.PaymentMethodType.SEPA_DEBIT -> PendingOneTimeDonation.PaymentMethodType.SEPA_DEBIT
|
||||
InAppPaymentData.PaymentMethodType.IDEAL -> PendingOneTimeDonation.PaymentMethodType.IDEAL
|
||||
InAppPaymentData.PaymentMethodType.PAYPAL -> PendingOneTimeDonation.PaymentMethodType.PAYPAL
|
||||
InAppPaymentData.PaymentMethodType.GOOGLE_PLAY_BILLING -> error("Not supported.")
|
||||
},
|
||||
amount = data.amount,
|
||||
badge = data.badge,
|
||||
timestamp = insertedAt.inWholeMilliseconds,
|
||||
error = data.error?.takeIf { it.data_ != InAppPaymentKeepAliveJob.KEEP_ALIVE }?.let {
|
||||
DonationErrorValue(
|
||||
type = when (it.type) {
|
||||
InAppPaymentData.Error.Type.REDEMPTION -> DonationErrorValue.Type.REDEMPTION
|
||||
InAppPaymentData.Error.Type.PAYMENT_PROCESSING -> DonationErrorValue.Type.PAYMENT
|
||||
else -> DonationErrorValue.Type.PAYMENT
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ManageDonationsViewModel::class.java)
|
||||
}
|
||||
|
||||
@@ -355,10 +355,8 @@ class InAppPaymentTable(context: Context, databaseHelper: SignalDatabase) : Data
|
||||
return readableDatabase.select()
|
||||
.from(TABLE_NAME)
|
||||
.where(
|
||||
"($STATE = ? OR $STATE = ? OR $STATE = ?) AND $TYPE = ?",
|
||||
State.serialize(State.PENDING),
|
||||
State.serialize(State.WAITING_FOR_AUTHORIZATION),
|
||||
State.serialize(State.END),
|
||||
"$STATE != ? AND $TYPE = ?",
|
||||
State.serialize(State.CREATED),
|
||||
InAppPaymentType.serialize(type)
|
||||
)
|
||||
.orderBy("$INSERTED_AT DESC")
|
||||
|
||||
@@ -153,6 +153,12 @@ class InAppPaymentKeepAliveJob private constructor(
|
||||
return
|
||||
}
|
||||
|
||||
if (type == InAppPaymentSubscriberRecord.Type.DONATION && activeSubscription.isCanceled) {
|
||||
info(type, "Subscription is canceled. Writing cancellation to DB.")
|
||||
InAppPaymentsRepository.updateInAppPaymentWithCancelation(activeSubscription, type)
|
||||
return
|
||||
}
|
||||
|
||||
val activeInAppPayment = getActiveInAppPayment(subscriber, subscription)
|
||||
if (activeInAppPayment == null) {
|
||||
warn(type, "Failed to generate active in-app payment. Exiting")
|
||||
|
||||
Reference in New Issue
Block a user